Introduction
Two-pointers is an effective technique where two indices (or pointers) traverse the data structure in tandem to achieve a goal. Below, we explore six popular LeetCode problems across Easy, Medium, and Hard difficulties that use two pointers, each highlighting a different variant of the pattern. For each variant, we outline the modification to the basic two-pointer approach, provide a representative LeetCode example, walk through the solution step by step, and include code for clarity.
1. Find First Occurrence in Duplicates (Skip Adjacent Duplicates)
Modified Template: One pointer iterates, a second "write" pointer lags to build the result in-place. Since the array is sorted, duplicates are adjacent. Advance write-pointer only for new distinct elements.
Example Problem: LeetCode 26. Remove Duplicates from Sorted Array – Easy. Given a sorted array, remove duplicates in-place so each element appears once; return new length.
i = 0(write),j = 1(read).nums[0]is unique.j=1:nums[1](0) == nums[0](0). Skip.j=2:nums[2](1) != nums[0](0).i++(i=1).nums[1]=nums[2](1). Array:[0,1,...].j=3,4: Duplicates ofnums[1](1). Skip.j=5:nums[5](2) != nums[1](1).i++(i=2).nums[2]=nums[5](2). Array:[0,1,2,...].j=6: Duplicate ofnums[2](2). Skip.j=7:nums[7](3) != nums[2](2).i++(i=3).nums[3]=nums[7](3). Array:[0,1,2,3,...].
i=3. Return i+1 = 4. Array's first 4: [0,1,2,3].
def removeDuplicates(nums: list[int]) -> int:
if not nums: return 0
i = 0 # index of last unique element
for j in range(1, len(nums)):
if nums[j] != nums[i]:
i += 1
nums[i] = nums[j]
return i + 1
2. Reverse Vowels in a String (Two Pointers at Ends with Conditional Skip)
Modified Template: Two pointers start at opposite ends, moving inward. Each pointer moves until it hits a vowel (skipping consonants), then swap vowels.
Example Problem: LeetCode 345. Reverse Vowels of a String – Easy.
left=0 ('h'),right=4 ('o').- Move
left:s[0]='h'(skip) ->left=1.s[1]='e'(vowel). - Move
right:s[4]='o'(vowel). - Swap
chars[1]andchars[4]. String becomes "holle". left++(to 2),right--(to 3).left < right.- Move
left:s[2]='l'(skip) ->left=3.s[3]='l'(skip) ->left=4. - Move
right:s[3]='l'(skip) ->right=2. - Now
left=4, right=2.left >= right, loop ends.
def reverseVowels(s: str) -> str:
vowels = set("aeiouAEIOU")
chars = list(s)
left, right = 0, len(chars) - 1
while left < right:
while left < right and chars[left] not in vowels: left += 1
while left < right and chars[right] not in vowels: right -= 1
if left < right:
chars[left], chars[right] = chars[right], chars[left]
left += 1
right -= 1
return "".join(chars)
3. Two Pointers for Triplet Sum (3Sum Problem)
Modified Template: Sort array. Outer loop fixes one number. Inner two pointers (left/right on remaining part) find two numbers summing to target. Skip duplicates.
Example Problem: LeetCode 15. 3Sum – Medium. Find unique triplets [a,b,c] where a+b+c=0.
i=0 (val -4): Target sum for pair = 4.left=1, right=5.(-4) + (-1) + 2 = -3(low).left++. ... Loop ends, no triplet.
i=1 (val -1): Target sum for pair = 1.left=2, right=5.(-1) + (-1) + 2 = 0. Found[-1,-1,2].left++,right--. Skip duplicates.left=3(0), right=4(1):(-1) + 0 + 1 = 0. Found[-1,0,1].left++,right--.left >= right.
i=2 (val -1): Same asnums[i-1]. Skip.- ... (Outer loop continues)
[[-1,-1,2], [-1,0,1]].
def threeSum(nums: list[int]) -> list[list[int]]:
nums.sort()
res = []
n = len(nums)
for i in range(n - 2):
if i > 0 and nums[i] == nums[i-1]: continue # skip duplicates
left, right = i + 1, n - 1
while left < right:
total = nums[i] + nums[left] + nums[right]
if total == 0:
res.append([nums[i], nums[left], nums[right]])
left += 1; right -= 1
while left < right and nums[left] == nums[left-1]: left += 1
while left < right and nums[right] == nums[right+1]: right -= 1
elif total < 0: left += 1
else: right -= 1
return res
4. Container With Most Water (Max Area by Converging Pointers)
Modified Template: Two pointers at ends of array (line heights). Calculate area. Move pointer at shorter line inward (hoping for taller line to increase area).
Example Problem: LeetCode 11. Container With Most Water – Medium.
left=0(h=1), right=8(h=7). Width=8. Area=min(1,7)*8 = 8. MaxArea=8. Shorter is left (1<7),left++.left=1(h=8), right=8(h=7). Width=7. Area=min(8,7)*7 = 49. MaxArea=49. Shorter is right (7<8),right--.left=1(h=8), right=7(h=3). Width=6. Area=min(8,3)*6 = 18. MaxArea=49. Shorter is right (3<8),right--.- ... (Loop continues until
left >= right)
def maxArea(height: list[int]) -> int:
left, right = 0, len(height) - 1
max_area = 0
while left < right:
h_left, h_right = height[left], height[right]
width = right - left
curr_area = min(h_left, h_right) * width
max_area = max(max_area, curr_area)
if h_left < h_right: left += 1
else: right -= 1
return max_area
5. Trapping Rain Water (Two Pointers with Dynamic Max Tracking)
Modified Template: Two pointers at ends. Maintain leftMax and rightMax (highest walls seen from left/right). Move pointer on side with lower max height inward. Accumulate water based on min(leftMax, rightMax) - current_height.
Example Problem: LeetCode 42. Trapping Rain Water – Hard.
l=0, r=5.lMax=h[0]=4, rMax=h[5]=5.water=0.lMax(4) <= rMax(5): Movel.l=1.lMax=max(4,h[1]=2)=4.water += max(0, 4-2) = 2.lMax(4) <= rMax(5): Movel.l=2.lMax=max(4,h[2]=0)=4.water += max(0, 4-0) = 4. Total water=6.lMax(4) <= rMax(5): Movel.l=3.lMax=max(4,h[3]=3)=4.water += max(0, 4-3) = 1. Total water=7.lMax(4) <= rMax(5): Movel.l=4.lMax=max(4,h[4]=2)=4.water += max(0, 4-2) = 2. Total water=9.l=4, r=5. Loop conditionl < ris now false (or next iterlbecomes 5). Loop ends.
def trap(height: list[int]) -> int:
if not height: return 0
n = len(height)
left, right = 0, n - 1
leftMax, rightMax = height[left], height[right]
water = 0
while left < right:
if leftMax <= rightMax:
left += 1
leftMax = max(leftMax, height[left])
water += max(0, leftMax - height[left])
else:
right -= 1
rightMax = max(rightMax, height[right])
water += max(0, rightMax - height[right])
return water
6. Minimum Window Substring (Sliding Window with Two Pointers)
Modified Template: Sliding window. Right pointer expands window. Left pointer contracts when condition (all target chars present) is met. Track char frequencies.
Example Problem: LeetCode 76. Minimum Window Substring – Hard. Given s and t, find min window in s containing all chars of t.
need={'A':1,'B':1,'C':1},required=3,have=0.window_counts={}.res=(inf,N,N).left=0.right=0..5("ADOBEC"): `have` becomes 3. Window valid.res=(6,0,5).- Try shrink:
left=0('A'). If remove 'A',havebecomes 2. Cannot shrink.
- Try shrink:
right=6..9("ADOBECODEB"): No new chars fromtfully satisfied.right=10("ADOBECODEBA"): `have` is 3. Window [0..10] valid.- Try shrink:
left=0('A'). Can remove (another 'A' at index 10).left=1. Window [1..10] "DOBECODEBA". Still valid. ... Shrink untilleft=5. Window [5..10] "CODEBA" (len 6). No better thanres.
- Try shrink:
right=11("ADOBECODEBAN"): ...right=12("ADOBECODEBANC"): Window [5..12] "CODEBANC" valid.- Try shrink: ... until
left=9. Window [9..12] "BANC" (len 4).res=(4,9,12).
- Try shrink: ... until
from collections import Counter
def minWindow(s: str, t: str) -> str:
if not s or not t: return ""
need = Counter(t)
required = len(need)
have = 0
window_counts = Counter()
res = (float("inf"), None, None)
left = 0
for right, ch in enumerate(s):
window_counts[ch] += 1
if ch in need and window_counts[ch] == need[ch]:
have += 1
while have == required:
if right - left + 1 < res[0]:
res = (right - left + 1, left, right)
char_left = s[left]
window_counts[char_left] -= 1
if char_left in need and window_counts[char_left] < need[char_left]:
have -= 1
left += 1
return s[res[1]:res[2]+1] if res[0] != float("inf") else ""
Interactive Animation (Container With Most Water)
The animation below demonstrates the "Container With Most Water" problem using the two-pointer technique. Input array: [1,8,6,2,5,4,8,3,7]. Watch how the pointers converge and the max area is updated.