Minimum Window Substring in DSA Rust - Time & Space Complexity
We want to understand how the time needed to find the smallest substring containing all characters grows as the input string gets longer.
How does the algorithm's work increase when the input size grows?
Analyze the time complexity of the following code snippet.
fn min_window(s: &str, t: &str) -> String {
use std::collections::HashMap;
let mut need = HashMap::new();
for c in t.chars() { *need.entry(c).or_insert(0) += 1; }
let (mut left, mut right, mut count) = (0, 0, t.len());
let s_chars: Vec = s.chars().collect();
let mut min_len = usize::MAX;
let mut start = 0;
while right < s_chars.len() {
if let Some(n) = need.get_mut(&s_chars[right]) {
if *n > 0 { count -= 1; }
*n -= 1;
}
right += 1;
while count == 0 {
if right - left < min_len {
min_len = right - left;
start = left;
}
if let Some(n) = need.get_mut(&s_chars[left]) {
*n += 1;
if *n > 0 { count += 1; }
}
left += 1;
}
}
if min_len == usize::MAX { "".to_string() } else { s_chars[start..start+min_len].iter().collect() }
}
This code finds the smallest substring in s that contains all characters of t using a sliding window approach.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: The
whileloop that moves therightpointer over the strings, and the innerwhileloop that moves theleftpointer to shrink the window. - How many times: Each character in
sis visited at most twice: once whenrightmoves forward, once whenleftmoves forward.
As the input string s grows, the algorithm moves two pointers across it. Each pointer moves forward without going back, so the total steps grow roughly in proportion to the length of s.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 | About 20 steps (each pointer moves across 10 characters) |
| 100 | About 200 steps |
| 1000 | About 2000 steps |
Pattern observation: The total work grows roughly linearly with input size, doubling input roughly doubles the work.
Time Complexity: O(n + m)
This means the time grows linearly with the size of the input string s (length n) plus the size of the target string t (length m).
[X] Wrong: "The inner loop makes this algorithm quadratic because it is nested inside the outer loop."
[OK] Correct: Each character is visited at most twice, once by each pointer, so the total steps add up linearly, not multiply.
Understanding this sliding window approach and its linear time complexity shows you can handle efficient substring search problems, a common skill in coding challenges and interviews.
"What if the target string t contains all unique characters? How would the time complexity change?"
Practice
Minimum Window Substring problem?Solution
Step 1: Understand the problem statement
The problem asks to find the smallest part of a string that includes all characters from another string.Step 2: Identify the goal
We want the minimum length substring that contains all required characters, not sorting or counting.Final Answer:
Find the smallest substring in a string that contains all characters of another string -> Option DQuick Check:
Minimum Window Substring = smallest substring containing all chars [OK]
- Confusing with sorting or reversing strings
- Thinking it counts characters instead of finding substring
- Ignoring the requirement to include all characters
Solution
Step 1: Identify the need for character frequency tracking
We need to count how many times each character appears in the target string and current window.Step 2: Choose a data structure that maps characters to counts
A HashMap allows storing counts for each character efficiently.Final Answer:
HashMap -> Option BQuick Check:
Use HashMap for char counts [OK]
- Using Vec<char> which doesn't track counts
- Choosing LinkedList which is inefficient here
- Using BinaryHeap which is for priority, not counts
let s = "ADOBECODEBANC";
let t = "ABC";
let result = min_window(s, t);
println!("{}", result);
fn min_window(s: &str, t: &str) -> String {
use std::collections::HashMap;
let mut need = HashMap::new();
for c in t.chars() {
*need.entry(c).or_insert(0) += 1;
}
let (mut left, mut right, mut count) = (0, 0, 0);
let mut min_len = usize::MAX;
let mut start = 0;
let mut window = HashMap::new();
let s_chars: Vec<char> = s.chars().collect();
while right < s_chars.len() {
let c = s_chars[right];
if need.contains_key(&c) {
*window.entry(c).or_insert(0) += 1;
if window[&c] == need[&c] {
count += 1;
}
}
while count == need.len() {
if right - left + 1 < min_len {
min_len = right - left + 1;
start = left;
}
let left_char = s_chars[left];
if need.contains_key(&left_char) {
*window.entry(left_char).or_insert(0) -= 1;
if window[&left_char] < need[&left_char] {
count -= 1;
}
}
left += 1;
}
right += 1;
}
if min_len == usize::MAX {
"".to_string()
} else {
s_chars[start..start + min_len].iter().collect()
}
}Solution
Step 1: Understand the input strings
We want the smallest substring of "ADOBECODEBANC" containing all chars of "ABC".Step 2: Trace the sliding window algorithm
The code finds "BANC" as the minimum window containing 'A', 'B', and 'C'.Final Answer:
"BANC" -> Option AQuick Check:
Minimum window substring = "BANC" [OK]
- Choosing first found substring instead of minimum
- Ignoring character counts in window
- Confusing substring boundaries
fn min_window(s: &str, t: &str) -> String {
use std::collections::HashMap;
let mut need = HashMap::new();
for c in t.chars() {
*need.entry(c).or_insert(0) += 1;
}
let (mut left, mut right, mut count) = (0, 0, 0);
let mut min_len = usize::MAX;
let mut start = 0;
let mut window = HashMap::new();
let s_chars: Vec<char> = s.chars().collect();
while right <= s_chars.len() {
let c = s_chars[right];
if need.contains_key(&c) {
*window.entry(c).or_insert(0) += 1;
if window[&c] == need[&c] {
count += 1;
}
}
while count == need.len() {
if right - left + 1 < min_len {
min_len = right - left + 1;
start = left;
}
let left_char = s_chars[left];
if need.contains_key(&left_char) {
*window.entry(left_char).or_insert(0) -= 1;
if window[&left_char] < need[&left_char] {
count -= 1;
}
}
left += 1;
}
right += 1;
}
if min_len == usize::MAX {
"".to_string()
} else {
s_chars[start..start + min_len].iter().collect()
}
}Solution
Step 1: Check the while loop boundary
The loop usesright <= s_chars.len(), which causes out-of-bounds access ats_chars[right].Step 2: Correct the loop condition
Changing it toright < s_chars.len()prevents accessing beyond the vector length.Final Answer:
The loop condition should beright < s_chars.len()instead of <= to avoid out-of-bounds -> Option CQuick Check:
Loop boundary must prevent out-of-bounds [OK]
- Using <= instead of < in loop condition
- Ignoring vector length causing panic
- Not incrementing count correctly
s = "aabbccdd" and t = "abc", what is the minimum window substring that contains all characters of t? Consider the Rust sliding window approach.Solution
Step 1: Identify required characters and counts
String t requires 'a', 'b', and 'c' once each.Step 2: Find smallest substring in s containing all
"aabbc" (positions 0 to 4) contains 'a', 'a', 'b', 'b', 'c' and is the smallest window covering all required chars.Final Answer:
"aabbc" -> Option AQuick Check:
Minimum window substring = "aabbc" [OK]
- Choosing substring missing one required character
- Picking longer substring than necessary
- Ignoring character counts in window
Ready to prove it?
Pass the test and this pattern moves unknown
