Sliding Window & Two Pointers
Efficient techniques for array and string problems.
1. The Sliding Window Technique
The Sliding Window technique is used for problems that involve finding a subarray or substring that satisfies a certain condition. Instead of re-calculating from scratch for every possible subarray, we cleverly slide a "window" over the data, adding an element at one end and removing an element at the other.
Example 1: Maximum Sum Subarray of Fixed Size k
Problem: Given an array of integers and a number `k`, find the maximum sum of a subarray of size `k`.
Approach & Algorithm
- Initialize: Set `maxSum = 0`, `windowSum = 0`, and `windowStart = 0`.
- Iterate: Loop through the array with a `windowEnd` pointer from 0 to the end.
- Expand Window: Add the element at `windowEnd` to `windowSum`.
- Check Window Size: Once the window size is `k` (i.e., `windowEnd >= k - 1`):
- Compare `windowSum` with `maxSum` and update `maxSum` if `windowSum` is larger.
- Slide: Subtract the element at `windowStart` from `windowSum` and increment `windowStart`.
- Return: After the loop, `maxSum` holds the result.
public class MaxSumSubarray {
public static int findMaxSumSubarray(int[] arr, int k) {
if (arr.length < k) { return -1; }
int maxSum = 0, windowSum = 0, windowStart = 0;
for (int windowEnd = 0; windowEnd < arr.length; windowEnd++) {
windowSum += arr[windowEnd];
if (windowEnd >= k - 1) {
maxSum = Math.max(maxSum, windowSum);
windowSum -= arr[windowStart];
windowStart++;
}
}
return maxSum;
}
}
Dry Run: `arr = [2, 1, 5, 1, 3, 2]`, `k = 3`
| windowEnd | Window | windowSum | maxSum | Action |
|---|---|---|---|---|
| 0 | [2] | 2 | 0 | Add `arr[0]` (2). |
| 1 | [2, 1] | 3 | 0 | Add `arr[1]` (1). |
| 2 | [2, 1, 5] | 8 | 8 | Window full. `maxSum = max(0, 8)`. Slide: `sum -= arr[0]` (2). |
| 3 | [1, 5, 1] | 7 | 8 | Window slides. `maxSum = max(8, 7)`. Slide: `sum -= arr[1]` (1). |
| 4 | [5, 1, 3] | 9 | 9 | Window slides. `maxSum = max(8, 9)`. Slide: `sum -= arr[2]` (5). |
| 5 | [1, 3, 2] | 6 | 9 | Window slides. `maxSum = max(9, 6)`. Slide: `sum -= arr[3]` (1). |
Example 2: Minimum Swaps to Group Elements ≤ k
Problem: You're given an array `arr[]` and a number `k`. Bring all elements less than or equal to `k` together with the minimum number of swaps.
Approach & Algorithm
- Find Window Size: Count all elements `≤ k` in the array. This count is our fixed `windowSize`.
- Analyze First Window: Count the "bad" elements (`> k`) in the initial window of size `windowSize`. This is your initial `minSwaps`.
- Slide Window: Iterate from `windowSize` to the end of the array.
- Update Bad Count: In each step, if the element leaving the window was bad, decrement the count. If the element entering is bad, increment the count.
- Track Minimum: After each slide, update `minSwaps = Math.min(minSwaps, currentBadCount)`.
- Return: `minSwaps` is the final answer.
public class MinSwaps {
public static int minSwaps(int[] arr, int k) {
int windowSize = 0;
for (int val : arr) { if (val <= k) windowSize++; }
int badCount = 0;
for (int i = 0; i < windowSize; i++) { if (arr[i] > k) badCount++; }
int minSwaps = badCount;
for (int i = windowSize; i < arr.length; i++) {
if (arr[i - windowSize] > k) badCount--;
if (arr[i] > k) badCount++;
minSwaps = Math.min(minSwaps, badCount);
}
return minSwaps;
}
}
Dry Run: `arr = [10, 12, 1, 2, 14, 3, 16]`, `k = 5`
Elements `≤ 5` are `{1, 2, 3}`, so `windowSize = 3`.
| Window End | Current Window | `badCount` | `minSwaps` | Action |
|---|---|---|---|---|
| 2 | `[10, 12, 1]` | 2 | 2 | Initial window has two "bad" elements (>5). |
| 3 | `[12, 1, 2]` | 1 | 1 | Leaving `10` (bad) decs count. Entering `2` (good) no change. `min = min(2,1)=1` |
| 4 | `[1, 2, 14]` | 1 | 1 | Leaving `12` (bad) decs count. Entering `14` (bad) incs count. No change overall. |
| 5 | `[2, 14, 3]` | 1 | 1 | Leaving `1` (good). Entering `3` (good). No change. |
| 6 | `[14, 3, 16]` | 2 | 1 | Leaving `2` (good). Entering `16` (bad) incs count. `min = min(1,2)=1`. |
Example 3 (Variable Size Window): Longest Subarray with Sum `k`
Problem: Given an array of positive integers and a target sum `k`, find the length of the longest subarray whose elements sum up to `k`.
Approach & Algorithm
- Initialize: Set `windowStart = 0`, `windowSum = 0`, `maxLength = 0`.
- Expand Window: Loop with `windowEnd`. Add `arr[windowEnd]` to `windowSum`.
- Shrink Window: While `windowSum > k`, subtract `arr[windowStart]` from `windowSum` and increment `windowStart`.
- Check for Solution: If `windowSum == k`, update `maxLength` with the current window size (`windowEnd - windowStart + 1`).
- Return: After the loop, `maxLength` is the answer.
public class LongestSubarrayWithSumK {
public static int findLength(int[] arr, int k) {
int windowStart = 0, windowSum = 0, maxLength = 0;
for (int windowEnd = 0; windowEnd < arr.length; windowEnd++) {
windowSum += arr[windowEnd];
while (windowSum > k) {
windowSum -= arr[windowStart];
windowStart++;
}
if (windowSum == k) {
maxLength = Math.max(maxLength, windowEnd - windowStart + 1);
}
}
return maxLength;
}
}
Dry Run: `arr = [4, 1, 1, 1, 2, 3, 5]`, `k = 5`
| windowEnd | Window | windowSum | maxLength | Action |
|---|---|---|---|---|
| 0 | [4] | 4 | 0 | Expand. |
| 1 | [4, 1] | 5 | 2 | Expand. Sum is 5. `maxLength = max(0, 2) = 2`. |
| 2 | [1, 1, 1] | 6 -> 2 | 2 | Expand (sum=6). Shrink: `sum -= arr[0]` (4), `sum=2`. `start=1`. |
| 3 | [1, 1, 1] | 3 | 2 | Expand. |
| 4 | [1, 1, 1, 2] | 5 | 4 | Expand. Sum is 5. `maxLength = max(2, 4) = 4`. |
| 5 | [2, 3] | 8 -> 3 | 4 | Expand (sum=8). Shrink twice (sum=3). `start=3`. |
| 6 | [3, 5] | 8 -> 5 | 4 | Expand (sum=8). Shrink: `sum -= arr[4]`(2),`sum=6`. Shrink: `sum -= arr[5]`(3), `sum=3`. `start=5`. No, wait... let's re-trace 5 & 6. At `end=4`, `sum=5, start=1, maxL=4`. `end=5`: `sum=5+arr[5]=8`. `sum>k`. `sum-=arr[1]`=7. `start=2`. `sum>k`. `sum-=arr[2]`=6.`start=3`. `sum>k`. `sum-=arr[3]`=5.`start=4`. `sum==k`, `len=2`. `maxL=4`. `end=6`: `sum=5+arr[6]=10`. Shrink... final sum=5, `start=6`. `len=1`. `maxL=4`. |
2. The Two Pointer Technique
The Two Pointer technique is related to the Sliding Window. It involves using two pointers to iterate through a data structure. The pointers can move in different ways, such as from opposite ends towards each other or in the same direction at different speeds.
Example 1: Find a Pair with Target Sum in a Sorted Array
Problem: Given a sorted array of integers and a target sum `x`, find if there exists a pair of elements in the array that sum up to `x`.
Approach & Algorithm
- Initialize Pointers: Set a `left` pointer to the start (index 0) and a `right` pointer to the end of the array.
- Loop: Continue as long as `left < right`.
- Calculate Sum: Find the `currentSum = arr[left] + arr[right]`.
- Compare:
- If `currentSum == target`, a pair is found. Return `true`.
- If `currentSum < target`, we need a larger sum, so increment `left`.
- If `currentSum > target`, we need a smaller sum, so decrement `right`.
- End Condition: If the loop finishes without finding a pair, return `false`.
public class PairWithTargetSum {
public static boolean hasPair(int[] arr, int target) {
int left = 0, right = arr.length - 1;
while (left < right) {
int currentSum = arr[left] + arr[right];
if (currentSum == target) return true;
else if (currentSum < target) left++;
else right--;
}
return false;
}
}
Dry Run: `arr = [1, 2, 4, 6, 8, 9]`, `target = 10`
| `left` (value) | `right` (value) | `currentSum` | Action |
|---|---|---|---|
| 0 (1) | 5 (9) | 10 | `sum == target`. Found pair (1, 9). Return `true`. |
Dry Run: `arr = [1, 2, 4, 6, 8, 9]`, `target = 13`
| `left` (value) | `right` (value) | `currentSum` | Action |
|---|---|---|---|
| 0 (1) | 5 (9) | 10 | `sum < target`. Increment `left`. |
| 1 (2) | 5 (9) | 11 | `sum < target`. Increment `left`. |
| 2 (4) | 5 (9) | 13 | `sum == target`. Found pair (4, 9). Return `true`. |
Example 2: Remove Duplicates from a Sorted Array
Problem: Given a sorted array, remove the duplicates in-place such that each unique element appears only once. Return the new length of the array.
Approach & Algorithm
- Initialize Pointers: Use a "slow" pointer, `nextNonDuplicate`, starting at index 1. This pointer marks the end of the unique subarray.
- Iterate: Use a "fast" pointer `i` to loop through the array from index 1.
- Check for Uniqueness: Compare the element at the fast pointer `arr[i]` with the last known unique element `arr[nextNonDuplicate - 1]`.
- Place Unique Element: If they are different, it means `arr[i]` is a new unique element. Place it at the `nextNonDuplicate` index and increment `nextNonDuplicate`.
- Return Length: After the loop, `nextNonDuplicate` will be the length of the array with unique elements.
public class RemoveDuplicates {
public static int remove(int[] arr) {
if (arr.length == 0) return 0;
int nextNonDuplicate = 1; // Slow pointer
for (int i = 1; i < arr.length; i++) { // Fast pointer
if (arr[nextNonDuplicate - 1] != arr[i]) {
arr[nextNonDuplicate] = arr[i];
nextNonDuplicate++;
}
}
return nextNonDuplicate;
}
}
Dry Run: `arr = [2, 3, 3, 3, 6, 9, 9]`
| `i` (Fast) | `nextNonDuplicate` (Slow) | Condition `arr[n-1] != arr[i]` | `arr` state | Action |
|---|---|---|---|---|
| 1 | 1 | `arr[0](2) != arr[1](3)` -> true | `[2, 3, 3, 3, 6, 9, 9]` | `arr[1]=3`, `nND++` -> 2 |
| 2 | 2 | `arr[1](3) != arr[2](3)` -> false | `[2, 3, 3, 3, 6, 9, 9]` | Do nothing. |
| 3 | 2 | `arr[1](3) != arr[3](3)` -> false | `[2, 3, 3, 3, 6, 9, 9]` | Do nothing. |
| 4 | 2 | `arr[1](3) != arr[4](6)` -> true | `[2, 3, 6, 3, 6, 9, 9]` | `arr[2]=6`, `nND++` -> 3 |
| 5 | 3 | `arr[2](6) != arr[5](9)` -> true | `[2, 3, 6, 9, 6, 9, 9]` | `arr[3]=9`, `nND++` -> 4 |
| 6 | 4 | `arr[3](9) != arr[6](9)` -> false | `[2, 3, 6, 9, 6, 9, 9]` | Do nothing. |
Loop ends. Return `nextNonDuplicate` which is 4. The first 4 elements are `[2, 3, 6, 9]`.