Pattern family: Array / String / LinkedList Optimization Notes & Templates: 01_two_pointers_notes.md Extended problems: 03_two_pointers_extended.md Language: Java
- P1 β Pair with Target Sum (2Sum II)
- P2 β Triplet Sum to Zero (3Sum)
- P3 β Triplet Sum Close to Target (3Sum Closest)
- P4 β Triplets with Smaller Sum
- P5 β Quadruple Sum to Target (4Sum)
- P9 β Remove Duplicates from Sorted Array
- P10 β Rearrange 0s and 1s
- P11 β Comparing Strings with Backspaces
- P16 β Minimum Window Sort
- P17 β Dutch Flag β Remove Palindromic Subsequences
- P18 β Reverse Words in a String
- P19 β Trapping Rain Water
- P20 β Container with Most Water
LeetCode #167 | Difficulty: Easy | Company: Amazon | Pattern: Both-ends
| Condition | Action |
|---|---|
arr[L] + arr[R] == target |
return [L+1, R+1] (1-indexed) |
arr[L] + arr[R] < target |
L++ (need bigger) |
arr[L] + arr[R] > target |
R-- (need smaller) |
public int[] twoSum(int[] numbers, int target) {
int left = 0, right = numbers.length - 1;
while (left < right) {
int sum = numbers[left] + numbers[right];
if (sum == target) return new int[]{left + 1, right + 1};
else if (sum < target) left++;
else right--;
}
return new int[]{-1, -1};
}Input: numbers = [2,7,11,15], target = 9
left=0(2), right=3(15): sum=17 > 9 β right--
left=0(2), right=2(11): sum=13 > 9 β right--
left=0(2), right=1(7): sum=9 == 9 β return [1,2] β
LeetCode #15 | Difficulty: Medium | Company: Google, Amazon | Pattern: Both-ends in loop
| Step | Action | Note |
|---|---|---|
| 1 | Sort array | enables two pointers |
| 2 | Outer loop i |
fix first element |
| 3 | Skip nums[i] == nums[i-1] |
avoid outer duplicates |
| 4 | Two pointers L=i+1, R=n-1 |
find pair summing to -nums[i] |
| 5 | On match: skip inner duplicates | avoid duplicate triplets |
public List<List<Integer>> threeSum(int[] nums) {
Arrays.sort(nums);
List<List<Integer>> result = new ArrayList<>();
for (int i = 0; i < nums.length - 2; i++) {
if (i > 0 && nums[i] == nums[i-1]) continue; // skip outer dups
int left = i + 1, right = nums.length - 1;
while (left < right) {
int sum = nums[i] + nums[left] + nums[right];
if (sum == 0) {
result.add(Arrays.asList(nums[i], nums[left], nums[right]));
while (left < right && nums[left] == nums[left+1]) left++;
while (left < right && nums[right] == nums[right-1]) right--;
left++; right--;
} else if (sum < 0) left++;
else right--;
}
}
return result;
}Input: nums = [-1,0,1,2,-1,-4]
Sorted: [-4,-1,-1,0,1,2]
i=0(-4): L=1,R=5 β sum=-4+-1+2=-3<0βL++; -4+0+2=-2<0βL++; -4+1+2=-1<0βL++; L>=R stop
i=1(-1): L=2,R=5 β sum=-1+-1+2=0 β add[-1,-1,2], skip dups, L=3,R=4
L=3,R=4 β -1+0+1=0 β add[-1,0,1], L=4,R=3 stop
i=2(-1): same as i=1, skip (dup)
Output: [[-1,-1,2],[-1,0,1]] β
LeetCode #16 | Difficulty: Medium | Company: Amazon, Adobe | Pattern: Both-ends in loop
public int threeSumClosest(int[] nums, int target) {
Arrays.sort(nums);
int closest = nums[0] + nums[1] + nums[2];
for (int i = 0; i < nums.length - 2; i++) {
int left = i + 1, right = nums.length - 1;
while (left < right) {
int sum = nums[i] + nums[left] + nums[right];
if (Math.abs(sum - target) < Math.abs(closest - target))
closest = sum;
if (sum < target) left++;
else if (sum > target) right--;
else return sum; // exact match
}
}
return closest;
}Input: nums = [-1,2,1,-4], target = 1
Sorted: [-4,-1,1,2]
i=0(-4): L=1,R=3: -4+-1+2=-3βclosest=-3; -4+1+2=-1βclosest=-1
i=1(-1): L=2,R=3: -1+1+2=2βclosest=2 (|2-1|=1 < |-1-1|=2)
Output: 2 β
GFG | Difficulty: Medium | Pattern: Both-ends in loop β count instead of collect
Count triplets with sum < target. When
sum < target, ALL pairs fromlefttoright-1with currentrightalso work β addright - leftto count.
public int tripletWithSmallerSum(int[] nums, int target) {
Arrays.sort(nums);
int count = 0;
for (int i = 0; i < nums.length - 2; i++) {
int left = i + 1, right = nums.length - 1;
while (left < right) {
int sum = nums[i] + nums[left] + nums[right];
if (sum < target) {
count += right - left; // all pairs [left, left+1..right] with i are valid
left++;
} else {
right--;
}
}
}
return count;
}Input: nums = [-1,4,2,1,3], target = 5
Sorted: [-1,1,2,3,4]
i=0(-1): L=1,R=4: -1+1+4=4<5 β count+=3(R-L), L=2
L=2,R=4: -1+2+4=5 not <5 β R=3
L=2,R=3: -1+2+3=4<5 β count+=1, L=3; L>=R stop
i=1(1): L=2,R=4: 1+2+4=7 β₯5βR=3; 1+2+3=6 β₯5βR=2; L>=R stop
Output: count=4 β
LeetCode #18 | Difficulty: Medium | Company: Google | Pattern: Both-ends in double loop
public List<List<Integer>> fourSum(int[] nums, int target) {
Arrays.sort(nums);
List<List<Integer>> result = new ArrayList<>();
int n = nums.length;
for (int i = 0; i < n - 3; i++) {
if (i > 0 && nums[i] == nums[i-1]) continue;
for (int j = i + 1; j < n - 2; j++) {
if (j > i + 1 && nums[j] == nums[j-1]) continue;
int left = j + 1, right = n - 1;
while (left < right) {
long sum = (long)nums[i] + nums[j] + nums[left] + nums[right];
if (sum == target) {
result.add(Arrays.asList(nums[i],nums[j],nums[left],nums[right]));
while (left < right && nums[left] == nums[left+1]) left++;
while (left < right && nums[right] == nums[right-1]) right--;
left++; right--;
} else if (sum < target) left++;
else right--;
}
}
}
return result;
}Input: nums = [1,0,-1,0,-2,2], target = 0
Sorted: [-2,-1,0,0,1,2]
Output: [[-2,-1,1,2],[-2,0,0,2],[-1,0,0,1]] β
LeetCode #125 | Difficulty: Easy | Company: Facebook | Pattern: Both-ends compare
public boolean isPalindrome(String s) {
int left = 0, right = s.length() - 1;
while (left < right) {
// Skip non-alphanumeric
while (left < right && !Character.isLetterOrDigit(s.charAt(left))) left++;
while (left < right && !Character.isLetterOrDigit(s.charAt(right))) right--;
if (Character.toLowerCase(s.charAt(left)) !=
Character.toLowerCase(s.charAt(right))) return false;
left++; right--;
}
return true;
}Input: s = "A man, a plan, a canal: Panama"
After cleaning: "amanaplanacanalpanama"
Both ends meet at center β all chars match β true β
LeetCode #345 | Difficulty: Easy | Company: Google | Pattern: Both-ends conditional swap
public String reverseVowels(String s) {
char[] arr = s.toCharArray();
Set<Character> vowels = Set.of('a','e','i','o','u','A','E','I','O','U');
int left = 0, right = arr.length - 1;
while (left < right) {
while (left < right && !vowels.contains(arr[left])) left++;
while (left < right && !vowels.contains(arr[right])) right--;
if (left < right) {
char temp = arr[left]; arr[left] = arr[right]; arr[right] = temp;
left++; right--;
}
}
return new String(arr);
}Input: s = "hello"
Vowels: e(1), o(4) β swap β "holle" β
LeetCode #977 | Difficulty: Easy | Company: Google, Amazon | Pattern: Both-ends fill from end
Sorted array has negatives on left, positives on right. Largest squares are at both ends. Fill result array from the back.
public int[] sortedSquares(int[] nums) {
int n = nums.length;
int[] result = new int[n];
int left = 0, right = n - 1, idx = n - 1;
while (left <= right) {
int lSq = nums[left] * nums[left];
int rSq = nums[right] * nums[right];
if (lSq >= rSq) {
result[idx--] = lSq;
left++;
} else {
result[idx--] = rSq;
right--;
}
}
return result;
}Input: nums = [-4,-1,0,3,10]
left=0(-4,sq=16), right=4(10,sq=100): 16<100 β result[4]=100, right=3
left=0(-4,sq=16), right=3(3,sq=9): 16>9 β result[3]=16, left=1
left=1(-1,sq=1), right=3(3,sq=9): 1<9 β result[2]=9, right=2
left=1(-1,sq=1), right=2(0,sq=0): 1>0 β result[1]=1, left=2
left=2=right: sq=0 β result[0]=0
Output: [0,1,9,16,100] β
LeetCode #26 | Difficulty: Easy | Company: Amazon, Microsoft | Pattern: Slow/fast
public int removeDuplicates(int[] nums) {
if (nums.length == 0) return 0;
int slow = 1; // slow = next write position (index 0 always kept)
for (int fast = 1; fast < nums.length; fast++) {
if (nums[fast] != nums[fast - 1]) { // new unique element
nums[slow++] = nums[fast];
}
}
return slow; // new length
}Input: nums = [0,0,1,1,1,2,2,3,3,4]
fast=1(0): same as prev β skip
fast=2(1): new β nums[1]=1, slow=2
fast=3(1): same β skip
fast=4(1): same β skip
fast=5(2): new β nums[2]=2, slow=3
...
Output: slow=5, nums=[0,1,2,3,4,...] β
GFG | Difficulty: Easy | Pattern: Slow/fast partition
Move all 0s to front, all 1s to back. In-place O(1) space.
public void rearrange(int[] arr) {
int slow = 0; // next position to place a 0
for (int fast = 0; fast < arr.length; fast++) {
if (arr[fast] == 0) {
int temp = arr[slow]; arr[slow] = arr[fast]; arr[fast] = temp;
slow++;
}
}
}Input: arr = [1,0,1,1,0,1,0]
fast=1(0): swap arr[0]βarr[1] β [0,1,1,1,0,1,0], slow=1
fast=4(0): swap arr[1]βarr[4] β [0,0,1,1,1,1,0], slow=2
fast=6(0): swap arr[2]βarr[6] β [0,0,0,1,1,1,1], slow=3
Output: [0,0,0,1,1,1,1] β
LeetCode #844 | Difficulty: Medium | Company: Google | Pattern: Reverse traversal two pointers
Process both strings from the end, simulating backspace (
#) by skipping characters.
public boolean backspaceCompare(String s, String t) {
int i = s.length() - 1, j = t.length() - 1;
while (i >= 0 || j >= 0) {
i = nextValidChar(s, i);
j = nextValidChar(t, j);
if (i < 0 && j < 0) return true;
if (i < 0 || j < 0) return false;
if (s.charAt(i) != t.charAt(j)) return false;
i--; j--;
}
return true;
}
private int nextValidChar(String str, int idx) {
int backspace = 0;
while (idx >= 0) {
if (str.charAt(idx) == '#') { backspace++; idx--; }
else if (backspace > 0) { backspace--; idx--; }
else break;
}
return idx;
}Input: s = "ab#c", t = "ad#c"
s processed: ab#c β ac
t processed: ad#c β ac
Both are "ac" β return true β
LeetCode #75 | Difficulty: Medium | Company: Microsoft, Amazon | Pattern: 3-pointer partition
nums[mid] |
Action | Why |
|---|---|---|
| 0 | swap(low,mid), low++, mid++ |
0 goes to front |
| 1 | mid++ |
1 is in correct region |
| 2 | swap(mid,high), high-- |
2 goes to back β don't mid++ |
public void sortColors(int[] nums) {
int low = 0, mid = 0, high = nums.length - 1;
while (mid <= high) {
if (nums[mid] == 0) {
swap(nums, low++, mid++);
} else if (nums[mid] == 1) {
mid++;
} else {
swap(nums, mid, high--); // don't mid++ β recheck swapped value
}
}
}
private void swap(int[] nums, int i, int j) {
int t = nums[i]; nums[i] = nums[j]; nums[j] = t;
}Input: nums = [2,0,2,1,1,0]
low=0,mid=0,high=5
mid=0(2): swap(0,5)β[0,0,2,1,1,2], high=4
mid=0(0): swap(0,0)βsame, low=1,mid=1
mid=1(0): swap(1,0)β[0,0,2,1,1,2]... wait idx shifted
After sort: [0,0,1,1,2,2]
Output: [0,0,1,1,2,2] β
LeetCode #88 | Difficulty: Easy | Company: Amazon, Google | Pattern: Two arrays from end
Merge from the END to avoid overwriting unprocessed elements in
nums1.
public void merge(int[] nums1, int m, int[] nums2, int n) {
int i = m - 1, j = n - 1, k = m + n - 1;
while (i >= 0 && j >= 0) {
if (nums1[i] >= nums2[j]) nums1[k--] = nums1[i--];
else nums1[k--] = nums2[j--];
}
// Copy remaining nums2 (nums1's remaining are already in place)
while (j >= 0) nums1[k--] = nums2[j--];
}Input: nums1=[1,2,3,0,0,0] m=3, nums2=[2,5,6] n=3
i=2(3), j=2(6), k=5: 3<6 β nums1[5]=6, j=1
i=2(3), j=1(5), k=4: 3<5 β nums1[4]=5, j=0
i=2(3), j=0(2), k=3: 3>2 β nums1[3]=3, i=1
i=1(2), j=0(2), k=2: 2>=2 β nums1[2]=2, i=0
i=0(1), j=0(2), k=1: 1<2 β nums1[1]=2, j=-1
j<0, i=0(1) remaining β nums1[0]=1
Output: [1,2,2,3,5,6] β
LeetCode #392 | Difficulty: Easy | Company: Google | Pattern: Two arrays
public boolean isSubsequence(String s, String t) {
int i = 0, j = 0;
while (i < s.length() && j < t.length()) {
if (s.charAt(i) == t.charAt(j)) i++; // match: advance s pointer
j++; // always advance t
}
return i == s.length(); // all of s matched
}Input: s = "ace", t = "abcde"
j=0(a): match β i=1; j=1(b): no match; j=2(c): match β i=2; j=3(d): no; j=4(e): match β i=3
i==3==s.length() β return true β
LeetCode #713 | Difficulty: Medium | Company: Amazon | Pattern: Sliding window two pointers
For each
right, shrinkleftuntil product < k. All subarrays ending atrightstarting fromlefttorightare valid β addright - left + 1to count.
public int numSubarrayProductLessThanK(int[] nums, int k) {
if (k <= 1) return 0;
int left = 0, product = 1, count = 0;
for (int right = 0; right < nums.length; right++) {
product *= nums[right];
while (product >= k) product /= nums[left++];
count += right - left + 1; // subarrays ending at right: [left..right],[left+1..right],...,[right]
}
return count;
}Input: nums = [10,5,2,6], k = 100
right=0(10): prod=10<100, count+=1=1
right=1(5): prod=50<100, count+=2=3
right=2(2): prod=100β₯100βdiv10,left=1,prod=10<100, count+=2=5
right=3(6): prod=60<100, count+=3=8
Output: 8 β
LeetCode #581 | Difficulty: Medium | Company: Amazon, Adobe | Pattern: Both-ends scan inward
Find the shortest subarray that, if sorted, makes the whole array sorted.
public int findUnsortedSubarray(int[] nums) {
int n = nums.length;
int low = -1, high = -2; // initialise to make length = 0 when already sorted
int maxSeen = nums[0], minSeen = nums[n-1];
for (int i = 1; i < n; i++) {
maxSeen = Math.max(maxSeen, nums[i]);
if (nums[i] < maxSeen) high = i; // extends the unsorted right boundary
}
for (int i = n-2; i >= 0; i--) {
minSeen = Math.min(minSeen, nums[i]);
if (nums[i] > minSeen) low = i; // extends the unsorted left boundary
}
return high - low + 1;
}Input: nums = [2,6,4,8,10,9,15]
Left scan β maxSeen grows. nums[2]=4 < maxSeen=6 β high=2; nums[5]=9 < maxSeen=10 β high=5
Right scan β minSeen shrinks. nums[3]=8 > minSeen=4 β low=3; nums[1]=6 > minSeen=4 β low=1
Output: high - low + 1 = 5 - 1 + 1 = 5 (subarray [6,4,8,10,9]) β
LeetCode #1332 | Difficulty: Easy | Company: Amazon | Pattern: Both-ends palindrome check
String of only 'a' and 'b'. Remove subsequences (palindromes) to empty the string. Answer is always 1 (whole string is palindrome) or 2 (not palindrome β remove all 'a's then all 'b's).
public int removePalindromeSub(String s) {
int left = 0, right = s.length() - 1;
while (left < right) {
if (s.charAt(left) != s.charAt(right)) return 2; // not palindrome β 2 steps
left++; right--;
}
return 1; // already palindrome β 1 step
}Input: s = "ababa" β palindrome β return 1 β
Input: s = "abb" β not palindrome β return 2 β
LeetCode #151 | Difficulty: Medium | Company: Amazon, Microsoft | Pattern: Both-ends + word reversal
public String reverseWords(String s) {
// Split, trim, reverse the list
String[] words = s.trim().split("\\s+");
int left = 0, right = words.length - 1;
while (left < right) {
String temp = words[left]; words[left] = words[right]; words[right] = temp;
left++; right--;
}
return String.join(" ", words);
}Input: s = " the sky is blue "
Split+trim: ["the","sky","is","blue"]
Reverse: ["blue","is","sky","the"]
Output: "blue is sky the" β
LeetCode #42 | Difficulty: Hard | Company: Amazon, Google, Bloomberg | Pattern: Both-ends with max trackers
Water at index i = min(maxLeft[i], maxRight[i]) - height[i]. Two pointers track maxLeft and maxRight on the fly. Advance the shorter side β its contribution is determined by its own max.
public int trap(int[] height) {
int left = 0, right = height.length - 1;
int maxLeft = 0, maxRight = 0, water = 0;
while (left < right) {
if (height[left] <= height[right]) {
if (height[left] >= maxLeft) maxLeft = height[left];
else water += maxLeft - height[left];
left++;
} else {
if (height[right] >= maxRight) maxRight = height[right];
else water += maxRight - height[right];
right--;
}
}
return water;
}Input: height = [0,1,0,2,1,0,1,3,2,1,2,1]
Output: 6 β
Key: advance shorter side β its max determines water above it.
left side: maxLeft grows as we go right
right side: maxRight grows as we go left
LeetCode #11 | Difficulty: Medium | Company: Amazon, Google | Pattern: Both-ends area maximization
Area = min(height[L], height[R]) * (R - L). To find more water, we must increase the shorter side (increasing the taller side can't help since min is bottlenecked).
public int maxArea(int[] height) {
int left = 0, right = height.length - 1, maxWater = 0;
while (left < right) {
int water = Math.min(height[left], height[right]) * (right - left);
maxWater = Math.max(maxWater, water);
if (height[left] <= height[right]) left++; // move shorter side inward
else right--;
}
return maxWater;
}Input: height = [1,8,6,2,5,4,8,3,7]
left=0(1), right=8(7): area=min(1,7)*8=8, maxWater=8, left++ (shorter)
left=1(8), right=8(7): area=min(8,7)*7=49, maxWater=49, right-- (shorter)
left=1(8), right=7(3): area=min(8,3)*6=18, right--
...
Output: 49 β
| Problem | Category | Key move |
|---|---|---|
| Pair with Target Sum | Both-ends sum | L++ or R-- by sum vs target |
| 3Sum | Both-ends in loop | sort + outer + L/R + skip dups |
| 3Sum Closest | Both-ends in loop | track min abs diff |
| Triplets Smaller Sum | Both-ends count | count += R-L when sum < target |
| 4Sum | Both-ends double loop | sort + 2 outer loops + L/R |
| Valid Palindrome | Both-ends compare | skip non-alphanumeric, compare chars |
| Reverse Vowels | Both-ends swap | skip non-vowels, swap |
| Squares Sorted Array | Both-ends fill end | compare abs values, fill from back |
| Remove Duplicates | Slow/fast | write only on new value |
| Rearrange 0s/1s | Slow/fast swap | swap 0s to front |
| Backspace Compare | Reverse traversal | skip on #, compare valid chars |
| Sort Colors | 3-pointer | 0βswap+low+mid++, 1βmid++, 2βswap+high-- |
| Merge Sorted Array | Two arrays end | compare from end, write to back |
| Is Subsequence | Two arrays | advance both on match, only t otherwise |
| Product < K | Sliding window | count += R-L+1 per position |
| Min Window Sort | Both-ends scan | find unsorted range via max/min sweep |
| Remove Palindromic Sub | Both-ends | palindrome check β 1 or 2 |
| Reverse Words | Split + reverse | split by spaces, reverse array |
| Trapping Rain Water | Both-ends max | advance shorter, accumulate above max |
| Container with Most Water | Both-ends area | move shorter side inward |