Two Pointer Algorithm: Li Yin January 19, 2019
Two Pointer Algorithm: Li Yin January 19, 2019
Two Pointer Algorithm: Li Yin January 19, 2019
Li Yin1
1
www.liyinscience.com
ii
Linear Search
2. Opposite-directional: One at the start and the other at the end, they
move close to each other and meet in the middle, (-> <-).
In order to use two pointers, most times the data structure needs to be
ordered in some way, and decrease the time complexity from O(n2 ) or O(n3 )
of two/three nested for/while loops to O(n) of just one loop with two pointers
and search each item just one time. In some cases, the time complexity is
highly dependable on the data and the criteria we set.
As shown in Fig. 1, the pointer i and j can decide: a pair or a subarray
(with all elements starts from i and end at j). We can either do search
related with a pair or a subarray. For the case of subarray, the algorithm
is called sliding window algorithm. As we can see, two pointers and sliding
window algorithm can be used to solve K sum (Section ??), most of the
subarray (Section ??), and string pattern match problems (Section ??).
Two pointer algorithm is less of a talk and more of problem attached. We
will explain this type of algorithm in virtue of both the leetcode problems
and definition of algorihtms. To understand two pointers techniques, better
to use examples, here we use two examples: use slow-faster pointer to find
iii
iv LINEAR SEARCH
the median and Floyd’s fast-slow pointer algorithm for loop detection in an
array/linked list and two pointers to get two sum.
Input : [ 1 , 2 , 3 , 4 , 5 ]
Output : Node 3 from t h i s l i s t ( S e r i a l i z a t i o n : [ 3 , 4 , 5 ] )
Example 2 ( even l e n g t h ) :
Input : [ 1 , 2 , 3 , 4 , 5 , 6 ]
Output : Node 4 from t h i s l i s t ( S e r i a l i z a t i o n : [ 4 , 5 , 6 ] )
[h] We place two pointers simultaneously at the head node, each one moves
at different paces, the slow pointer moves one step and the fast moves two
steps instead. When the fast pointer reached the end, the slow pointer will
stop at the middle. For the loop, we only need to check on the faster pointer,
0.1. TWO-POINTER SEARCH v
make sure fast pointer and fast.next is not None, so that we can successfuly
visit the fast.next.next. When the length is odd, fast pointer will point at
the end node, because fast.next is None, when its even, fast pointer will
point at None node, it terimates because fast is None.
1 d e f middleNode ( s e l f , head ) :
2 slow , f a s t = head , head
3 w h i l e f a s t and f a s t . next :
4 f a s t = f a s t . next . next
5 s l o w = s l o w . next
6 return slow
In order to know the starting node of the cycle. Here, we set the distance
of the starting node of the cycle from the head is x, and y is the distance from
the start node to the slow and fast pointer’s node, and z is the remaining
distance from the meeting point to the start node.
Now, let’s try to device the algorithm. Both slow and fast pointer starts
at position 0, the node index they travel each step is: [0,1,2,3,...,k] and
[0,2,4,6,...,2k] for slow and fast pointer respectively. Therefore, the total
distance traveled by the slow pointer is half of the distance travelled by the
fat pointer. From the above figure, we have the distance travelled by slow
pointer to be ds = x+y, and for the fast pointer df = x+y+z+y = x+2y+z.
With the relation 2 ∗ ds = df . We will eventually get x = z. Therefore, by
moving slow pointer to the start of the linked list after the meeting point,
and making both slow and fast pointer to move one node at a time, they
will meet at the starting node of the cycle. (LeetCode problem: 142. Linked
List Cycle II (medium)).
1 d e f d e t e c t C y c l e ( s e l f , head ) :
2 s l o w = f a s t = head
3 bCycle = F a l s e
4 w h i l e f a s t and f a s t . next :
vi LINEAR SEARCH
5 s l o w = s l o w . next
6 f a s t = f a s t . next . next
7 i f s l o w == f a s t : # a c y c l e i s found
8 bCycle = True
9 break
10
11 i f not bCycle :
12 r e t u r n None
13 # r e s e t t h e s l o w p o i n t e r t o f i n d t h e s t a r t i n g node
14 s l o w = head
15 w h i l e f a s t and s l o w != f a s t :
16 s l o w = s l o w . next
17 f a s t = f a s t . next
18 return slow
In order to remove the cycle as shown in Fig. 4, the starting node is when
slow and fast intersect, the last fast node before they meet. For the example,
we need to set -4 node’s next node to None. Therefore, we modify the above
code to stop at the last fast node instead:
1 # r e s e t t h e s l o w p o i n t e r t o f i n d t h e s t a r t i n g node
2 s l o w = head
3 w h i l e f a s t and s l o w . next != f a s t . next :
4 s l o w = s l o w . next
0.1. TWO-POINTER SEARCH vii
5 f a s t = f a s t . next
6 f a s t . next = None
Due to the fact that the array is sorted which means in the array [s,s1
..., e1, e], the sum of any two integer is in range of [s+s1, e1+e]. By placing
two pointers each start from s and e, we started the search space from the
middle of the possible range. [s+s1, s+e, e1+e]. Compare the target t with
the sum of the two pointers v1 and v2 :
1. t == v1 + v2 : found
1 d e f twoSum ( s e l f , numbers , t a r g e t ) :
2 #u s e two p o i n t e r s
3 n = l e n ( numbers )
4 i , j = 0 , n−1
5 while i < j :
6 temp = numbers [ i ] + numbers [ j ]
viii LINEAR SEARCH
7 i f temp == t a r g e t :
8 r e t u r n [ i +1 , j +1]
9 e l i f temp < t a r g e t :
10 i += 1
11 else :
12 j −= 1
13 return [ ]
13 r e t u r n ans
1. It is a subarray/substring problem.
For example, 644. Maximum Average Subarray II (hard) does not satisfy
the completeness. Because the average of subarray does not follow a certain
order that we can decided how to move the window.
Sliding Window Algorithm with Sum In this part, we list two exam-
ples that we use flexible sliding window algorithms to solve subarray problem
with sum condition.
Given an array of n positive integers and a positive integer s, find the
minimal length of a contiguous subarray of which the sum >= s. If there isn’t
one, return 0 instead. (LeetCode Problem: 209. Minimum Size Subarray
Sum (medium)).
Example :
I n p u t : s = 7 , nums = [ 2 , 3 , 1 , 2 , 4 , 3 ]
Output : 2
E x p l a n a t i o n : t h e s u b a r r a y [ 4 , 3 ] has t h e minimal l e n g t h under t h e
problem c o n s t r a i n t .
x LINEAR SEARCH
As we have shown in Fig. 6, the prefix sum is the subarray starts with the
first item in the array, we know that the sum of the subarray is monotoni-
cally increasing as the size of the subarray increase. Therefore, we place a
’window’ with left and right as i and j at the first item first. The steps are
as follows:
1. Get the optimal subarray starts from current i, 0: Then we first move
the j pointer to include enough items that sum[0:j+1]>=s, this is the
process of getting the optimial subarray that starts with 0. And as-
sume j stops at e0
2. Get the optimal subarray ends with current j, e0 : we shrink the window
size by moving the i pointer forward so that we can get the optimal
subarray that ends with current j and the optimal subarray starts from
s0 .
3 n = l e n ( nums )
4 i = j = 0
5 acc = 0 # acc i s the s t a t e
6 while j < n :
7 a c c += nums [ j ]# i n c r e a s e t h e window s i z e
8 w h i l e a c c >= s :# s h r i n k t h e window t o g e t t h e o p t i m a l
result
9 ans = min ( ans , j −i +1)
10 a c c −= nums [ i ]
11 i += 1
12 j +=1
13 r e t u r n ans i f ans != f l o a t ( ’ i n f ’ ) e l s e 0
Example 2 :
window in a way to get rid of this char in the current window by moving
the i pointer one step after this char.
1 def lengthOfLongestSubstring ( s e l f , s ) :
2 i f not s :
3 return 0
4 n = len ( s )
5 state = set ()
6 i = j = 0
7 ans = − f l o a t ( ’ i n f ’ )
8 while j < n :
9 i f s [ j ] not i n s t a t e :
10 s t a t e . add ( s [ j ] )
11 ans = max( ans , j −i )
12 else :
13 # s h r i n k t h e window : g e t t h i s c h a r out o f t h e window
14 w h i l e s [ i ] != s [ j ] : # f i n d t h e c h a r
15 s t a t e . remove ( s [ i ] )
16 i += 1
17 # skip t h i s char
18 i += 1
19 j += 1
20 r e t u r n ans i f ans != − f l o a t ( ’ i n f ’ ) e l s e 0
Now, let us see another example with string ang given a pattern to match.
Given a string S and a string T, find the minimum window in S which will
contain all the characters in T in complexity O(n). (LeetCode Problem: 76.
Minimum Window Substring (hard))
Example :
In this problem, the desirable window is one that has all characters from
T. The solution is pretty intuitive. We keep expanding the window by
moving the right pointer. When the window has all the desired characters,
we contract (if possible) and save the smallest window till now. The only
difference compared with the above problem is the definition of desirable:
we need to compare the state of current window with the required state in
T. They can be handled as a hashmap with character as key and frequency
of characters as value.
1 d e f minWindow ( s e l f , s , t ) :
2 d i c t _ t = Counter ( t )
3 s t a t e = Counter ( )
4 required = len ( dict_t )
5
6 # l e f t and r i g h t p o i n t e r
7 i , j = 0, 0
8
9 formed = 0
10 ans = f l o a t ( " i n f " ) , None # min l e n , and s t a r t pos
0.1. TWO-POINTER SEARCH xiii
11
12 while j < len ( s ) :
13 char = s [ j ]
14 # record current state
15 i f char in dict_t :
16 s t a t e [ c h a r ] += 1
17 i f s t a t e [ c h a r ] == d i c t _ t [ c h a r ] :
18 formed += 1
19
20 # Try and c o n t r a c t t h e window t i l l t h e p o i n t where i t
c e a s e s t o be ’ d e s i r a b l e ’ .
21 # bPrint = False
22 w h i l e i<=j and formed == r e q u i r e d :
23 # i f not b P r i n t :
24 # p r i n t ( ’ found : ’ , s [ i : j +1] , i , j )
25 # b P r i n t = True
26 char = s [ i ]
27 i f j −i +1 < ans [ 0 ] :
28 ans = j − i + 1 , i
29 # change t h e s t a t e
30 i f char in dict_t :
31 s t a t e [ c h a r ] −= 1
32 i f s t a t e [ c h a r ] == d i c t _ t [ c h a r ] −1:
33 formed −= 1
34
35 # Move t h e l e f t p o i n t e r ahead ,
36 i += 1
37
38 # Keep expanding t h e window
39 j += 1
40 # i f bPrint :
41 # p r i n t ( ’ move t o : ’ , s [ i : j +1] , i , j )
42 r e t u r n " " i f ans [ 0 ] == f l o a t ( " i n f " ) e l s e s [ ans [ 1 ] : ans [ 1 ] +
ans [ 0 ] ]
I nput : A = [ 1 , 0 , 1 , 0 , 1 ] , S = 2
Output : 4
xiv LINEAR SEARCH
Explanation :
The 4 s u b a r r a y s a r e b o l d e d below :
[1 ,0 ,1 ,0 ,1]
[1 ,0 ,1 ,0 ,1]
[1 ,0 ,1 ,0 ,1]
[1 ,0 ,1 ,0 ,1]
• Substring: for an array with char as value, it requires the state of each
subarray does not related to the order of the characters (anagram-like
state) so that we can have the sliding window property.
2. Handle the right pointer and record the state of the current window;
4. Up till here, the state is not desirable. Move the right pointer in order
to find a desirable window;