Loading W Code...
Master the art of O(n) optimization with pointer tricks.
The Two Pointer Technique is an algorithmic pattern that uses two distinct indices (pointers) to traverse a data structure (typically an array or list) in O(n) time complexity. It is primarily used to minimize nested loops by moving pointers towards each other or in the same direction based on specific conditions, effectively optimizing brute-force solutions.
Expert Note: Senior Engineers at Google and Meta frequently ask Two Pointer questions (like 3Sum and Container With Most Water) to test a candidate's ability to optimize brute-force O(n²) solutions into linear O(n) solutions.
7
Patterns
45
Minutes
O(n)
Complexity
3
Visualizers
Imagine two friends walking towards each other from opposite sides of a narrow bridge. They adjust their walking speeds based on their distance from one another.
The Two-Pointer Technique is an algorithmic pattern that uses two index variables (pointers) to traverse a data structure concurrently. Rather than checking all pairs using nested loops (O(n²) complexity), we move the pointers based on specific rules, reducing execution time to linear O(n) complexity.
// Opposite Direction Traversal
int left = 0;
int right = n - 1;
while (left < right) {
if (condition_met) {
// Success
} else if (value_too_small) {
left++;
} else {
right--;
}
}Imagine a balance scale. If the weight is too low, you add weight to the left pan (moving the left pointer up). If the weight is too high, you remove weight from the right pan (moving the right pointer down).
In a sorted array, moving the left pointer increases values, and moving the right pointer decreases values.
left = 0 and right = n - 1.sum = nums[left] + nums[right].sum == target, return the indices.sum < target, we need a larger value, so we increment left++.sum > target, we need a smaller value, so we decrement right--.#include <vector>
using namespace std;
// Find pair that sums to target in a sorted array
vector<int> twoSumSorted(vector<int>& nums, int target) {
int left = 0;
int right = nums.size() - 1;
while (left < right) {
int sum = nums[left] + nums[right];
if (sum == target) {
return {left, right};
} else if (sum < target) {
left++; // Move left pointer rightward to increase sum
} else {
right--; // Move right pointer leftward to decrease sum
}
}
return {};
}Imagine a quality control inspector on an assembly line. The conveyor belt continuously feeds products (the reader pointer). If a product passes inspection, the inspector places it directly in the shipping box (the writer pointer). If a product is defective, the inspector skips it, leaving the shipping box pointer where it is.
We use two pointers moving in the same direction to filter or modify array elements in-place:
i): Iterates sequentially through every element.j): Points to the next location where a valid element should be written.#include <vector>
using namespace std;
// Remove duplicates from a sorted array in-place
int removeDuplicates(vector<int>& nums) {
if (nums.empty()) return 0;
int j = 1; // Writer pointer
for (int i = 1; i < nums.size(); i++) { // Reader pointer
if (nums[i] != nums[i - 1]) {
nums[j] = nums[i];
j++;
}
}
return j; // Returns the new length of unique elements
}Imagine two runners on a circular athletics track. If runner A runs twice as fast as runner B, runner A will eventually lap runner B and pass them from behind. If they were running on a straight line, they would never meet again.
The Fast & Slow Pointer configuration uses two pointers traversing at different speeds:
struct ListNode {
int val;
ListNode *next;
};
// Check if a linked list contains a cycle
bool hasCycle(ListNode *head) {
ListNode *slow = head;
ListNode *fast = head;
while (fast && fast->next) {
slow = slow->next; // Moves 1 step
fast = fast->next->next; // Moves 2 steps
if (slow == fast) {
return true; // Collision detected (cycle exists)
}
}
return false;
}Imagine looking through a camera viewfinder frame of fixed width K as you pan across a panoramic view.
Instead of recalculating the sum of all elements inside the window at each step (which takes O(n * k) time), we can update the running sum in O(1) time:
K elements.K to n - 1.currentSum += arr[i] - arr[i - K].#include <vector>
#include <algorithm>
using namespace std;
// Find maximum sum of a contiguous subarray of size K
int maxSumSubarray(vector<int>& arr, int K) {
if (arr.size() < K) return -1;
int currentSum = 0;
for (int i = 0; i < K; i++) {
currentSum += arr[i]; // Initialize first window
}
int maxSum = currentSum;
for (int i = K; i < arr.size(); i++) {
currentSum += arr[i] - arr[i - K]; // Slide window in O(1)
maxSum = max(maxSum, currentSum);
}
return maxSum;
}Imagine a caterpillar crawling along a branch. It stretches its head forward to search (expanding the window). Once it finds a leaf or satisfies a condition, it pulls its tail forward to contract its body (shrinking the window).
A variable-sized sliding window dynamically expands and contracts to find the optimal subarray satisfying a condition (e.g., sum is at least target).
arr[right] to the window state.arr[left] from the window state, and increment left++ to look for a smaller valid window.#include <vector>
#include <algorithm>
#include <climits>
using namespace std;
// Find length of smallest subarray with a sum >= target
int minSubArrayLen(int target, vector<int>& nums) {
int left = 0;
int sum = 0;
int minLen = INT_MAX;
for (int right = 0; right < nums.size(); right++) {
sum += nums[right]; // Expand window rightward
while (sum >= target) { // Contract window leftward
minLen = min(minLen, right - left + 1);
sum -= nums[left];
left++;
}
}
return minLen == INT_MAX ? 0 : minLen;
}Choose the appropriate pattern based on these common problem indicators:
0 and n - 1.K, running average of a specific duration.K, then slide by adding arr[i] and removing arr[i - K].// Quick Decisions
// 1. Array Sorted + Find Pair -> Opposite Direction
// 2. Continuous Subarray of Size K -> Fixed Sliding Window
// 3. Find Longest/Shortest Subarray -> Variable Sliding Window
// 4. Linked List Cycles -> Fast & Slow PointersUse Sliding Window when dealing with subarrays (contiguous elements) of size K or finding the longest/shortest substring meeting a condition. Use Two Pointers when the array is sorted (finding pairs) or when comparing elements from both ends (palindromes).
Mostly yes, for finding pairs (like Two Sum). However, for techniques like "Move Zeroes" or "Remove Duplicates" (Writer/Reader pointers) or Linked List cycles (Fast and Slow), sorting is not required.
The time complexity is O(n) (Linear Time). Although there might be nested loops (like the `while` loop for shrinking), each element is added once and removed at most once, resulting in `2n` operations in the worst case, which simplifies to O(n).