Sliding Window Protocol on Strings
A comprehensive architectural deep-dive into two fundamental string manipulation problems solved using the Sliding Window Protocol and Two-Pointer Framework. This chapter explores how to optimize brute-force and search spaces down to optimal linear time complexity using fixed ASCII frequency maps and dynamic window boundaries.
1. Introduction: Substrings and the Uniqueness Constraint
In string processing, a substring is defined as a contiguous sequence of characters within a string. Unlike subsequences, substrings cannot skip elements or reorder characters.
Core Definitions & Constraints
- Original String (): A sequence of characters of length .
- Subsequence: Elements derived from maintaining relative order, but not necessarily contiguous.
- Substring: Continuous contiguous memory block where .
- Uniqueness Constraint: Every character inside the evaluated window must have a frequency of exactly .
String S = "a b a b c"
Valid Substrings: "a", "ab", "abc", "b"
Invalid Substrings: "abb" (not contiguous in original string order)
2. Problem 1: Longest Substring Without Repeating Characters
Problem Statement
Given a string s, find the length of the longest substring that contains no duplicate characters.
- Input:
s = "ababc" - Output:
3 - Explanation: The longest contiguous substring with all unique characters is
"abc", which has a length of ."aba"is invalid because'a'is repeated.
3. Naive & Sub-Optimal Approaches ( to )
Approach 1: Pure Brute-Force — Generate All Substrings ()
The naive approach generates every possible substring , then iterates through each generated substring to verify whether all characters are unique.
- Generate all substrings using nested loops for start index and end index .
- For each substring, pass it to a helper function that checks for duplicates using a set or frequency array.
- Track the maximum valid length found.
Total Substrings = N * (N + 1) / 2
Validation Time per Substring = O(N)
Total Time Complexity = O(N^3)
Space Complexity = O(N) or O(256)
Approach 2: Fixing the Left Endpoint ()
Instead of extracting the substring and checking uniqueness from scratch, we can expand the right endpoint incrementally for a fixed starting index . As soon as collides with a character already present in the current iteration's frequency set, we stop expanding for that index and move to .
// O(N^2) Solution Concept
int maxLen = 0;
int n = s.length();
for (int i = 0; i < n; i++) {
vector<bool> visited(256, false);
for (int j = i; j < n; j++) {
if (visited[s[j]]) {
// Duplicate detected; further expansion starting at 'i' will still contain this duplicate
break;
}
visited[s[j]] = true;
maxLen = max(maxLen, j - i + 1);
}
}
- Time Complexity: — In the worst-case (e.g.,
"abcdefg"), both loops run nearly times. - Space Complexity: auxiliary space (-element boolean array).
4. Optimal Mental Model: The Sliding Window Technique ()
Instead of resetting the search boundary back to whenever a duplicate is found, we maintain a dynamically resizing dynamic window defined by two pointers:
first(Left boundary of the window)second(Right boundary of the window)
Real-Life Analogy
Mental Hook: Imagine a flexible accordion frame framing a row of houses. As long as you keep passing unique house colors, you extend the right end of the frame (
second). The moment you spot a duplicate color on the right, you do not collapse the frame completely back to the start. You simply slide the left side of the frame (first) inward to push the previous duplicate house out of view.
State Transition Logic
- Expand: Increment
second. Mark as visited in a frequency map/array. - Conflict: If was already visited (
count[s[second]] == true), shrink the window from the left by advancingfirstand unmarking until the duplicate instance of is ejected from the window. - Record: At every valid state, calculate the current window length as
second - first + 1and updatemaxLen.
5. Visual Pointer Flow Walkthrough (Problem 1)
Tracing s = "ababc" with initial state first = 0, second = 0, maxLen = 0.
6. C++ Implementation & Dynamic Memory Tracking (Problem 1)
To handle all standard Extended ASCII characters (alphabets, numbers, symbols), a boolean array or frequency vector of size 256 is instantiated.
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;
class Solution {
public:
int lengthOfLongestSubstring(string s) {
// Frequency array covering all 256 Extended ASCII characters
vector<bool> count(256, false);
int first = 0;
int second = 0;
int len = 0;
while (second < s.size()) {
// If character at 'second' is already in current window,
// shrink window from left until duplicate is removed
while (count[s[second]]) {
count[s[first]] = false;
first++;
}
// Mark current character as included in the window
count[s[second]] = true;
// Calculate current valid window size
len = max(len, second - first + 1);
// Expand right boundary
second++;
}
return len;
}
};
Note on ASCII Implicit Conversion:
s[second]is achar. Passings[second]insidecount[s[second]]implicitly converts thecharto its underlying integer ASCII value ( to ), mapped directly to array indices.
7. Problem 2: Smallest Distinct Substring / Window
Problem Statement
Given a string s, find the minimum length of a substring that contains all the distinct characters present in the original string at least once.
- Input:
s = "AABBBCBBAC" - Distinct Characters in :
{'A', 'B', 'C'}(Total count = 3) - Output:
3 - Explanation: Substring
"BAC"(indices 7 to 9) contains'B','A', and'C'. Length is .
8. Dual-Pointer Expansion & Contraction Mechanics (Problem 2)
Unlike Problem 1 (where we expanded until a duplicate occurred), Problem 2 requires a two-phase expansion-contraction loop driven by a distinct character deficit counter (diff).
Algorithmic Strategy
- Pre-computation: Count total distinct characters in the entire string using a frequency array. Store this number in
diff. - Phase 1: Expansion (
diff > 0): Advancesecondto absorb characters into the window. Decrementdiffonly when a character's frequency in the window transitions from . - Phase 2: Contraction (
diff == 0): Once all distinct characters are present within the current window (diff == 0):- Record/update
minLen = min(minLen, current_window_len). - Shrink from the left by advancing
firstto find smaller valid windows. - If removing causes its frequency in the window to drop to , increment
diffback to (signifying that a required distinct character is now missing), forcing the algorithm back to Phase 1.
- Record/update
9. C++ Implementation: Smallest Distinct Window
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;
class Solution {
public:
int findSubString(string str) {
int n = str.size();
vector<int> count(256, 0);
int diff = 0;
// Step 1: Calculate total unique characters in the input string
for (int i = 0; i < n; i++) {
if (count[str[i]] == 0) {
diff++;
count[str[i]] = 1;
}
}
// Reset frequency counts for sliding window tracking
fill(count.begin(), count.end(), 0);
int first = 0;
int second = 0;
int len = n; // Max possible length initially
// Step 2: Sliding Window execution
while (second < n) {
// Expansion Phase: Expand window until all distinct characters are present
while (diff > 0 && second < n) {
if (count[str[second]] == 0) {
diff--; // Found a missing required character
}
count[str[second]]++;
second++;
}
// Contraction Phase: Shrink window from left to optimize length
while (diff == 0) {
// Note: 'second' was incremented at the end of the expansion loop,
// so current window size is (second - first)
len = min(len, second - first);
count[str[first]]--;
if (count[str[first]] == 0) {
diff++; // Window lost a required unique character
}
first++;
}
}
return len;
}
};
Critical Implementation Detail: Indexing Off-By-One
Pedagogical Callout: Notice why window length inside the contraction loop is computed as
second - firstinstead ofsecond - first + 1: In the expansion loop,second++runs after absorbingstr[second]. When the loop terminates,secondis pointing to the index one position past the rightmost element of the window. Therefore, true length .
10. Complexity Analysis & Comparative Overview
Comparative Analysis Table
Why the Time Complexity is strictly
Although nested while loops exist in both implementations, each pointer (first and second) moves strictly left-to-right across the string array length :
secondincrements from to exactly once.firstincrements from to at most once across the entire runtime execution.- Total pointer movements .
Thus, the overall amortized time complexity is linear:
Summary and Key Takeaways
- Substrings vs Subsequences: A substring is strictly contiguous memory. Substring problems naturally map to two-pointer dynamic window algorithms.
- Brute Force Bottleneck: Generating all substrings takes time. Validating each substring from scratch increases complexity to .
- Sliding Window Core Idea: Instead of resetting both search boundaries when a constraint fails, adjust boundaries dynamically based on character counts.
- Frequency Map Optimization: Utilizing a fixed array of size covers all Extended ASCII codes in constant space , avoiding hash map overhead.
- Pointer Movement Mechanics:
- Problem 1 (Longest Unique): Expand
seconduntil a duplicate occurs; shrinkfirstuntil the duplicate is evicted. - Problem 2 (Smallest Covered): Expand
seconduntil all target characters are collected (diff == 0); shrinkfirstto find the minimal valid span.
- Problem 1 (Longest Unique): Expand
- Amortized Linear Time: Nested loops do not imply complexity if the inner loop pointers only increment monotonically without resetting. Each pointer traverses the array at most once, yielding execution time.