String · 33 of 43
Write a program to find the smallest window that contains all characters of string itself
Find the shortest stretch of a string that still contains every different letter the whole string has.
- Simple way
- time O(n^2)
room O(k) - Clever way
- time O(n)
room O(k)
The problem, simply
You get a string, like aabcbcdbca. First find which different letters it uses. Here that is a, b, c and d, so k = 4 kinds.
Find the shortest piece of the string, letters next to each other, that contains at least one of every kind. If several pieces tie, give back the first one.
A story to remember it
Picture it
aabcbcdbca uses 4 kinds of letters. The window dbca, from index 6 to 9, holds one of each, and no shorter window does.
- answer
Examples
A normal case
- In
s = 'aabcbcdbca'- Out
'dbca'
4 kinds. dbca has all of them in 4 letters, and 4 kinds need at least 4 letters, so it cannot get shorter.
Longer words
- In
s = 'GEEKSGEEKSFOR'- Out
'GEEKSFOR'
The kinds are G, E, K, S, F, O and R. The second GEEKS plus FOR is the first shortest piece with all 7.
Repeats at the front
- In
s = 'aaab'- Out
'ab'
Two kinds. The b is at the very end, so the shortest window with both is ab, the last two letters.
All different
- In
s = 'abcd'- Out
'abcd'
Every letter is its own kind, so the whole string is needed.
All the same
- In
s = 'aaaa'- Out
'a'
One kind, so one letter is enough. The first a wins the tie.
Way 1: the simple way
Try every starting spot, and from there grow to the right until all kinds are collected.
- Count the kinds
kin the whole string. - For each start
i, walkjto the right and put letters in a set. - As soon as the set has
kkinds, the pieces[i..j]is the shortest one that starts ati. Compare it with the best and move to the next start.
class Solution {
public String smallestWindow(String s) {
int n = s.length();
Set<Character> all = new HashSet<>();
for (char c : s.toCharArray()) all.add(c);
int kinds = all.size(); // how many different letters
int bestStart = 0, bestLen = n;
for (int i = 0; i < n; i++) {
Set<Character> seen = new HashSet<>();
for (int j = i; j < n; j++) {
seen.add(s.charAt(j));
if (seen.size() == kinds) { // all kinds collected
if (j - i + 1 < bestLen) {
bestLen = j - i + 1;
bestStart = i;
}
break; // longer pieces from i cannot be better
}
}
}
return s.substring(bestStart, bestStart + bestLen);
}
}How long it takes: n starts, and each may walk to the end, O(n^2). Extra room: a set of at most k kinds, O(k).
Way 2: the clever way
Starts that are next to each other share most of their work. A sliding window reuses it.
- Count the kinds
k. Keep a count for every letter inside the window, andhave, the number of kinds whose count is at least one. - Move
rightone step at a time and add its letter. If that letter's count goes from 0 to 1,havegrows. - While
have == k, the window is good. Record it if it is the shortest so far. Then drop the letter atleftand moveleftone step. If its count falls to 0,haveshrinks and the inner loop stops. - When
righthas passed the end, the best window is the answer.
Stretch on the right, pull in on the left
01 / 13
The string has 4 kinds of letters: a, b, c and d. The window is empty.
- kinds in window
- 0 of 4
- looking now
- answer
- rejected
- path
- just added
- ignored
class Solution {
public String smallestWindow(String s) {
int n = s.length();
if (n == 0) return "";
boolean[] exists = new boolean[256];
int kinds = 0;
for (char c : s.toCharArray()) {
if (!exists[c]) { exists[c] = true; kinds++; } // count the kinds once
}
int[] count = new int[256]; // letters inside the window
int have = 0; // kinds with count at least 1
int bestStart = 0, bestLen = Integer.MAX_VALUE;
int left = 0;
for (int right = 0; right < n; right++) {
if (count[s.charAt(right)]++ == 0) have++; // a new kind came in
while (have == kinds) { // the window is good
if (right - left + 1 < bestLen) {
bestLen = right - left + 1;
bestStart = left;
}
if (--count[s.charAt(left)] == 0) have--; // drop from the back
left++;
}
}
return s.substring(bestStart, bestStart + bestLen);
}
}How long it takes: right visits each letter once and left visits each letter at most once, so O(n). Extra room: the count table, O(k) kinds (a fixed 256 here, which is O(1) for plain text).
Watch out for
- Recording the window after shrinking instead of before. Record while it is still good, then drop the left letter.
- Using
ifinstead ofwhilefor the shrinking. One step of shrinking is not enough when many extra letters sit at the back. - Upper and lower case are different letters unless the problem says otherwise.
- A string with one kind, or one letter. The answer is a single letter, and the code above handles it.
What interviewers ask next
- Smallest window that contains all letters of another string
t, with repeats (LeetCode 76). Same window, butneed[c]comes fromt, and a kind is satisfied whencount[c]reachesneed[c]. - Longest window with at most
kkinds (LeetCode 340). Flip the loop: grow on the right, and shrink while there are more thankkinds. - Longest piece with no repeated letter (LeetCode 3). Same idea, shrink while the new letter's count is above one.
- Why is it
O(n)even with a loop inside a loop?leftonly moves forward, so the inner loop runs at mostntimes in total.
Remember this
Grown-up words: the stretchy magnifying glass is a sliding window with two pointers, the sticker counts are a frequency table (hash map), and have is the number of satisfied distinct characters.