Using the Two Pointer Technique
Jake Z.
Posted on August 15, 2020
The Two Pointer Technique
The two pointer technique is a near necessity in any software developer's toolkit, especially when it comes to technical interviews. In this guide, we'll cover the basics so that you know when and how to use this technique.
This lesson was originally published at https://algodaily.com, where I maintain a technical interview course and write think-pieces for ambitious developers.
What is the pattern?
The name two pointers
does justice in this case, as it is exactly as it sounds. It's the use of two different pointers (usually to keep track of array or string indices) to solve a problem involving said indices with the benefit of saving time and space. See the below for the two pointers highlighted in yellow.
But what are pointers
? In computer science, a pointer
is a reference to an object. In many programming languages, that object stores a memory address of another value located in computer memory, or in some cases, that of memory-mapped computer hardware.
When do we use it?
In many problems involving collections such as arrays or lists, we have to analyze each element of the collection compared to its other elements.
There are many approaches to solving problems like these. For example we usually start from the first index and iterate through the data structure
one or more times depending on how we implement our code.
Sometimes we may even have to create an additional data structure
depending on the problem's requirements. This approach might give us the correct result, but it likely won't give us the most space and time efficient result.
This is why the two-pointer technique
is efficient. We are able to process two elements per loop instead of just one. Common patterns in the two-pointer approach entail:
- Two pointers, each starting from the beginning and the end until they both meet.
- One pointer moving at a slow pace, while the other pointer moves at twice the speed.
These patterns can be used for string or array questions. They can also be streamlined and made more efficient by iterating through two parts of an object simultaneously. You can see this in the Two Sum problem or Reverse a String problems.
Running through an example
One usage is while searching for pairs in an array. Let us consider a practical example: assume that you have a sorted array arr
.
You're tasked with figuring out the pair of elements where arr[p]
+ arr[q]
add up to a certain number. (To try this problem out, check the Two Sum and Sorted Two Sum problems here.)
The brute force solution is to compare each element with every other number, but that's a time complexity of O(n^2)
. We can do better!
So let's optimize. You need to identify the indices pointer_one
and pointer_two
whose values sum to the integer target
.
Let's initialize two variables, pointer_one
and pointer_two
, and consider them as our two pointers.
pointer_one = 0
pointer_two = len(arr)-1
Note that len(arr)-1
helps to get the last index possible in an array.
Also observe that when we start, pointer_one
points to the first element of the array, and pointer_two
points to the last element.
This won't always be the case with this technique (we'll explore the sliding window concept later, which uses two pointers but have them move in a different direction). For our current purposes, it is more efficient to start wide, and iteratively narrow in (particularly if the array is sorted).
def two_sum(arr, target):
pointer_one = 0
pointer_two = input.length - 1
while pointer_one < pointer_two:
Since the array is already sorted, and we're looking to process an index at each iteration, we can use two pointers to process them faster. One pointer starts from the beginning of the array, and the other pointer begins from the end of the array, and then we add the values at these pointers.
Once we're set up, what we want to do is check if the current pointers already sum up to our target. This might happen if the correct ones are on the exact opposite ends of the array.
Here's what the check might look like:
if sum == targetValue:
return true
However, it likely will not be the target immediately. Thus, we apply this logic: if the sum of the values is less than the target value, we increment the left pointer (move your left pointer pointer_one
one index rightwards).
And if the sum is higher than the target value, we decrement the right pointer (correct the position of your pointer pointer_two
if necessary).
elif sum < targetValue:
pointer_one += 1
else:
pointer_two -= 1
In other words, understand that if arr[pointer_one]
< target-arr[pointer_two]
, it means we should move forward on pointer_one
to get closer to where we want to be in magnitude.
This is what it looks like all put together:
def two_sum(arr, target):
pointer_one = 0
pointer_two = input.length - 1
while pointer_one < pointer_two:
sum = input[pointer_one] + input[pointer_two]
if sum == targetValue:
return true
elif sum < targetValue:
pointer_one += 1
else:
pointer_two -= 1
return false
It's crucial to see that how both indices were moving in conjunction, and how they depend on each other.
We kept moving the pointers until we got the sum that matches the target value-- or until we reached the middle of the array, and no combinations were found.
The time complexity of this solution is O(n)
and space complexity is O(1)
, a significant improvement over our first implementation!
Another Example
In addition, to the previous example, the two pointer technique can also involve the pattern of using a fast pointer and a slow pointer.
Node fast = head, slow = head
One usage is through detecting cycles in a linked list
data structure. For example, a cycle (when a node points back to a previous node) begins at the last node of the linked list
in the example below.
1 -- > 2 --> 3 --> 4
^ |
| |
<- - -
The idea is to move the fast pointer twice as quickly as the slow pointer so the distance between them increases by 1
at each step.
while(fast!=null && fast.next!=null){
slow = slow.next;
fast = fast.next.next;
However, if at some point both pointers meet, then we have found a cycle in the linked list. Otherwise we'll have have reached the end of the list and no cycle is present.
if (slow==fast) return true;
The attached code is what the entire method would look like all together.
The time complexity would be O(N)
or linear time.
public static boolean detectCycle(Node head){
Node fast = head, slow = head;
while(fast!=null && fast.next!=null){
slow = slow.next;
fast = fast.next.next;
if(slow==fast) return true;
}
return false;
}
Posted on August 15, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.