Two pointers problems are commonly easy and medium questions in Leetcode,, and may become difficult questions under specific question backgrounds and requirements.
If it is a sliding window, the difficulty is basically in the medium and difficult level.

Overview

Two pointer is the first intermediate algorithm that most algorithm beginners can come up with individually on their way to advanced level.

When faced with a local search problem in an array (i.e. finding a segment that meets the conditions of arr[i:j]). Compared with O(N^2) brute force cracking, under certain requirements, algorithm beginners will start to think: Is there a way to solve the problem by searching the array only once?

After some hearty thinking, the beginner will realize that Compared with the mindless movement of the two search pointers i and j by brute force, it is entirely possible to record more meaningful content by customizing the movement rules of the pointers, thereby ignoring some unnecessary repeated searches and achieving O(N) time complexity.

It is also at this time that most beginners begin to realize the meaning of exchanging space (complexity) for time (complexity).

Of course, double pointer is a very general and rough name. Any algorithm that uses two pointers with specific movement rules can be called double pointer. Therefore, the binary search method can also be regarded as a double-pointer algorithm, but the essence of the binary search algorithm still lies in the core idea of binary search.

Categories of Two Pointers

We can categorize the Two Pointers into four main categories based on the moving pattern of the two pointers.

  1. Left & Right / Head & Tail / Collision
  2. Feature: One array, move from two sides to the middle / towards each other

    Usually used in: search in ordered array (binary search)

  3. Fast & Slow (Forward)
  4. Feature: One array, both move forward/the same direction, in different pattern

    Usually used in: Linked List, search for cycle / middle point

  5. Parallel
  6. Feature: Two arrays, each array has been assigned a pointer.

    Usually used in: Searching in more than one array & Merging two sorted sub-arraies in Merge Sort.

  7. Sliding Window
  8. Feature: Both pointers moving in the same direction at a fixed difference of k.

    Usually used in: subarray, string, substring

Difficulty in Two Pointers⭐️

There are generally only two difficulties, corresponding to design and implementation:

  1. Design: Is it possible to clearly define the movement strategies of the two pointers (what are the two pointers used for?)
  2. Implementation: Can you take all edge cases into account during coding (can you write them down or not)?
Difficulty 1

Usually it is because of lack of sufficient practice that one does not think of using the two-pointer algorithm [which is the most inappropriate thing], or one cannot figure out the movement logic of the two pointers, resulting in failure in solving the problem. Therefore, for Difficulty 1, there is no other technique except practicing more.

Difficulty 2

The other problem is that you lack sufficient coding skills and lose patience or make mistakes when dealing with complex logic. Unlike Difficulty 1, this problem can be solved with some skills.

Trick:「Loop Invariant」

The loop invariant refers to an unchanging property that we must follow when writing code.

This property requires us to define it according to the problem, It is important to ensure that the three stages of 「initialization」, 「loop traversal」 and 「ending」 have the same properties so that a problem can be solved correctly.

Different definitions of intervals determine different initialization logic and logic during the traversal process.

Therefore, the loop invariant is not the essence of the so-called two-pointer algorithm, but a technique to write a two-pointer solution sufficiently thorough for complex problems.

This trick can also apply to other algorithms.

Problem Collection of Classic Two Pointers

ID Problem Diff Two Pointers Solution
26 Remove Duplicates from Sorted Array Easy Type: Fast & Slow

Fast pointer: Keep searching for next unknown element.
Slow pointer: Locate the position to fill the next element

674 Longest Continuous Increasing Subsequence Easy Type: Fast & Slow

Fast pointer: Keep searching for the next element that satisfies the monotonically increasing
Slow pointer: Locates the start of this incrementing sequence

When the fast pointer element violates monotonically increasing, the slow pointer moves to the position of the current fast pointer, which means searching for a new increasing sequence.

977 Squares of a Sorted Array Easy Type: Collision

Left pointer: Indicates that the negative end may compete for the element with the maximum value after square
Right pointer: Indicates that the positive end may compete for the element with the maximum value after square

Hint: When negative and positive numbers can be excluded, the result can be directly output

27 Remove Element Easy Type: Fast & Slow

Fast pointer: Constantly looking for the next element that does not need to be removed
Slow pointer: Locates the next element to be filled

80 Remove Duplicates from Sorted Array II Medium Type: Fast & Slow

Fast pointer: Continuously searches for the next element and decides whether to insert and update the reference element based on the number of repetitions.
Slow pointer: Locates the insertion position of the next element

Difficulty: A special restriction of "no more than two repetitions" was added to question 26, but it is just an additional judgment, not complicated.
283 Move Zeroes Easy Type: Fast & Slow

Fast pointer: Keep looking for the next non-zero element
Slow pointer: Locates the insertion position of the next non-zero element

Hint: Can fill up all the 0 using the interval [slow:n) at last.
11 Container With Most Water Medium Type: Collision

Left pointer: Points to the left border of the current traversal layer
Right pointer: Points to the right border of the current traversal layer

Pointer Pattern: [Greedy] Always move the shorter side, because the length decreases at a fixed rate, and there is no loss in replacing the shorter side.

Problem Collection of Tricky Loop Invariant Two Pointers

ID Problem Diff Two Pointers Solution
75 Sort Colors Medium 【Dutch National Flag Problem⭐️】
Type: Collision [variant]

Fast pointer: Locate the position to swap 1
Slow pointer: Locate the position to swap 2
In the Dutch flag problem, a single pointer O(2N) can also be used. The moving strategy of the two pointers in the double pointer solution is inconsistent with the conventional collision pointer.

Difficulty: Consider the case [2, 1, 2]
There exists a situation where both right pointer and i are 2, so we need to use "while i <= right and nums[i] == 2: … right -= 1" to make sure that we won't miss the 2 on the left [if we takes the index from 0 to n-1 by default]
215 Kth Largest Element in an Array Medium [A variant of Quick Sort]
Type: Collision [variant]

The left and right pointers are not prestigiously distinguished, with the Sentinel Division and Recursion to partition the left & right of the pivot, till the end.

Trick: Quick Selection

The question requires finding the Kth largest value, so there is no need to sort the array. Instead, it is similar to a binary search. When the number on the left and right sides of the pivot can determine that the Kth largest value does not exist in an interval, the interval can be discarded directly.

Because the time complexity is required to be O(N), it is obviously a divide-and-conquer algorithm.
59 Spiral Matrix II Medium 【Simulation Method based on Loop Invariant】
Loop Invariant: Simulated by layer, and there are 4 edges in each layer, can be represented by the intervals:[left,right), [up, down),[right, left), [down, up).
Till all the layers are traversed.

Problem Collection of Two Pointers in Linked List

ID Problem Diff Two Pointer Solution
141 Linked List Cycle Easy Type: Fast & Slow

Fast pointer: traverse 2 nodes each step.
Slow pointer: traverse 1 node each step.

[Trick]: If there is a cycle, then the two pointers will eventually meet, otherwise the fast pointer can easily reach the tail.
19 Remove Nth Node From End of List Medium Type: Fast & Slow

Fast pointer: N nodes ahead of slow pointer.
Slow pointer: starts from the【dummy head】

If the fast pointer has reached the end, then directly remove the next node at the slow pointer.

876 Middle of the Linked List Easy Type: Fast & Slow

Fast pointer: traverse 2 nodes at a time.
Slow pointer: traverse 1 node at a time.

Once the fast pointer reached the end, directly return the node pointed by slow pointer.
142 Linked List Cycle II Medium Type: Fast & Slow

Fast pointer: traverse 2 nodes at a time.
Slow pointer: traverse 1 node at a time.

Difficulty: O(1) time complexity to find the entrance of cycle.

Might need some mathematical mindset Assume there are a nodes before the entrance of cycle, and there are b nodes in cycle, you will notice:

Once the two pointers meet, the fast pointer mvoe twice the nodes than slow pointer, so we can have:

  • 2x = a + M*b + w [w is the index of node in cycle]
  • x = a + N*b + w

Substract x from 2x, and we can prove that

  • x = (M - N) * b [let M-N = k]
This means that it only takes `a` more steps for the slow pointer to find the entrance of cycle (a + k * b).

Now we need to reconstruct the Fast & Slow pointers, and the slow pointer can point back to head, and the two pointers now need to walk in the same pace, so they will meet at the entrance of the cycle.

160 Intersection of Two Linked Lists Easy [This question does not require space complexity, and can be solved with a hash table, so it is a simple question, but the double pointer can be O(1) space, O(m+n) time, this step is a little difficult, it is considered medium difficulty]
Type: Two Single Pointers

Trick: the core technique is actually to align the two linked lists. You can find the length by traversing, and then align the two according to the length, and then traverse both linked lists synchronously from the beginning with a pointer. When you traverse to the same node, it is the intersection entrance.

Problem Collection of Two Pointers Improving Deduplication

This type of problem usually has one or more typical solutions, but the algorithm that simplifies the retrieving process often makes the deduplication process more complicated. The deduplication complexity can be simplified by using sorted double pointers.

ID Problem Diff Two Pointer Solution
15 3Sum Medium 【Hash Map Solution】

On the basis of two sum, the most typical solution would be Hash Map, which only takes two nested for loop to determine the value of a + b. And then determine wheter 0-(a+b) exists in the array using HashMap. This idea is actually correct. However, the major issue is deduplication, as the question suggests that the return array should not contain any duplicated combination.

It is very time-consuming to first find all the unique triples in the index through hashing and then remove them, which may easily time out.

It would be more efficient to solve this problem by using two pointers than hashmaps

Due to the complexity of this problem, the double pointer type we are going to use is also quite special, which I call: Nested Collision Pointers

First we need to sort the array, and has a for loop, where i starts from index 0, and init a pointer left starting at index i+1, along with another pointer right starting at the end of the array.

Pointers Startegy

if nums[i] + nums[left] + nums[right] > 0, then it means that the three sum is too large, and as the array has been sorted, so the right pointer needs to be moves to its left

if nums[i] + nums[left] + nums[right] < 0, then it means that the three sum is too small, so left needs to go right, until left and right eventually meet.

18 4Sum Medium

The same idea with 15. 3Sum. Using Two Pointers, and only requires one more for loop nested on the basis of 3Sum.

The solution is to have two nested for loop, where nums[k] + nums[i] are certain, and still have left and right pointers within each for loop, and move based on the three situations of: nums[k] + nums[i] + nums[left] + nums[right] == target, The time complexsity of 3Sum is O(n^2), where the time complexsity of 4Sum is O(n^3) 。

Likewise, 5Sum, 6Sum, etc can be solved in similar manners.

Think: Can we simplifies the Two Sum problem using Two Pointers?

The answer is NO. It is very simple, because the Two Sum problem needs us to return index, instead of values, so we cannot sort the array.

Note: You can get more Two Pointers exercises through the [Similar Questions] function of Leetcode

Data Structure and Algorithm - Two Pointers
https://billyjojojobulidogithub.io/notes/Algorithm/twopointer/english/index.html
Author
Baocheng Wang
Posted on
July 27, 2024
Licensed under