Our task is to find the smallest substring in a given string that contains all the characters of a given pattern. In other words, we are given two strings: one is the main string and the other is the pattern. We need to locate the smallest window in the main string which includes every character from the pattern. For example, consider: s = "geeksforgeeks" and p = "gks" here the smallest substring in s that contains all characters from p is "geeks".
Sliding Window with defaultdict()
This method uses a sliding window to expand and contract while tracking character frequencies with a defaultdict(). The window expands until it satisfies the required character counts from the pattern then contracts to find the smallest valid window.
from collections import defaultdict
s = "geeksforgeeks"
p = "gks"
src = defaultdict(int)
tgt = defaultdict(int)
for ch in p:
tgt[ch] += 1
best = ""
min_len = len(s) + 1
j = 0
for i in range(len(s)):
# Expand the window until it contains all required characters
while j < len(s) and not all(src.get(ch, 0) >= tgt[ch] for ch in tgt):
src[s[j]] += 1
j += 1
# If the window is valid and smaller than previously found, update best window
if all(src.get(ch, 0) >= tgt[ch] for ch in tgt) and (j - i) < min_len:
min_len = j - i
best = s[i:j]
src[s[i]] -= 1
print(best)
Output
geeks
Explanation:
- window expands by increasing j updating the character counts in src (a dictionary).
- when all characters in p are present with required frequencies in src, a valid window is found.
- window is shrunk by moving the left boundary (i) forward, reducing counts in src then the smallest valid window is stored and returned.
Dynamic Last-Occurrence Update
This approach dynamically updates the last seen indices for each required character as we traverse the string and when all required characters have been encountered, the window is defined from the minimum to the maximum index among these characters.
s = "geeksforgeeks"
p = "gks"
req = set(p)
last_occ = {}
best_window = s + "X" # Initialize with a value longer than s
for i, ch in enumerate(s):
if ch in req:
last_occ[ch] = i
if len(last_occ) == len(req):
start = min(last_occ.values())
end = max(last_occ.values())
window = s[start:end+1]
if len(window) < len(best_window):
best_window = window
print(best_window)
Output
geeks
Explanation:
- last_occ tracks the last index of characters in p found in s.
- when all characters from p are found then the leftmost and rightmost indices are used to form a window.
- smallest window is compared and updated if necessary and the final smallest window is returned.
Using enumerate()
This simple method uses enumerate() to collect all indices of characters in the main string that are in the pattern then extracts the substring spanning from the minimum to maximum index.
s = "new string"
p = "rg"
indices = [i for i, ch in enumerate(s) if ch in p]
res = s[min(indices): max(indices)+1] if indices else ""
print(res)
Output
ring
Explanation:
- enumerate(s) identifies indices of characters in p found in s and the leftmost and rightmost indices from indices form the window.
- substring between those indices is returned as the result.
- If no indices are found then an empty string is returned.