String · 37 of 43
Find the smallest window in a string containing all characters of another string
Find the shortest stretch of a long word that still holds every letter on your shopping list.
- Simple way
- time O(n^2)
room O(1) - Clever way
- time O(n + m)
room O(1)
The problem, simply
You get a long string s and a short string t. Find the shortest piece of s, with its letters next to each other, that holds every letter of t. If t has a letter two times, the piece must have it at least two times too.
Give back that shortest piece. If no piece works, give back an empty string "".
A story to remember it
Picture it
s = ADOBECODEBANC and t = ABC. The bracket shows the best window, BANC. It holds one A, one B and one C, and nothing shorter does.
- answer
Examples
A normal case
- In
s = 'ADOBECODEBANC', t = 'ABC'- Out
'BANC'
ADOBEC also holds A, B and C, but it is 6 letters long. BANC does it in 4.
t has a letter twice
- In
s = 'AAABBC', t = 'AABC'- Out
'AABBC'
The window needs two A's, not one. ABBC has only one A, so the best window is AABBC.
Not enough letters
- In
s = 'a', t = 'aa'- Out
''
There is only one a on the shelf, and the list wants two. No window works, so the answer is empty.
The whole string
- In
s = 'a', t = 'a'- Out
'a'
The only window is the whole string, and it works.
Way 1: the simple way
Try every starting slot. From each start, walk to the right and count what you have. Stop as soon as the list is covered, because walking further from this start only makes the window longer.
- Count the letters of
tin a small table. - For each start
i, make a fresh copy of that table and walkjfromito the right. - Each letter you pass that is still needed lowers the missing count.
- When missing hits 0, the window
i..jis the shortest one starting ati. Keep it if it beats the best.
class Solution {
public String minWindow(String s, String t) {
int[] want = new int[128];
for (char c : t.toCharArray()) want[c]++;
int bestStart = 0, bestLen = Integer.MAX_VALUE;
for (int i = 0; i < s.length(); i++) {
int[] need = want.clone(); // a fresh list for this start
int missing = t.length();
for (int j = i; j < s.length(); j++) {
char c = s.charAt(j);
if (need[c] > 0) missing--; // this letter was on the list
need[c]--;
if (missing == 0) { // shortest window from i
if (j - i + 1 < bestLen) {
bestLen = j - i + 1;
bestStart = i;
}
break;
}
}
}
return bestLen == Integer.MAX_VALUE ? "" : s.substring(bestStart, bestStart + bestLen);
}
}How long it takes: for each of the n starts we may walk to the end, so O(n^2). Extra room: the count table has one slot per possible letter (128), which does not grow with the input, so O(1).
Way 2: the clever way
Way 1 throws away its counts every time it moves the start. The rubber band keeps them. When the left hand lets go of one letter, you fix one count. You never start over.
Keep a table need: for each letter, how many more you still want. Keep one number missing: how many letters of t are not inside the band yet.
- Fill
needfromt. Setmissingto the length oft. - Move
Rright one slot at a time. Ifneedfor that letter is above 0, it is useful, so lowermissing. Always lowerneedfor it. - While
missingis 0, the band holds everything. Measure it. Then moveLright: raiseneedfor the letter you let go, and if it climbs above 0, that letter is now missing again. - When
Rreaches the end, the best band you measured is the answer.
A count below 0 means you hold spares. Letters that are not in t also go below 0, and that is fine, because they never make missing change.
The rubber band on ADOBECODEBANC with list ABC
01 / 19
R grabs A. A is on the list, so missing drops from 3 to 2.
- missing
- 2
- best
- none
- looking now
- answer
- rejected
- path
- just added
class Solution {
public String minWindow(String s, String t) {
int[] need = new int[128];
for (char c : t.toCharArray()) need[c]++;
int missing = t.length(); // letters of t not in the band yet
int left = 0, bestStart = 0, bestLen = Integer.MAX_VALUE;
for (int right = 0; right < s.length(); right++) {
char c = s.charAt(right);
if (need[c] > 0) missing--; // a useful letter came in
need[c]--;
while (missing == 0) { // the band holds everything
if (right - left + 1 < bestLen) {
bestLen = right - left + 1;
bestStart = left;
}
char d = s.charAt(left);
need[d]++; // let go of the leftmost letter
if (need[d] > 0) missing++; // it was not a spare
left++;
}
}
return bestLen == Integer.MAX_VALUE ? "" : s.substring(bestStart, bestStart + bestLen);
}
}How long it takes: R moves n times and L moves at most n times, plus m steps to read t, so O(n + m). Extra room: one table of 128 counts, O(1).
Watch out for
- Measure the window before you let go of the left letter, not after.
tlonger thansmeans no answer. The code returns""becausemissingnever reaches 0.- Upper and lower case are different letters.
Aandalive in different slots of the table. - Return the best start and length, not the last window you saw.
What interviewers ask next
- Return only the length? Keep
bestLenand skip thesubstring. - What if the alphabet is huge, like any Unicode letter? Use a
HashMapinstead of the 128 table. The idea does not change. - Smallest window that holds every different letter of
sitself? Lettbe the set of distinct letters ofsand run the same band. - Find all start points of anagrams of
tins? That band has a fixed size oft.length(), so both hands move together. - Why is
Lmoving inside a loop still linear? BecauseLonly moves right and never comes back. Over the whole run it moves at mostntimes.
Remember this
Grown-up words: the rubber band is a sliding window, the two hands are two pointers, the list is a frequency count (a hash map or an array of counts), and missing is the number of unsatisfied characters.