Start learning free

LESSON 396 OF 572 · SLIDING WINDOW

Permutation in String in C#: O(n + m) Sliding Window with Counts

Practice O(n) Read the full explanation

You are given two strings s1 and s2 made of lowercase English letters. Return true if s2 contains a permutation of s1 as a substring, and false otherwise.

A permutation of s1 is a string made of the same letters as s1, each one as many times, in any order: "abb", "bab" and "bba" are the permutations of "abb". A substring is a run of consecutive characters of s2.

So the question is: does s2 have a run of consecutive letters, exactly as long as s1, that holds every letter as many times as s1 does?

Your solution returns a boolean, and the tests print 1 for true and 0 for false.

 

Example 1:

Input: s1 = "ab", s2 = "eidbaooo"
Output: true
Explanation: s2 contains "ba", at indexes 3 and 4, and "ba" is a permutation of "ab".

Example 2:

Input: s1 = "ab", s2 = "eidboaoo"
Output: false
Explanation: s2 has an a and a b, but never next to each other: every run of two letters misses one of them.

Example 3:

Input: s1 = "adc", s2 = "dcda"
Output: true
Explanation: The last three letters, "cda", hold one a, one c and one d, just like "adc". The first three, "dcd", have two d's and no a.

 

Constraints:

  • 1 <= s1.length, s2.length <= 105
  • s1 and s2 are made of lowercase English letters only (a to z).

Note:

Your algorithm should run in O(n + m) time, where n is the length of s2 and m the length of s1, and use O(1) extra space: one count for each of the 26 letters. Checking every substring of s2 on its own takes O(n · m) time, too slow for the largest tests.

STEP 1 OF 14
    Ctrl + Enter to check

    C# · SLIDING WINDOW

    Permutation In String in C#, explained

    Problem overview

    The method CheckInclusion receives two strings of lowercase letters, s1 and s2. It returns true when some substring of s2 is a permutation of s1: a run of consecutive letters of s2, exactly as long as s1, that holds every letter as many times as s1 does, in any order.

    For s1 = "ab" and s2 = "eidbaooo" the answer is true, because s2 contains "ba". For s2 = "eidboaoo" it is false: there is an a and a b, but never next to each other. For s1 = "adc" and s2 = "dcda" it is true: the last three letters, "cda", hold one a, one c and one d.

    Approach: a window that counts letters

    The direct way is to check every substring of s2 of length s1.Length on its own, counting its letters from scratch. That takes O(n * m) time, where n is the length of s2 and m the length of s1. Both can reach 100,000, which is too slow.

    Notice that two neighbouring windows share almost all their letters. When the window slides one step to the right, one letter comes in on the right and one leaves on the left. So there is no need to recount: update the counts for those two letters only.

    The lesson keeps an array need of 26 counts, one per letter. need[0] says how many more as the window still needs, need[1] the bs, and so on. At the start the window is empty, so need holds the letter counts of s1. A letter entering the window lowers its count; a letter leaving raises it. A second number, missing, tracks how many letters of s1 the window still lacks in total. It starts at s1.Length, and when it reaches 0 the window holds everything s1 has. Since the window is never longer than s1, there is no room for anything else, so it is a permutation.

    The count in need can go below 0: the window then holds more of that letter than s1 has. Those extra copies must not lower missing, which is why the code only does missing-- when the count was above 0.

    Here is the run for s1 = "adc" and s2 = "dcda":

    last=0 in d missing=2
    last=1 in c missing=1
    last=2 in d missing=1
    last=3 in a out d missing=0
    -> true
    

    The second d was an extra copy, so missing stayed at 1. At last=3 the first d leaves and the a comes in, so the window is "cda" and nothing is missing.

    Step by step

    1. Create need with new int[26]. A new int array is filled with zeros.
    2. Count the letters of s1 with a foreach over its characters, and add 1 to need[c - 'a'] for each. A char is a number, its code, and a to z have consecutive codes, so c - 'a' is 0 for a, 1 for b and so on.
    3. Set missing to s1.Length.
    4. Slide the window with a loop over last, the index of the letter that enters on the right.
    5. Find the letter's number: code = s2[last] - 'a'.
    6. Update missing first: if need[code] is above 0, the window needed this letter, so missing--. At 0 or below, the copy is extra and missing stays.
    7. Then lower need[code] by one: the window holds one more of this letter.
    8. Drop the oldest letter when the window is too long. If last >= s1.Length, the window is one letter longer than s1. The letter that leaves is s2[last - s1.Length].
    9. Undo its effect: raise need[oldCode] by one. If the count is now above 0, the window lost a letter that s1 needs, so missing++. If it is still 0 or below, the window had a spare copy and lost nothing.
    10. Check for a match: if missing == 0, return true. In the first turns the window is shorter than s1, so it can't hold all its letters yet.
    11. After the loop, return false.

    The solution in C#

    class Solution {
        public bool CheckInclusion(string s1, string s2) {
            int[] need = new int[26];
            foreach (char c in s1) {
                need[c - 'a']++;
            }
            int missing = s1.Length;
            for (int last = 0; last < s2.Length; last++) {
                int code = s2[last] - 'a';
                if (need[code] > 0) missing--;
                need[code]--;
                if (last >= s1.Length) {
                    int oldCode = s2[last - s1.Length] - 'a';
                    need[oldCode]++;
                    if (need[oldCode] > 0) missing++;
                }
                if (missing == 0) return true;
            }
            return false;
        }
    }
    

    The C# details worth knowing:

    • Subtracting chars gives an int. c - 'a' has type int, so it can index the array directly, with no cast.
    • Strings are indexable. s2[last] returns a char. Strings are immutable, so the code never changes them and never builds substrings: slicing with Substring inside the loop would copy letters and bring back the O(n * m) cost.
    • need[c - 'a']++ is a single statement that reads, adds one and writes back. The same works for -- and for entries of an array.
    • Bounds are checked. A character outside a to z, such as an uppercase letter, would give an index below 0 or above 25 and throw an IndexOutOfRangeException. The problem promises lowercase letters only.
    • Single-line if. A statement without braces is legal for one statement, as in if (missing == 0) return true;. Adding braces is a safe habit when you later add a second line.

    Complexity

    Time: O(n + m). Counting the letters of s1 takes m steps. The window loop runs n times, and each turn does a constant amount of work: each letter of s2 comes into the window once and leaves it once.

    Space: O(1). The array has 26 entries whatever the input length.

    Edge cases and common mistakes

    • s1 longer than s2. "abc" in "ab" returns false. The window never gets enough letters, so missing never reaches 0, and the final return false covers it.
    • Repeated letters. "aab" in "abab" returns true ("aba" has two as and a b), while "aab" in "abcb" returns false. A plain "is this letter in s1?" check would not tell these apart; the counts do.
    • Same length. "abc" in "abc" returns true. The window never needs to slide off a letter, and last >= s1.Length is never true.
    • One letter. "a" in "a" returns true.
    • Lowering missing for extra copies. If missing-- ran for every letter that enters, a window with a repeated letter, such as "aab" against a window "abb", could reach 0 without being a permutation. The check need[code] > 0 is what stops it.
    • Updating in the wrong order. Lowering need[code] before testing it makes the test compare the wrong value. Test first, then lower.
    • Forgetting to remove the old letter. The window would grow past the length of s1, and a window that is too long can't be a permutation.

    Testing

    • "ab" in "eidbaooo" gives true.
    • "ab" in "eidboaoo" gives false.
    • "adc" in "dcda" gives true.
    • "abc" in "ab" gives false.
    • "a" in "a" gives true.
    • "aab" in "abab" gives true.
    • "aab" in "abcb" gives false.
    • "abc" in "abc" gives true.
    Keep going with C#500+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free