Master DSA: Pattern 1 — Two Pointers
The Two Pointers pattern is an algorithmic technique where two index variables traverse a linear data structure—typically an array or string—either toward each other or in the same direction. By exploiting structural properties such as sorting, symmetry, or monotonicity, this pattern drastically reduces redundant work, frequently turning naive brute-force solutions into optimal linear-time algorithms with auxiliary space.
1. Introduction to the Two Pointers Pattern
At its core, the Two Pointers pattern involves coordinating two positions within an iterable sequence to locate pairs, triplets, partitions, or sub-segments that satisfy explicit target conditions.
Rather than running nested iterations over every possible combination, the two pointers advance based on deterministic decision rules. At each step, comparing the elements referenced by the pointers reveals whether to increment, decrement, or stop, thereby pruning large swaths of the problem space.
2. Core Mental Models & Traversal Strategies
Depending on the underlying problem constraints and whether the sequence is sorted, two pointers operate via distinct mechanical setups:
Strategy A: Inward-Converging (Opposite Ends)
- Initialization:
left = 0,right = array.length - 1 - Termination:
while (left < right) - Mental Model: Squeezing a range from both boundaries. If the current combination is too small, advance
leftto introduce larger values; if too large, decrementrightto introduce smaller values. - Typical Use Cases: Two Sum on sorted arrays, Container With Most Water, Valid Palindrome.
Strategy B: Uni-Directional (Slow & Fast Pointers)
- Initialization:
slow = 0,fast = 1(or both at0) - Termination:
fast < array.length - Mental Model: The
fastpointer acts as an explorer scanning through each element, while theslowpointer acts as a writer or anchor maintaining a processed segment. - Typical Use Cases: In-place deduplication, partitioning, element removal.
3. Complexity Optimization: Brute Force vs. Two Pointers
Nested loops naturally incur quadratic work because they exhaustively test all pairs regardless of whether earlier comparisons already ruled out subsets of combinations.
4. Problem 1: Two Sum II (Input Array Is Sorted)
Problem Description
Given a 1-indexed array of integers numbers that is already sorted in non-decreasing order, find two numbers such that they add up to a specific target. Return the indices of the two numbers, incremented by one, as a two-element integer array [index1, index2]. The solution must use only additional space.
Algorithmic Flow
- Initialize
left = 0andright = numbers.length - 1. - Compute
currentSum = numbers[left] + numbers[right]. - If
currentSum == target, return[left + 1, right + 1]. - If
currentSum < target, the sum must be increased. Since the array is sorted, incrementleft++. - If
currentSum > target, the sum must be decreased. Decrementright--. - Terminate when
left >= right.
Java Implementation
public class TwoSumSorted {
public int[] twoSum(int[] numbers, int target) {
int left = 0;
int right = numbers.length - 1;
while (left < right) {
int sum = numbers[left] + numbers[right];
if (sum == target) {
return new int[] { left + 1, right + 1 }; // 1-based indexing
} else if (sum < target) {
left++;
} else {
right--;
}
}
return new int[] {
-1, -1 }; // No solution found
}
}
- Time Complexity: — Each iteration advances
leftor decrementsright, scanning at most elements. - Space Complexity: — No dynamic data structures allocated.
5. Problem 2: Remove Duplicates from Sorted Array
Problem Description
Given an integer array nums sorted in non-decreasing order, remove duplicates in place such that each unique element appears only once. The relative order of the elements must be kept identical. Return the number of unique elements k. The first k elements of nums must hold the final unique values.
Note on In-Place Mutation: Modifying an array "in place" means you must not allocate a secondary buffer or array. All updates occur by overwriting values inside the input array memory directly.
Algorithmic Flow
- Maintain a write-pointer
jinitialized to1(since index0is always the first unique element). - Use an explorer-pointer
iiterating from1throughnums.length - 1. - Compare the current candidate
nums[i]with the previously written unique elementnums[j - 1]. - If
nums[i] != nums[j - 1], a new distinct value is encountered:- Assign
nums[j] = nums[i]. - Increment write-index
j++.
- Assign
- If
nums[i] == nums[j - 1], skipnums[i](it is an unneeded duplicate). - Return
jas the count of unique elements.
Java Implementation
public class RemoveDuplicatesSorted {
public int removeDuplicates(int[] nums) {
if (nums.length == 0) {
return 0;
}
int j = 1; // Tracks the position for the next unique element
for (int i = 1; i < nums.length; i++) {
// Compare explorer with the last confirmed unique value
if (nums[i] != nums[j - 1]) {
nums[j] = nums[i];
j++;
}
}
return j; // The count of unique elements
}
}
- Time Complexity: — Single pass through the array.
- Space Complexity: — Array mutated entirely in-place.
6. Problem 3: Valid Palindrome
Problem Description
A phrase is a palindrome if, after converting all uppercase letters into lowercase letters and removing all non-alphanumeric characters, it reads identically forward and backward. Alphanumeric characters include letters and numbers.
Step-by-Step Evolution
Phase 1: String Normalization
- Convert the entire string to lowercase:
s.toLowerCase(). - Filter out non-alphanumeric characters using regular expression pattern matching:
- Expression:
[^a-zA-Z0-9] - Replacement:
""(empty string)
- Expression:
Phase 2: Symmetrical Verification
- Check characters from boundaries converging to the center index
n / 2. - Compare character at index
iagainst its mirrored counterpart atn - 1 - i. - If any pair mismatches, return
false. If iteration completes without mismatches, returntrue.
Java Implementation
public class ValidPalindrome {
public boolean isPalindrome(String s) {
// Step 1: Normalize case and strip all non-alphanumeric symbols
s = s.toLowerCase();
s = s.replaceAll("[^a-zA-Z0-9]", "");
int n = s.length();
// Step 2: Compare characters from start and end toward center
for (int i = 0; i < n / 2; i++) {
if (s.charAt(i) != s.charAt(n - 1 - i)) {
return false;
}
}
return true;
}
}
Note on Optimization: In production systems, string copying and regex replacement via
replaceAllintroduce extra memory overhead ( space). An optimal variation moves the two pointers over the raw string directly, incrementing or decrementing pointers past non-alphanumeric characters on the fly viaCharacter.isLetterOrDigit(), achieving auxiliary space.
- Time Complexity: — Linear string cleaning and single-pass half-length comparison.
- Space Complexity: — Required to store the cleaned string.
7. Problem 4: Container With Most Water
Problem Description
Given an integer array height of length n, representing n vertical lines drawn at coordinates to , find two lines that together with the x-axis form a container that holds the most water. Return the maximum area of water the container can store.
The Limiting Factor Principle
The area between two lines at indices left and right is determined by two factors:
Why Move the Shorter Pointer?
- The width monotonically decreases at every subsequent step because the pointers move closer together.
- The height of the water is strictly bottlenecked by the shorter line.
- If you move the taller line inwards:
- The width decreases.
- The height can never exceed the shorter line you kept.
- Thus, the area is guaranteed to shrink or stay smaller.
- The only potential for finding a larger container is to discard the shorter line in search of a taller replacement.
Algorithmic Execution Walkthrough
- Initialize
left = 0,right = height.length - 1,maxArea = 0. - While
left < right:- Calculate current width:
width = right - left. - Calculate current height:
h = Math.min(height[left], height[right]). - Update
maxArea = Math.max(maxArea, width * h). - If
height[left] < height[right], advanceleft++. - Else, decrement
right--.
- Calculate current width:
- Return
maxArea.
Java Implementation
public class ContainerWithMostWater {
public int maxArea(int[] height) {
int left = 0;
int right = height.length - 1;
int maxArea = 0;
while (left < right) {
// Determine the bottleneck height and current base width
int currentHeight = Math.min(height[left], height[right]);
int currentWidth = right - left;
int currentArea = currentHeight * currentWidth;
// Track the historical maximum
maxArea = Math.max(maxArea, currentArea);
// Shift pointer pointing to the shorter vertical bar
if (height[left] < height[right]) {
left++;
} else {
right--;
}
}
return maxArea;
}
}
- Time Complexity: — Every iteration discards one line from consideration, traversing the array in a single pass.
- Space Complexity: — Only scalar integer variables maintained.
8. Identifying Two-Pointer Problems: Decision Framework
When evaluating an interview problem, use the following operational criteria to decide whether the Two Pointers pattern applies:
9. Comparative Synthesis of Covered Problems
Summary and Key Takeaways
- Eliminates Combinatorial Overhead: The Two Pointers pattern systematically replaces quadratic nested loop evaluations with a coordinated, linear-time scan.
- Exploits Sorting Monotonicity: On sorted data, moving indices in from the boundaries provides deterministic knowledge of whether a combination will grow larger or smaller.
- In-Place Memory Preservation: Using a slow writer pointer alongside a fast explorer pointer enables arrays to be updated, partitioned, or deduplicated in auxiliary space.
- Greedy Boundary Elimination: In geometric container problems, the shorter wall dictates water capacity; shifting the taller boundary inward only ever reduces width without the potential to increase the constraining height.
- Unified Convergence Guard: Inward-converging two-pointer algorithms reliably guard against out-of-bounds errors and unnecessary work using the loop invariant
while (left < right). - Primary Pattern Trigger: Reach for Two Pointers whenever a question emphasizes searching for pairs/triplets in sorted lists, validating symmetry, computing intervals, or restructuring linear collections in place.