The O(n) Club: Permutation in String — Where Brute Force Goes To Die
⚡ TL;DR
Given two strings, does
s2
have a substring that’s a permutation ofs1
? Please, step away from generating all permutations (unless you hate yourself). Sliding window + counting is your best friend:// Java hot-take: boolean checkInclusion(String s1, String s2) { // Count the letters of s1, slide a window over s2 }
🧠 How Devs Usually Mess This Up
Ah, the classic: generating all permutations of s1
and searching for them in s2
. If that’s your go-to, may your CPU rest in peace. Spoiler: “abc” has only 6 permutations, but “abcdefgh” has 40,320. That’s almost as many regrets as lines of spaghetti code in your legacy project. Also, folks love confusing substrings (must be consecutive!) with subsequences (which are basically ninja letters teleporting around). Don’t be that dev.
🔍 What’s Actually Going On
Picture s1
as a highly suspicious grocery list, and you want to see if any block of s2
(with the same length) matches your list, order doesn’t matter. No shuffling, no swapping, just matching baskets. Here’s the trick: use two arrays of 26 (because nobody is asking for Unicode Sudoku), one for s1
, one for the current window in s2
. Slide that window through s2
and look for a perfect match. Boom — no factorials required, just some good old frequency matching.
🛠️ PseudoCode
- Make a count array for s1.
int[] s1Count = new int[26];
Count every character ins1
. - Set up the first window in s2.
int[] s2Count = new int[26];
Count the firsts1.length()
chars ins2
. - Slide the window along s2:
- Compare
s1Count
ands2Count
. If match: you win. - Else, remove the leftmost char and add the next rightmost.
- Repeat until done (don’t skip the very last window — rookie mistake!).
- Compare
- Return true if you get a match. If not, at least you tried.
💻 The Code
public boolean checkInclusion(String s1, String s2) {
if (s1.length() > s2.length()) return false;
int[] s1Count = new int[26];
int[] s2Count = new int[26];
for (int i = 0; i < s1.length(); i++) {
s1Count[s1.charAt(i) - 'a']++;
s2Count[s2.charAt(i) - 'a']++;
}
int matches = 0;
for (int i = 0; i < 26; i++) {
if (s1Count[i] == s2Count[i]) matches++;
}
int left = 0;
for (int right = s1.length(); right < s2.length(); right++) {
if (matches == 26) return true;
int idxIn = s2.charAt(right) - 'a';
int idxOut = s2.charAt(left++) - 'a';
s2Count[idxIn]++;
if (s2Count[idxIn] == s1Count[idxIn]) matches++;
else if (s2Count[idxIn] == s1Count[idxIn] + 1) matches--;
s2Count[idxOut]--;
if (s2Count[idxOut] == s1Count[idxOut]) matches++;
else if (s2Count[idxOut] == s1Count[idxOut] - 1) matches--;
}
return matches == 26;
}
⚠️ Pitfalls, Traps & Runtime Smacks
- Substring means consecutive — not “anywhere in s2” and not “skip-around-anagram bingo”.
- If
s1
is longer thans2
, don’t waste precious cycles — return false first. - Don’t forget to check the last window after the loop! (Off-by-one errors send more code to production hell than anything else.)
- Performance: O(N) time (where N = s2.length). Space? Two arrays, 26 each. A bargain for this much efficiency.
🧠 Interviewer Brain-Tattoo
When interviewers say permutation and substring in one breath, just smile and reach for your frequency arrays. Leave the factorials to math club after school.