Permutation in String is the classic fixed-size sliding window. It teaches a move you will reuse constantly: scan a string once, keeping a window of a known length and a running letter count, updating it in O(1) as the window slides.
Problem. Given two strings s1 and s2, return True if s2 contains a permutation of
s1 as a substring — that is, a contiguous window of s2 that is some rearrangement of s1's letters.
Example: s1 = "ab", s2 = "eidbaooo" → True, because the window "ba" is a permutation of "ab".
The slow way first
The obvious idea: look at every substring of s2 that has the same length as s1, sort it (or count its letters), and compare to s1. There are about n such windows and sorting each costs O(k log k), so this is roughly O(n · k log k) — wasteful, because neighboring windows overlap almost entirely.
The question to ask: when the window shifts by one, what actually changed? Only two letters: the one that fell off the left and the one that joined on the right. Everything else is identical, so recomputing the whole window from scratch is pure waste.
The idea: a window that slides
Keep a window of length len(s1) and a count have of its letters. A window is a permutation of s1 exactly when have == need, where need is the letter count of s1. To move the window right by one, do two O(1) updates: add the new right letter and remove the old left letter. Compare and repeat.
The key insight: comparing two small fixed-size letter counts is O(1), and updating the window is O(1), so the whole scan is O(n).
Walk through it
Step through the animation. need = {a:1, b:1}. The window starts on "ei" — no match. It slides to "id", then "db" (a b but no a), then "ba". At "ba" the count is {b:1, a:1}, which equals need, so we return True immediately without scanning the trailing ooo.
Pseudocode
if len(s1) > len(s2): return False
need = letter counts of s1
have = letter counts of the first window s2[0 : len(s1)]
if have == need: return True
for r from len(s1) to len(s2) - 1:
add s2[r] to have # letter entering on the right
remove s2[r - len(s1)] from have # letter leaving on the left
if have == need: return True
return FalseThe Python solution
def check_inclusion(s1, s2):
if len(s1) > len(s2):
return False
need = Counter(s1)
have = Counter(s2[:len(s1)])
if have == need:
return True
for r in range(len(s1), len(s2)):
have[s2[r]] += 1
have[s2[r - len(s1)]] -= 1
if have[s2[r - len(s1)]] == 0:
del have[s2[r - len(s1)]]
if have == need:
return True
return Falseneedis the target letter count;haveis the count for the current window.- We seed
havefrom the first window and compare once before sliding (the loop only handles windows 1 onward). - Lines 9–10 are the heart: add the entering letter
s2[r], drop the leaving letters2[r - len(s1)]— both O(1). - We
dela key once its count hits 0 so thathave == needis an exact dictionary equality (a leftover{x: 0}would make them unequal). - Each
have == needcheck compares at most 26 entries, so it is O(1).
Complexity
| Case | Time | Notes |
|---|---|---|
| Sort every window | O(n · k log k) (moderate) | recomputes overlapping work |
| Sliding window (this) | O(n) (moderate) | two O(1) edits per slide |
O(1) (fast)Space is O(1): the counts hold at most 26 lowercase letters regardless of input size. We turned an O(n · k log k) scan into a single O(n) pass by updating the window instead of rebuilding it.
When this pattern shows up
Whenever a problem fixes the window length ahead of time — "substring of length k", "permutation / anagram of s1", "max sum of k consecutive elements" — reach for a fixed-size sliding window: add the entering element, remove the leaving one, and keep a running aggregate you can compare in O(1).
Remember to drop the leaving letter and to delete keys that reach 0. If you only ever add letters, have
keeps growing and will never equal need again after the first window.
Practice
With s1 = 'ab' and s2 = 'eidbaooo', the window slides e i, i d, d b, then b a. At which window do the counts first match need = {a:1, b:1}?
1. Why is the sliding-window solution O(n) instead of O(n·k log k)?
2. What must be true for the current window to be a permutation of s1?
3. Why do we delete a key from have when its count reaches 0?
4. What is the extra space used by this solution?