two-pointers

::: info Prerequisites

Before reading this article, you should first learn:

:::

When working with array and linked list problems, two-pointer techniques come up all the time. These techniques fall into two main categories: left-right pointers and fast-slow pointers.

Left-right pointers move toward or away from each other. Fast-slow pointers move in the same direction, but one moves faster than the other.

For singly linked lists, most techniques involve fast-slow pointers. Six Patterns for Linked List Problemsarrow-up-right covers all of them—things like cycle detection and finding the Kth node from the end. These problems are solved using a fast pointer and a slow pointer working together.

Arrays don't have actual pointers, but we can treat indices as pointers. This lets us apply two-pointer techniques to arrays as well. This article focuses on two-pointer algorithms for arrays.

Fast-Slow Pointer Techniques

In-Place Modification

A common use of fast-slow pointers in array problems is in-place modification.

Take LeetCode problem 26, "Remove Duplicates from Sorted Arrayarrow-up-right," which asks you to deduplicate a sorted array:

LeetCode 26. Remove Duplicates from Sorted Array

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

Consider the number of unique elements of nums to be k, to get accepted, you need to do the following things:

  • Change the array nums such that the first k elements of nums contain the unique elements in the order they were present in nums initially. The remaining elements of nums are not important as well as the size of nums.

  • Return k.

Custom Judge:

The judge will test your solution with the following code:

If all assertions pass, then your solution will be accepted.

Example 1:**

Example 2:**

Constraints:

  • 1 <= nums.length <= 3 * 10^(4)

  • -100 <= nums[i] <= 100

  • nums is sorted in non-decreasing order.

Here's the function signature:

Let me quickly explain what "in-place" means:

If we weren't required to do this in-place, we could just create a new int[] array, put the deduplicated elements in it, and return it.

But the problem requires in-place deletion—no new arrays allowed. You can only work with the original array and return a length. With that length and the original array, you can identify which elements remain after deduplication.

Since the array is sorted, duplicate elements are always adjacent, so finding them is easy. But if you delete each duplicate immediately when you find it, the data shifting involved would push the time complexity to $O(N^2)$.

The efficient solution uses fast-slow pointers:

The slow pointer trails behind while the fast pointer scouts ahead. When fast finds a non-duplicate element, it assigns that value to slow, and slow moves forward one step.

This guarantees that nums[0..slow] contains only unique elements. Once fast finishes traversing the entire array, nums[0..slow] is the deduplicated result.

Here's the code:

Open the visualization panel below and click the line while (fast < nums.length) multiple times to see how the two pointers maintain unique elements in nums[0..slow]:

Let's extend this a bit. Look at LeetCode problem 83, "Remove Duplicates from Sorted Listarrow-up-right." How would you deduplicate a sorted singly linked list?

It's essentially the same as array deduplication—the only difference is replacing array assignment with pointer manipulation. Compare it with the previous code:

Check out the visualization panel below to see the algorithm in action:

::: note Note

You might be wondering: the duplicate nodes aren't actually deleted from the linked list—they're just left hanging there. Is that okay?

This comes down to language-specific behavior. Languages like Java and Python have garbage collection that automatically finds and reclaims memory from these "dangling" nodes. Languages like C++ don't have automatic garbage collection, so you'd need to manually free the memory for these nodes.

That said, when it comes to developing algorithmic thinking, just understanding this fast-slow pointer technique is what matters.

:::

Beyond deduplicating sorted arrays/linked lists, you might also need to "remove" certain elements from an array in-place.

Take LeetCode problem 27, "Remove Elementarrow-up-right":

LeetCode 27. Remove Element

Given an integer array nums and an integer val, remove all occurrences of val in nums in-placearrow-up-right. The order of the elements may be changed. Then return *the number of elements in nums which are not equal to *val.

Consider the number of elements in nums which are not equal to val be k, to get accepted, you need to do the following things:

  • Change the array nums such that the first k elements of nums contain the elements which are not equal to val. The remaining elements of nums are not important as well as the size of nums.

  • Return k.

Custom Judge:

The judge will test your solution with the following code:

If all assertions pass, then your solution will be accepted.

Example 1:**

Example 2:**

Constraints:

  • 0 <= nums.length <= 100

  • 0 <= nums[i] <= 50

  • 0 <= val <= 100

The problem asks you to remove all elements equal to val from nums in-place. Once again, we use fast-slow pointers:

If fast encounters an element equal to val, it skips over it. Otherwise, it assigns the value to slow and moves slow forward one step.

The approach is exactly the same as the array deduplication problem. Here's the code:

Open the visualization panel below and click the line while (fast < nums.length) multiple times to see how the two pointers maintain nums[0..slow] without the target element:

Notice a subtle difference from the sorted array deduplication solution: here we assign to nums[slow] first, then increment slow++. This ensures nums[0..slow-1] contains no elements equal to val, so the final result length is slow.

With this removeElement function implemented, let's look at LeetCode problem 283, "Move Zeroesarrow-up-right":

Given an array nums, modify it in-place to move all zeros to the end. Here's the function signature:

For example, given nums = [0,1,4,0,2], your algorithm returns nothing but modifies nums in-place to [1,4,2,0,0].

Given what we've covered so far, can you already see the solution?

A slight modification to the removeElement function above solves this problem, or you can just reuse removeElement directly.

Moving all zeros to the end is essentially removing all zeros from nums, then setting the remaining positions to 0:

Open the visualization panel below and click the line while (fast < nums.length) multiple times to watch the fast and slow pointers move. Then click the line nums[p] = 0; multiple times to see the remaining positions set to 0:

That wraps up these in-place array modification problems.

Sliding Window

Another major category of fast-slow pointer problems in arrays is the "sliding window algorithm." I've provided a code framework for sliding window in The Core Framework for Sliding Window Algorithmsarrow-up-right:

I won't repeat the specific problems here—instead, let me just highlight the fast-slow pointer nature of the sliding window algorithm:

The left pointer stays behind while the right pointer moves ahead. The portion between these two pointers forms the "window," and the algorithm solves problems by expanding and shrinking this window.

II. Common Left-Right Pointer Algorithms

I've covered the details of binary search code in The Binary Search Framework Explainedarrow-up-right. Here, I'll just show the simplest version to highlight its two-pointer nature:

n-Sum Problems

Let's look at LeetCode problem 167, "Two Sum IIarrow-up-right":

LeetCode 167. Two Sum II - Input Array Is Sorted

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 number. Let these two numbers be numbers[index1] and numbers[index2] where 1 <= index1 < index2 <= numbers.length.

Return* the indices of the two numbers, index1 and index2, added by one as an integer array [index1, index2] of length 2.*

The tests are generated such that there is exactly one solution. You may not use the same element twice.

Your solution must use only constant extra space.

Example 1:**

Example 2:**

Example 3:**

Constraints:

  • 2 <= numbers.length <= 3 * 10^(4)

  • -1000 <= numbers[i] <= 1000

  • numbers is sorted in non-decreasing order.

  • -1000 <= target <= 1000

  • The tests are generated such that there is exactly one solution.

Whenever you see a sorted array, think two pointers. The approach here is similar to binary search—by adjusting left and right, you can control the value of sum:

In another article, One Function to Solve All nSum Problemsarrow-up-right, I use a similar left-right pointer technique to provide a general approach for nSum problems. At its core, it's all about the two-pointer technique.

Reversing an Array

Most programming languages provide a reverse function, but the underlying principle is quite simple. LeetCode problem 344, "Reverse Stringarrow-up-right," asks you to do something similar—reverse a char[] character array. Let's look at the code:

For more advanced problems involving array reversal, check out Creative Ways to Traverse 2D Arraysarrow-up-right.

Palindrome Check

A palindrome is a string that reads the same forwards and backwards. For example, aba and abba are both palindromes because they're symmetric—reverse them and you get the same thing. On the other hand, abac is not a palindrome.

By now, you can probably sense that palindrome problems are closely tied to left-right pointers. If you need to check whether a string is a palindrome, you might write something like this:

Now let's step it up a notch. Given a string, can you use the two-pointer technique to find the longest palindrome within it?

That's LeetCode problem 5, "Longest Palindromic Substringarrow-up-right":

LeetCode 5. Longest Palindromic Substring

Given a string s, return the longest palindromic substring in s.

Example 1:**

Example 2:**

Constraints:

  • 1 <= s.length <= 1000

  • s consist of only digits and English letters.

The function signature looks like this:

The tricky part about finding palindromes is that they can have either odd or even length. The key to solving this is using two pointers that expand outward from the center.

If a palindrome has odd length, it has one center character. If it has even length, you can think of it as having two center characters. So let's first implement a helper function:

This way, if you pass in the same value for l and r, you're looking for odd-length palindromes. If you pass in adjacent values for l, r, you're looking for even-length palindromes.

With that in place, here's the general approach for finding the longest palindrome:

Translated into code, this solves the longest palindromic substring problem:

Click the visualization panel below and repeatedly click on the line while (l >= 0 && r < s.length && s[l] === s[r]) to watch the l, r pointers expand outward from the center:

You'll notice that the left-right pointers in the longest palindromic substring problem work differently from the previous examples. Before, our left and right pointers moved inward from both ends toward the middle. For palindrome problems, the pointers expand outward from the center. That said, this pattern really only comes up with palindrome-type problems, so I still classify it under left-right pointers.

That wraps up all the two-pointer techniques for arrays. For more extensions and variations on these techniques, check out More Classic Two-Pointer Problems for Arraysarrow-up-right.

Last updated