Permutation in String
MedPrompt
Given two strings s1 and s2, return true if s2 contains a permutation of s1, or false otherwise.
In other words, return true if one of s1's permutations is the substring of s2.
Example 1:
Input: s1 = "ab", s2 = "eidbaooo"
Output: true
Explanation: s2 contains one permutation of s1 ("ba").Example 2:
Input: s1 = "ab", s2 = "eidboaoo"
Output: false
Constraints:
1 <= s1.length, s2.length <= 104s1ands2consist of lowercase English letters.
Approaches
2 approaches with complexity analysis and trade-offs.
This approach is based on the property that two strings are permutations of each other if and only if their sorted versions are identical. We can iterate through all substrings of s2 that have the same length as s1. For each substring, we sort it and compare it with a pre-sorted version of s1.
Algorithm
- Let
nbe the length ofs1andmbe the length ofs2. - If
n > m, it's impossible fors2to contain a permutation ofs1, so returnfalse. - Convert
s1to a character array and sort it. This gives a canonical representation ofs1's permutations. - Iterate through
s2with a window of sizen. The loop runs fromi = 0tom - n. - For each window (substring of
s2fromitoi + n - 1):- Convert the substring to a character array and sort it.
- Compare the sorted substring with the sorted
s1. - If they are identical, it means the substring is a permutation of
s1. Returntrue.
- If the loop completes without finding any match, return
false.
Walkthrough
The core idea is to check every possible substring of s2 of length s1.length() and see if it's an anagram of s1. A reliable way to check for anagrams is to sort both strings and see if they become equal. While simple, this method involves a costly sorting operation inside a loop, which affects its performance significantly.
import java.util.Arrays; class Solution { public boolean checkInclusion(String s1, String s2) { if (s1.length() > s2.length()) { return false; } int n = s1.length(); int m = s2.length(); char[] s1Chars = s1.toCharArray(); Arrays.sort(s1Chars); for (int i = 0; i <= m - n; i++) { char[] subChars = s2.substring(i, i + n).toCharArray(); Arrays.sort(subChars); if (Arrays.equals(s1Chars, subChars)) { return true; } } return false; }}Complexity
Time
O((m-n) * n log n). Let `n` be the length of `s1` and `m` be the length of `s2`. Sorting `s1` initially takes `O(n log n)`. The main loop runs `m-n+1` times. Inside the loop, creating a substring takes `O(n)` and sorting it takes `O(n log n)`. This dominates, leading to the overall complexity.
Space
O(n), where `n` is the length of `s1`. This space is required to store the character arrays for `s1` and the substrings of `s2` for sorting.
Trade-offs
Pros
Conceptually straightforward and easy to implement.
Cons
Highly inefficient for large strings due to the repeated sorting of substrings within a loop.
Very likely to result in a 'Time Limit Exceeded' (TLE) error on competitive programming platforms for the given constraints.
A much more efficient method is to use the sliding window technique. We can determine if two strings are permutations by comparing their character frequency maps. We maintain a fixed-size window of length len(s1) that slides across s2. Instead of re-computing the frequency map for the window in each step, we efficiently update it by subtracting the character that leaves the window and adding the character that enters.
Algorithm
- Let
nbe the length ofs1andmbe the length ofs2. Ifn > m, returnfalse. - Since the strings consist of lowercase English letters, we can use two integer arrays of size 26,
s1Mapands2Map, as frequency maps. - Populate
s1Mapwith the character frequencies ofs1. - Populate
s2Mapwith the character frequencies of the first window ofs2(i.e., the firstncharacters). - Compare
s1Mapands2Map. If they are identical, a permutation is found. Returntrue. - Slide the window one character at a time from left to right across
s2. The loop runs fromi = ntom-1. - In each step of the slide:
- Add the new character entering the window: increment the count for
s2.charAt(i)ins2Map. - Remove the old character leaving the window: decrement the count for
s2.charAt(i - n)ins2Map. - After updating the window's map (
s2Map), compare it withs1Map. If they are equal, returntrue.
- Add the new character entering the window: increment the count for
- If the loop finishes without finding a match, return
false.
Walkthrough
This approach avoids the expensive sorting operation. By using frequency maps (arrays of size 26 for lowercase letters), we can check for permutations in O(1) time (or O(k) where k is the alphabet size, which is constant). The sliding window allows us to reuse the computation from the previous window. We only need to adjust the counts for two characters at each step: the one leaving the window and the one entering. This leads to a linear time solution.
import java.util.Arrays; class Solution { public boolean checkInclusion(String s1, String s2) { int n = s1.length(), m = s2.length(); if (n > m) { return false; } int[] s1Map = new int[26]; int[] s2Map = new int[26]; // Initialize maps for the first window for (int i = 0; i < n; i++) { s1Map[s1.charAt(i) - 'a']++; s2Map[s2.charAt(i) - 'a']++; } // Slide the window across s2 for (int i = 0; i < m - n; i++) { if (Arrays.equals(s1Map, s2Map)) { return true; } // Slide the window forward // Add the new character entering the window from the right s2Map[s2.charAt(i + n) - 'a']++; // Remove the character leaving the window from the left s2Map[s2.charAt(i) - 'a']--; } // Final check for the last window if (Arrays.equals(s1Map, s2Map)) { return true; } return false; }}Complexity
Time
O(n + (m-n)) = O(m). Let `n` be `s1.length()` and `m` be `s2.length()`. We take `O(n)` to build the initial frequency maps. Then, we iterate `m-n` times. In each step, updating the map is `O(1)`, and comparing the maps takes `O(k)` where `k` is the alphabet size (26). Since `k` is constant, the total time complexity is linear with respect to the length of `s2`.
Space
O(1). We use two arrays of a fixed size 26 to store character frequencies. Since the alphabet size is constant, the space complexity is constant.
Trade-offs
Pros
Optimal time complexity, making it very fast for large inputs.
Constant space complexity as the space used does not depend on the input string sizes.
Cons
Slightly more complex to reason about and implement compared to the naive sorting approach.
Solutions
Solution
class Solution { public boolean checkInclusion ( String s1 , String s2 ) { int n = s1 . length (); int m = s2 . length (); if ( n > m ) { return false ; } int [] cnt = new int [ 26 ]; for ( int i = 0 ; i < n ; ++ i ) { -- cnt [ s1 . charAt ( i ) - 'a' ]; ++ cnt [ s2 . charAt ( i ) - 'a' ]; } int diff = 0 ; for ( int x : cnt ) { if ( x != 0 ) { ++ diff ; } } if ( diff == 0 ) { return true ; } for ( int i = n ; i < m ; ++ i ) { int a = s2 . charAt ( i - n ) - 'a' ; int b = s2 . charAt ( i ) - 'a' ; if ( cnt [ b ] == 0 ) { ++ diff ; } if (++ cnt [ b ] == 0 ) { -- diff ; } if ( cnt [ a ] == 0 ) { ++ diff ; } if (-- cnt [ a ] == 0 ) { -- diff ; } if ( diff == 0 ) { return true ; } } return false ; } }public class Solution { public bool CheckInclusion(string s1, string s2) { int need = 0; int[] cnt = new int[26]; foreach(char c in s1) { if (++cnt[c - 'a'] == 1) { need++; } } int m = s1.Length, n = s2.Length; for (int i = 0; i < n; i++) { int c = s2[i] - 'a'; if (--cnt[c] == 0) { need--; } if (i >= m) { c = s2[i - m] - 'a'; if (++cnt[c] == 1) { need++; } } if (need == 0) { return true; } } return false; }}class Solution {public: bool checkInclusion(string s1, string s2) { int n = s1.size(), m = s2.size(); if (n > m) { return false; } vector<int> cnt(26); for (int i = 0; i < n; ++i) { --cnt[s1[i] - 'a']; ++cnt[s2[i] - 'a']; } int diff = 0; for (int x : cnt) { if (x) { ++diff; } } if (diff == 0) { return true; } for (int i = n; i < m; ++i) { int a = s2[i - n] - 'a'; int b = s2[i] - 'a'; if (cnt[b] == 0) { ++diff; } if (++cnt[b] == 0) { --diff; } if (cnt[a] == 0) { ++diff; } if (--cnt[a] == 0) { --diff; } if (diff == 0) { return true; } } return false; }};class Solution : def checkInclusion ( self , s1 : str , s2 : str ) -> bool : n , m = len ( s1 ), len ( s2 ) if n > m : return False cnt = Counter () for a , b in zip ( s1 , s2 ): cnt [ a ] -= 1 cnt [ b ] += 1 diff = sum ( x != 0 for x in cnt . values ()) if diff == 0 : return True for i in range ( n , m ): a , b = s2 [ i - n ], s2 [ i ] if cnt [ b ] == 0 : diff += 1 cnt [ b ] += 1 if cnt [ b ] == 0 : diff -= 1 if cnt [ a ] == 0 : diff += 1 cnt [ a ] -= 1 if cnt [ a ] == 0 : diff -= 1 if diff == 0 : return True return FalseVideo walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.