Permutation in String

Mediumstringsliding-windowhash-tabletwo-pointers
Category: Sliding Window
Companies that ask this question:
AmazonFacebookMicrosoftGoogle

Approach

Permutation in String

Problem

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 "ba" which is a permutation of s1

Example 2:

Input: s1 = "ab", s2 = "eidboaoo"
Output: false

Approach: Sliding Window with Frequency Count (Optimal)

Intuition

A permutation means the characters are the same, just in different order. So we need to find a substring in s2 that has the exact same character frequencies as s1.

Use a sliding window of size len(s1) and compare character frequencies.

Key Insight

  • Create frequency map for s1
  • Slide a window of size len(s1) through s2
  • At each position, check if window frequencies match s1 frequencies
  • Optimization: Instead of comparing entire maps, track number of matching characters

Algorithm

def checkInclusion(s1, s2):
    if len(s1) > len(s2):
        return False
    
    s1_count = {}
    s2_count = {}
    
    # Count characters in s1
    for c in s1:
        s1_count[c] = s1_count.get(c, 0) + 1
    
    # Initial window in s2
    for i in range(len(s1)):
        s2_count[s2[i]] = s2_count.get(s2[i], 0) + 1
    
    # Check initial window
    if s1_count == s2_count:
        return True
    
    # Slide window
    for i in range(len(s1), len(s2)):
        # Add new character
        s2_count[s2[i]] = s2_count.get(s2[i], 0) + 1
        
        # Remove old character
        left_char = s2[i - len(s1)]
        s2_count[left_char] -= 1
        if s2_count[left_char] == 0:
            del s2_count[left_char]
        
        # Check if match
        if s1_count == s2_count:
            return True
    
    return False

Example Walkthrough

For s1 = "ab", s2 = "eidbaooo":

s1_count = {a:1, b:1}

Window "ei": {e:1, i:1} ✗
Window "id": {i:1, d:1} ✗
Window "db": {d:1, b:1} ✗
Window "ba": {b:1, a:1} ✓ MATCH!

Time Complexity

  • O(n): Single pass through s2, where n = len(s2)
  • Map comparison is O(26) = O(1) for lowercase letters

Space Complexity

  • O(1): At most 26 characters in maps

Optimized Approach: Match Counter

Instead of comparing maps, count how many characters have matching frequencies.

def checkInclusion(s1, s2):
    if len(s1) > len(s2):
        return False
    
    s1_count = [0] * 26
    s2_count = [0] * 26
    
    for c in s1:
        s1_count[ord(c) - ord('a')] += 1
    
    matches = 0
    for i in range(26):
        if s1_count[i] == s2_count[i]:
            matches += 1
    
    for i in range(len(s2)):
        if matches == 26:
            return True
        
        # Add right character
        idx = ord(s2[i]) - ord('a')
        s2_count[idx] += 1
        if s2_count[idx] == s1_count[idx]:
            matches += 1
        elif s2_count[idx] == s1_count[idx] + 1:
            matches -= 1
        
        # Remove left character
        if i >= len(s1):
            idx = ord(s2[i - len(s1)]) - ord('a')
            s2_count[idx] -= 1
            if s2_count[idx] == s1_count[idx]:
                matches += 1
            elif s2_count[idx] == s1_count[idx] - 1:
                matches -= 1
    
    return matches == 26

Same complexity, but avoids repeated map comparisons.

Solution

java
1/**
2 * Permutation in String
3 * Time: O(n) where n = len(s2)
4 * Space: O(1) - at most 26 characters
5 */
6
7import java.util.*;
8
9// Approach 1: Sliding Window with Frequency Arrays
10class Solution {
11    public boolean checkInclusion(String s1, String s2) {
12        if (s1.length() > s2.length()) {
13            return false;
14        }
15        
16        int[] s1_count = new int[26];
17        int[] s2_count = new int[26];
18        
19        // Count s1 characters
20        for (char c : s1.toCharArray()) {
21            s1_count[c - 'a']++;
22        }
23        
24        // Initial window
25        for (int i = 0; i < s1.length(); i++) {
26            s2_count[s2.charAt(i) - 'a']++;
27        }
28        
29        // Check initial window
30        if (Arrays.equals(s1_count, s2_count)) {
31            return true;
32        }
33        
34        // Slide window
35        for (int i = s1.length(); i < s2.length(); i++) {
36            // Add new character
37            s2_count[s2.charAt(i) - 'a']++;
38            
39            // Remove old character
40            s2_count[s2.charAt(i - s1.length()) - 'a']--;
41            
42            // Check if match
43            if (Arrays.equals(s1_count, s2_count)) {
44                return true;
45            }
46        }
47        
48        return false;
49    }
50}
51
52// Approach 2: Match Counter (Optimized)
53class Solution2 {
54    public boolean checkInclusion(String s1, String s2) {
55        if (s1.length() > s2.length()) {
56            return false;
57        }
58        
59        int[] s1_count = new int[26];
60        int[] s2_count = new int[26];
61        
62        for (char c : s1.toCharArray()) {
63            s1_count[c - 'a']++;
64        }
65        
66        int matches = 0;
67        for (int i = 0; i < 26; i++) {
68            if (s1_count[i] == s2_count[i]) {
69                matches++;
70            }
71        }
72        
73        for (int i = 0; i < s2.length(); i++) {
74            if (matches == 26) {
75                return true;
76            }
77            
78            // Add right character
79            int idx = s2.charAt(i) - 'a';
80            s2_count[idx]++;
81            if (s2_count[idx] == s1_count[idx]) {
82                matches++;
83            } else if (s2_count[idx] == s1_count[idx] + 1) {
84                matches--;
85            }
86            
87            // Remove left character
88            if (i >= s1.length()) {
89                idx = s2.charAt(i - s1.length()) - 'a';
90                s2_count[idx]--;
91                if (s2_count[idx] == s1_count[idx]) {
92                    matches++;
93                } else if (s2_count[idx] == s1_count[idx] - 1) {
94                    matches--;
95                }
96            }
97        }
98        
99        return matches == 26;
100    }
101}
102
Loading visualizer...