567. Permutation In String - Explanation
Description
You are given two strings s1 and s2.
Return true if s2 contains a permutation of s1, or false otherwise. That means if a permutation of s1 exists as a substring of s2, then return true.
Both strings only contain lowercase letters.
Example 1:
Input: s1 = "abc", s2 = "lecabee"
Output: trueExplanation: The substring "cab" is a permutation of "abc" and is present in "lecabee".
Example 2:
Input: s1 = "abc", s2 = "lecaabee"
Output: falseConstraints:
1 <= s1.length, s2.length <= 10000
Topics
Recommended Time & Space Complexity
You should aim for a solution with O(n) time and O(1) space, where n is the maximum of the lengths of the two strings.
Hint 1
A brute force solution would be to check every substring of s2 with s1 by sorting s1 as well as the substring of s2. This would be an O(n^2) solution. Can you think of a better way? Maybe we can use the freqency of the characters of both the strings as we did in checking anagrams.
Hint 2
We return false if the length of s1 is greater than the length of s2. To count the frequency of each character in a string, we can simply use an array of size O(26), since the character set consists of a through z (26 continuous characters). Which algorithm can we use now?
Hint 3
We use a sliding window approach on s2 with a fixed window size equal to the length of s1. To track the current window, we maintain a running frequency count of characters in s2. This frequency count represents the characters in the current window. At each step, if the frequency count matches that of s1, we return true.
Prerequisites
Before attempting this problem, you should be comfortable with:
- Hash Tables / Frequency Counting - Tracking character counts to compare substrings
- Sliding Window Technique - Efficiently checking fixed-size substrings without rebuilding counts
- String Manipulation - Working with substrings and character comparisons
- Sorting - Used in the brute-force approach to compare permutations
1. Brute Force
Intuition
The brute-force approach tries every possible substring of s2 and checks whether it is a permutation of s1.
To do this, we sort s1 once, and then for each substring of s2, we sort it and compare.
If the sorted substring matches the sorted s1, it means the substring contains exactly the same characters.
This method is simple to understand but very slow because it examines all substrings and sorts each one.
Algorithm
- Sort
s1so we can compare substrings against it. - Loop through every starting index
iins2. - For each
i, loop through every ending indexj ≥ i:- Extract the substring
s2[i : j + 1]. - Sort it and compare with sorted
s1. - If they match, return
true.
- Extract the substring
- If no matching substring is found, return
false.
Time & Space Complexity
- Time complexity:
- Space complexity:
2. Hash Table
Intuition
We first count the characters in s1, since any valid substring in s2 must match these exact frequencies.
Then, for every starting point in s2, we build a frequency map as we extend the substring.
If we ever exceed the needed count for a character, we stop early because the substring can no longer be a valid permutation.
If all character counts match exactly, we have found a valid permutation.
This method is much cleaner than brute force but still slow because it restarts counting for each position.
Algorithm
- Build a frequency map
count1for all characters ins1. - Let
needbe the number of unique characters ins1whose counts must match. - For each starting index
iins2:- Create an empty map
count2and a match countercur = 0. - Extend the substring by moving
jfromiforward:- Increment the frequency of
s2[j]incount2. - If
count2[s2[j]]exceeds whatcount1requires, break — this substring can't work. - If the count for this character now matches
count1, increasecur. - If
cur == need, returntrue— we found a valid permutation.
- Increment the frequency of
- Create an empty map
- If no starting index yields a match, return
false.
class Solution:
def checkInclusion(self, s1: str, s2: str) -> bool:
count1 = {}
for c in s1:
count1[c] = 1 + count1.get(c, 0)
need = len(count1)
for i in range(len(s2)):
count2, cur = {}, 0
for j in range(i, len(s2)):
count2[s2[j]] = 1 + count2.get(s2[j], 0)
if count1.get(s2[j], 0) < count2[s2[j]]:
break
if count1.get(s2[j], 0) == count2[s2[j]]:
cur += 1
if cur == need:
return True
return FalseTime & Space Complexity
- Time complexity:
- Space complexity: since we have at most different characters.
Where is the length of the string1 and is the length of string2.
3. Sliding Window
Intuition
Since a permutation of s1 must have the same character counts, we can use a fixed-size sliding window over s2 whose length is exactly len(s1).
We maintain two frequency arrays:
- one for
s1 - one for the current window in
s2
If these two arrays ever match, the window is a valid permutation.
As we slide the window forward, we update counts by removing the left character and adding the new right character — no need to rebuild the counts each time.
This makes the solution fast and efficient.
Algorithm
- If
s1is longer thans2, returnfalse. - Build character frequency arrays for:
s1- the first window of
s2of sizelen(s1)
- Count how many positions match between the two arrays (
matches). - Slide the window from left to right across
s2:- At each step, add the new right character and update counts/matches.
- Remove the left character and update counts/matches.
- If at any time
matches == 26, returntrue.
- After finishing the loop, return whether
matches == 26.
class Solution:
def checkInclusion(self, s1: str, s2: str) -> bool:
if len(s1) > len(s2):
return False
s1Count, s2Count = [0] * 26, [0] * 26
for i in range(len(s1)):
s1Count[ord(s1[i]) - ord('a')] += 1
s2Count[ord(s2[i]) - ord('a')] += 1
matches = 0
for i in range(26):
matches += (1 if s1Count[i] == s2Count[i] else 0)
l = 0
for r in range(len(s1), len(s2)):
if matches == 26:
return True
index = ord(s2[r]) - ord('a')
s2Count[index] += 1
if s1Count[index] == s2Count[index]:
matches += 1
elif s1Count[index] + 1 == s2Count[index]:
matches -= 1
index = ord(s2[l]) - ord('a')
s2Count[index] -= 1
if s1Count[index] == s2Count[index]:
matches += 1
elif s1Count[index] - 1 == s2Count[index]:
matches -= 1
l += 1
return matches == 26Time & Space Complexity
- Time complexity:
- Space complexity:
Common Pitfalls
Not Checking Length Before Proceeding
If s1 is longer than s2, no permutation of s1 can exist as a substring of s2. Forgetting this early check leads to unnecessary computation or index errors when initializing the sliding window.
Incorrect Window Size
The sliding window must be exactly the length of s1. A common mistake is using a variable-size window or forgetting to remove the leftmost character when adding a new right character. This breaks the frequency comparison and produces incorrect results.
Mishandling the Matches Counter
When updating the frequency counts during window sliding, the matches counter must be carefully updated. A character transition from matching to non-matching should decrement matches, and vice versa. Off-by-one errors in these conditions (e.g., checking == vs +1 or -1) cause the algorithm to miss valid windows or report false positives.
Sign in to join the discussion