Two Pointers Pattern

The Two Pointers technique involves using two pointers to iterate through an array or list simultaneously. This pattern is particularly effective for problems involving sorted arrays, palindromes, or finding pairs that meet specific criteria.

When to Use This Pattern

  • When dealing with sorted arrays or lists
  • When you need to find pairs of elements that satisfy a condition
  • When working with palindromes or symmetric structures
  • When you need to merge two sorted arrays
  • When removing duplicates from sorted arrays

Key Concepts

There are two main variations of the two pointers technique:

Visual Representation

Two Pointers Technique - Opposite Ends

Sorted Array: [1, 2, 3, 4, 6] | Target: 6 1 [0] 2 [1] 3 [2] 4 [3] 6 [4] left right Algorithm Steps Step 1: sum = 1 + 6 = 7 > 6 → move right left Step 2: sum = 1 + 4 = 5 < 6 → move left right Step 3: sum = 2 + 4 = 6 = target ✓

1. Opposite Ends (Converging Pointers)

Start with one pointer at the beginning and one at the end, then move them toward each other:

left = 0
right = n - 1

while left < right:
    # Process elements at left and right
    if condition:
        left += 1
    else:
        right -= 1

2. Same Direction (Fast and Slow)

Both pointers start at the beginning, but move at different speeds:

slow = 0
fast = 0

while fast < n:
    # Process elements
    if condition:
        # Move slow pointer
        slow += 1
    fast += 1

Time Complexity

Two pointers typically reduces time complexity from \(O(n^2)\) to \(O(n)\) by eliminating the need for nested loops.

Visual Simulation: Two Sum in Sorted Array

Let's trace through finding two numbers that sum to target = 9 in array [2, 7, 11, 15]:

Initial State:

Array: [2, 7, 11, 15]

left = 0, right = 3, target = 9

Iteration 1:

nums[left] + nums[right] = 2 + 15 = 17

17 > 9, so move right pointer left (need smaller sum)

left = 0, right = 2

Iteration 2:

nums[left] + nums[right] = 2 + 11 = 13

13 > 9, so move right pointer left

left = 0, right = 1

Iteration 3:

nums[left] + nums[right] = 2 + 7 = 9

9 == 9, found! Return [0, 1]

Algorithm Section

Basic Two Pointers Algorithm

function twoPointers(arr, target):
    left = 0
    right = length(arr) - 1
    
    while left < right:
        current_sum = arr[left] + arr[right]
        
        if current_sum == target:
            return [left, right]
        else if current_sum < target:
            left += 1  # Need larger sum
        else:
            right -= 1  # Need smaller sum
    
    return []  # No solution found

Implementation in C++

#include <vector>
#include <iostream>
using namespace std;

vector<int> twoSumSorted(vector<int>& nums, int target) {
    int left = 0;
    int right = nums.size() - 1;
    
    while (left < right) {
        int current_sum = nums[left] + nums[right];
        
        if (current_sum == target) {
            // Return 1-indexed positions
            return {left + 1, right + 1};
        } else if (current_sum < target) {
            // Sum is too small, move left pointer right
            // (to get a larger number)
            left++;
        } else {
            // Sum is too large, move right pointer left
            // (to get a smaller number)
            right--;
        }
    }
    
    return {};  // No solution found
}

// Example usage
int main() {
    vector<int> nums = {2, 7, 11, 15};
    int target = 9;
    vector<int> result = twoSumSorted(nums, target);
    
    if (!result.empty()) {
        cout << "Indices: [" << result[0] << ", " << result[1] << "]" << endl;
        // Output: Indices: [1, 2]
    }
    
    return 0;
}

Detailed Explanation

  • Why it works: Since the array is sorted, if the sum is too small, we need a larger number, so we move the left pointer right. If the sum is too large, we need a smaller number, so we move the right pointer left.
  • Correctness: We never skip valid pairs because if nums[left] + nums[right] < target, then nums[left] + any number to the left of right is also < target. Similarly for the other case.
  • Time Complexity: O(n) - each element is visited at most once
  • Space Complexity: O(1) - only using two pointers

Common Patterns

  • Finding pairs: Use converging pointers on sorted array
  • Removing duplicates: Use slow and fast pointers
  • Palindrome checking: Use converging pointers from both ends
  • Merging arrays: Use two pointers, one for each array

Practice Problems

Problem 1: Two Sum II - Input Array is Sorted (LeetCode #167)

Given a 1-indexed array of integers that is already sorted in non-decreasing order, find two numbers such that they add up to a specific target number.

Example:

Input: numbers = [2,7,11,15], target = 9
Output: [1,2]
Explanation: The sum of 2 and 7 is 9. Therefore index1 = 1, index2 = 2.

Solution in C++:

#include <vector>
using namespace std;

vector<int> twoSum(vector<int>& numbers, int target) {
    int left = 0;
    int right = numbers.size() - 1;
    
    while (left < right) {
        int current_sum = numbers[left] + numbers[right];
        
        if (current_sum == target) {
            return {left + 1, right + 1};  // 1-indexed
        } else if (current_sum < target) {
            left++;  // Need larger sum
        } else {
            right--;  // Need smaller sum
        }
    }
    
    return {};  // No solution
}

Step-by-Step Simulation:

Input: numbers = [2, 7, 11, 15], target = 9

Initial: left = 0, right = 3

Iteration 1:

numbers[0] + numbers[3] = 2 + 15 = 17

17 > 9, move right left: right = 2

Iteration 2:

numbers[0] + numbers[2] = 2 + 11 = 13

13 > 9, move right left: right = 1

Iteration 3:

numbers[0] + numbers[1] = 2 + 7 = 9

9 == 9, found! Return [1, 2]

Explanation: Since the array is sorted, we can use two pointers starting from both ends. If the sum is too small, we move the left pointer right (increase sum). If too large, we move the right pointer left (decrease sum). This works because the array is sorted, guaranteeing we don't miss any valid pairs. The key insight is that if nums[left] + nums[right] < target, then nums[left] + any element to the left of right is also < target, so we can safely move left forward.

Time Complexity: O(n)

Space Complexity: O(1)

Problem 2: 3Sum (LeetCode #15)

Given an integer array nums, return all the triplets [nums[i], nums[j], nums[k]] such that i != j, i != k, and j != k, and nums[i] + nums[j] + nums[k] == 0.

Example:

Input: nums = [-1,0,1,2,-1,-4]
Output: [[-1,-1,2],[-1,0,1]]

Solution in C++:

#include <vector>
#include <algorithm>
using namespace std;

vector<vector<int>> threeSum(vector<int>& nums) {
    sort(nums.begin(), nums.end());
    vector<vector<int>> result;
    int n = nums.size();
    
    for (int i = 0; i < n - 2; i++) {
        // Skip duplicates for first number
        if (i > 0 && nums[i] == nums[i-1]) {
            continue;
        }
        
        int left = i + 1;
        int right = n - 1;
        
        while (left < right) {
            int current_sum = nums[i] + nums[left] + nums[right];
            
            if (current_sum == 0) {
                result.push_back({nums[i], nums[left], nums[right]});
                
                // Skip duplicates
                while (left < right && nums[left] == nums[left + 1]) {
                    left++;
                }
                while (left < right && nums[right] == nums[right - 1]) {
                    right--;
                }
                
                left++;
                right--;
            } else if (current_sum < 0) {
                left++;  // Need larger sum
            } else {
                right--;  // Need smaller sum
            }
        }
    }
    
    return result;
}

Step-by-Step Simulation:

Input: nums = [-1,0,1,2,-1,-4]

After sorting: [-4,-1,-1,0,1,2]

i=0, nums[0]=-4: left=1, right=5

sum = -4 + (-1) + 2 = -3 < 0, left++

sum = -4 + (-1) + 2 = -3 < 0, left++

No solution found

i=1, nums[1]=-1: left=2, right=5

sum = -1 + (-1) + 2 = 0, found! Add [-1,-1,2]

left=3, right=4, sum = -1 + 0 + 1 = 0, found! Add [-1,0,1]

Result: [[-1,-1,2], [-1,0,1]]

Explanation: We fix the first number and use two pointers for the remaining two. After sorting, we iterate through each number as the first element, then use two pointers to find pairs that sum to the negative of the first number. We skip duplicates to avoid duplicate triplets in the result. Sorting allows us to use two pointers efficiently and skip duplicates easily.

Time Complexity: O(n²)

Space Complexity: O(1) excluding output array

Problem 3: Container With Most Water (LeetCode #11)

You are given an integer array height of length n. Find two lines that together with the x-axis form a container, such that the container contains the most water.

Example:

Input: height = [1,8,6,2,5,4,8,3,7]
Output: 49
Explanation: The above vertical lines form a container with area 49.

Solution in C++:

#include <vector>
#include <algorithm>
using namespace std;

int maxArea(vector<int>& height) {
    int left = 0;
    int right = height.size() - 1;
    int max_water = 0;
    
    while (left < right) {
        // Calculate area: width × min(height[left], height[right])
        int width = right - left;
        int current_area = width * min(height[left], height[right]);
        max_water = max(max_water, current_area);
        
        // Move the pointer with smaller height
        // This is optimal because:
        // - Moving the taller pointer can only decrease area (width decreases)
        // - Moving the shorter pointer might increase area (if we find taller line)
        if (height[left] < height[right]) {
            left++;
        } else {
            right--;
        }
    }
    
    return max_water;
}

Step-by-Step Simulation:

Input: height = [1,8,6,2,5,4,8,3,7]

left=0, right=8: area = 8 × min(1,7) = 8, max_water=8

height[0] < height[8], move left: left=1

left=1, right=8: area = 7 × min(8,7) = 49, max_water=49

height[1] > height[8], move right: right=7

left=1, right=7: area = 6 × min(8,3) = 18, max_water=49

height[1] > height[7], move right: right=6

left=1, right=6: area = 5 × min(8,8) = 40, max_water=49

height[1] == height[6], move either: left=2

Result: 49

Explanation: We start with pointers at both ends. The area is limited by the shorter line. We always move the pointer with the smaller height because moving the taller one can only decrease the area (width decreases, height can't increase). This greedy approach ensures we check all possible optimal containers. The key insight is that we never miss the optimal solution because if the optimal container uses lines at positions i and j, we'll find it before one of the pointers passes the other.

Time Complexity: O(n)

Space Complexity: O(1)

Problem 4: Valid Palindrome (LeetCode #125)

A phrase is a palindrome if, after converting all uppercase letters into lowercase letters and removing all non-alphanumeric characters, it reads the same forward and backward.

Example:

Input: s = "A man, a plan, a canal: Panama"
Output: true
Explanation: "amanaplanacanalpanama" is a palindrome.

Solution in C++:

#include <string>
#include <cctype>
using namespace std;

bool isPalindrome(string s) {
    int left = 0;
    int right = s.length() - 1;
    
    while (left < right) {
        // Skip non-alphanumeric characters
        while (left < right && !isalnum(s[left])) {
            left++;
        }
        while (left < right && !isalnum(s[right])) {
            right--;
        }
        
        // Compare characters (case-insensitive)
        if (tolower(s[left]) != tolower(s[right])) {
            return false;
        }
        
        left++;
        right--;
    }
    
    return true;
}

Explanation: We use two pointers from both ends. We skip non-alphanumeric characters by moving pointers until we find valid characters. Then we compare them case-insensitively using tolower(). If any pair doesn't match, it's not a palindrome. The algorithm continues until pointers meet or cross. This approach is efficient because we only process each character once.

Time Complexity: O(n)

Space Complexity: O(1)

Problem 5: Remove Duplicates from Sorted Array (LeetCode #26)

Given an integer array nums sorted in non-decreasing order, remove the duplicates in-place such that each unique element appears only once. The relative order of the elements should be kept the same.

Example:

Input: nums = [1,1,2]
Output: 2, nums = [1,2,_]
Explanation: Your function should return k = 2, with the first two elements of nums being 1 and 2 respectively.

Solution in C++:

#include <vector>
using namespace std;

int removeDuplicates(vector<int>& nums) {
    if (nums.empty()) {
        return 0;
    }
    
    int slow = 0;  // Position to place next unique element
    
    for (int fast = 1; fast < nums.size(); fast++) {
        // If current element is different from previous unique element
        if (nums[fast] != nums[slow]) {
            slow++;
            nums[slow] = nums[fast];
        }
    }
    
    return slow + 1;  // Length of array with unique elements
}

Step-by-Step Simulation:

Input: nums = [1,1,2]

Initial: slow=0, fast=1

fast=1: nums[1]=1 == nums[0]=1, skip

fast=2: nums[2]=2 != nums[0]=1

slow=1, nums[1]=2

Array: [1,2,2]

Result: 2 (first 2 elements are unique)

Explanation: We use slow and fast pointers. The slow pointer tracks where to place the next unique element. The fast pointer scans through the array. When we find a new unique element (different from the one at slow), we increment slow and place the new element there. Since the array is sorted, duplicates are adjacent, so this efficiently removes them. The in-place modification ensures O(1) space complexity.

Time Complexity: O(n)

Space Complexity: O(1)