Two pointers coding pattern
If input is in sorted order then we can apply two pointers left
and right
to solve few problems. Let’s try few.
1. Two Sum
https://leetcode.com/problems/two-sum/description/
Given an array of integers nums
and an integer target
, return indices of the two numbers such that they add up to target
.
You may assume that each input would have exactly one solution, and you may not use the same element twice.
You can return the answer in any order.
Example 1:
Input: nums = [2,7,11,15], target = 9
Output: [0,1]
Explanation: Because nums[0] + nums[1] == 9, we return [0, 1].
Approach # Use hashmap
We can use hashmap to quick lookup target-num
to find if second number exist or not. Follownig is code for reference.
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
unordered_map<int, int> map;
int n = nums.size();
for (int i = 0; i < n; i++) {
map[nums[i]] = i;
}
for (int i = 0; i < n; i++) {
int remaining = target - nums[i];
if (map.count(remaining) > 0 && map[remaining] != i) {
return {i, map[remaining]};
}
}
return {};
}
};
167. Two Sum II — Input Array Is Sorted
https://leetcode.com/problems/two-sum-ii-input-array-is-sorted/description/
Given a 1-indexed array of integers numbers
that is already sorted in non-decreasing order, find two numbers such that they add up to a specific target
number. Let these two numbers be numbers[index1]
and numbers[index2]
where 1 <= index1 < index2 <= numbers.length
.
Return the indices of the two numbers, index1
and index2
, added by one as an integer array [index1, index2]
of length 2.
The tests are generated such that there is exactly one solution. You may not use the same element twice.
Your solution must use only constant extra space.
Example 1:
Input: numbers = [2,7,11,15], target = 9
Output: [1,2]
Approach
- Take two pointers
left
andright
- If sum of both i.e.
nums[left] + nums[right]=target
then return answer. - If
nums[left] + nums[right] < target
then it meansleft
can’t be contribute to build the closesttarget
, we need to try nextleft+1
- Similarly,
nums[left] + nums[right] > target
then it means we can let got currentright
Following is code for reference.
class Solution {
public:
vector<int> twoSum(vector<int>& numbers, int target) {
int n = numbers.size();
int left = 0, right = n - 1;
while (left < right) {
int sum = numbers[left] + numbers[right];
if (sum == target) {
return {left+1, right+1}; // match 1 base index
} else if (sum < target) {
left++; // need to add bigger number to get closer to target
} else {
right--; // need to drop bigger number to get closer to target
}
}
return {};
}
};
15. 3Sum
https://leetcode.com/problems/3sum/description/
Given an integer array nums, return all the triplets [nums[i], nums[j], nums[k]]
such that i != j
, i != k
, and j != k
, and nums[i] + nums[j] + nums[k] == 0
.
Notice that the solution set must not contain duplicate triplets.
Example 1:
Input: nums = [-1,0,1,2,-1,-4]
Output: [[-1,-1,2],[-1,0,1]]
Approach
a+b+c=0
i.e.a+b=c
it means if we fixc
then problem is reduced to two sum problem.- Since order doesn’t matter therefore we can first sort the array then apply two sum problem.
- Every time we scan new num, skip older same number already processed.
Following is code for reference.
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> ans;
int n = nums.size();
sort(nums.begin(), nums.end());
for (int i = 0; i < n - 2; i++) {
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
int target = -nums[i];
int left = i + 1;
int right = n - 1;
while (left < right) {
if (left > i + 1 && nums[left] == nums[left - 1]) {
left++;
continue;
}
if (right < n - 1 && nums[right] == nums[right + 1]) {
right--;
continue;
}
int sum = nums[left] + nums[right];
if (sum == target) {
ans.push_back({nums[i], nums[left], nums[right]});
left++;
right--;
} else if (sum < target) {
left++;
} else {
right--;
}
}
}
return ans;
}
};
18. 4Sum
https://leetcode.com/problems/4sum/
Given an array nums
of n
integers, return an array of all the unique quadruplets [nums[a], nums[b], nums[c], nums[d]]
such that:
0 <= a, b, c, d < n
a
,b
,c
, andd
are distinct.nums[a] + nums[b] + nums[c] + nums[d] == target
You may return the answer in any order.
Example 1:
Input: nums = [1,0,-1,0,-2,2], target = 0
Output: [[-2,-1,1,2],[-2,0,0,2],[-1,0,0,1]]
Approach
- We can design a generic
k
level target sum problem. - First we will sort the given input
nums
- We will apply decrease and conquer approach.
- I.e. first we will apply outer loop to find out kth, k-1th….till 3rd.
- Then at the end will apply two sum
Following is code for reference.
class Solution {
private:
// Find a & b for given target on sorted array
vector<vector<int>> twoSum(vector<int>& nums, long target, int start) {
vector<vector<int>> ans;
int n = nums.size();
int left = start;
int right = n - 1;
while (left < right) {
if (left > start && nums[left] == nums[left - 1]) {
left++;
continue;
}
if (right < n - 1 && nums[right] == nums[right + 1]) {
right--;
continue;
}
long sum = nums[left] + nums[right];
if (sum == target) {
ans.push_back({nums[left], nums[right]});
left++;
right--;
} else if (sum < target) {
left++;
} else {
right--;
}
}
return ans;
}
vector<vector<int>> kSum(vector<int>& nums, long target, int k, int start) {
// Base case for two level sum
if (k == 2) {
return twoSum(nums, target, start);
}
vector<vector<int>> ans;
int n = nums.size();
for (int i = start; i < n; i++) {
// skip duplicate
if (i > start && nums[i] == nums[i - 1]) {
continue;
}
auto local = kSum(nums, target - nums[i], k - 1, i + 1);
// As a manager, take output from worker and solve it
for (auto tuple : local) {
tuple.push_back(nums[i]);
ans.push_back(tuple);
}
}
return ans;
}
public:
vector<vector<int>> fourSum(vector<int>& nums, int target) {
sort(nums.begin(), nums.end()); // Sort array
return kSum(nums, target, 4, 0);
}
};
1099. Two Sum Less Than K
https://leetcode.com/problems/two-sum-less-than-k/description/
Given an array nums
of integers and integer k
, return the maximum sum
such that there exists i < j
with nums[i] + nums[j] = sum
and sum < k
. If no i
, j
exist satisfying this equation, return -1
.
Example 1:
Input: nums = [34,23,1,24,75,33,54,8], k = 60
Output: 58
Explanation: We can use 34 and 24 to sum 58 which is less than 60.
Approach #1: Use hashmap (not possible)
Since our sum can be any value smaller than the target, we cannot use a hashmap.
Approach #2: Use two pointers approach
To apply the two pointers approach, we will first need to sort it. Following is code for reference.
class Solution {
public:
int twoSumLessThanK(vector<int>& nums, int k) {
int n = nums.size();
sort(nums.begin(), nums.end());
int left = 0, right = n - 1;
int ans = INT_MIN;
while (left < right) {
int sum = nums[left] + nums[right];
if (sum >= k) {
// move to left
right--;
} else {
// move to right
left++;
// This is ans zone, capture it.
ans = max(ans, sum);
}
}
return ans == INT_MIN ? -1 : ans;
}
};
Q. But hold on, doesn’t sorting break the problem statement of i<j
?
Ans. No. Let’s take example of [34,23,24] with k=60
. Here answer is 34+24=58
with i=0
and j=2
. If we sort then we get [23,24,34]
with the original index mapping of [1,2,0]
but above code will give answer with {24+34}
i.e. compare to original index as i=2
and j=0
so we can say that hey i<j
is not satisfied so answer is wrong. But the important concept is, addition is Commutative i.e. a+b==b+a
therefore when we got answer for i=2 and j=0
therefore to satisfy i<j
we can simply swap the orer i.e. i=0 and j=2
.
It means, i<j
condition is irrelevant and distraction here due to the commutative properties of addition.
If problem is changed to find out the subtraction of i & j
with i<j
then we can’t apply sorting.
2 Sum Smaller
Given a nums array, find the number of index pairs i, j with 0≤i<j<n that satisfy the condition nums[i]+nums[j]<target.
Approach 1 # Use hashmap (not possible)
Approach #2 Use two pointers
First sort the nums array. For example, nums=[3,5,2,8,1] becomes [1,2,3,5,8].
Let us look at an example nums=[1,2,3,5,8], and target=7.
[1, 2, 3, 5, 8]
↑ ↑
left right
Let us initialize two indices, left and right pointing to the first and last element respectively.
When we look at the sum of first and last element, it is 1+8=9, which is ≥target. That tells us no index pair will ever contain the index right.
So the next logical step is to move the right pointer one step to its left.
[1, 2, 3, 5, 8]
↑ ↑
left right
Now the pair sum is 1+5=6, which is less than target.
Q. How many pairs with one of the index=left that satisfy the condition?
You can tell by the difference between right and left which is 3, namely (1,2),(1,3), and (1,5). Therefore, we move left one step to its right.
Following is code for reference.
int twoSumSmaller(vector<int>& nums, int target) {
sort(nums.begin(), nums.end());
int n = nums.size();
int left=0, right = n-1;
int count =0;
while(left < right) {
int sum = nums[left] + nums[right];
if(sum >= target) {
// move right
right--;
} else {
count += right - left;
left++;
}
}
return count;
}
259. 3Sum Smaller
https://leetcode.com/problems/3sum-smaller/
Given an array of n
integers nums
and an integer target
, find the number of index triplets i
, j
, k
with 0 <= i < j < k < n
that satisfy the condition nums[i] + nums[j] + nums[k] < target
.
Example 1:
Input: nums = [-2,0,1,3], target = 2
Output: 2
Explanation: Because there are two triplets which sums are less than 2:
[-2,0,1]
[-2,0,3]
Approach # Extend two sum smaller logic
We can use the two sum smaller logic to solve this problem as below.
class Solution {
public:
int threeSumSmaller(vector<int>& nums, int target) {
sort(nums.begin(), nums.end());
int n = nums.size();
int ans = 0;
for (int i = 0; i < n-2; i++) {
int c = nums[i];
int rest = target - c;
// Run two sum
int left = i + 1, right = n - 1;
while (left < right) {
int sum = nums[left] + nums[right];
if (sum < rest) {
// Ans zone
ans += (right - left);
left++;
} else {
right--;
}
}
}
return ans;
}
};
2 Sum Closest
Given an integer array nums
of length n
and an integer target
, find two integers in nums
such that the sum is closest to target
.
Example 1:
Input: nums = [-1,2,1,-4], target = 1
Output: 0
Explanation: The sum that is closest to the target is 0. (-1 + 1 = 0).
Approach# Two pointer approach
- We first need to sort
nums
to apply two pointers approach - Now use
left
andright
as two pointers to explorea=nums[left]
andb=nums[right]
- We have
sum = a + b
- Let’s find the
diff = abs(sum — target)
- Let’s keep the global
distance=Inf
- If
diff < distance
then update thediff
- If
sum < target
then explore right - Otherwise explore left
- Return
target — diff
as an answer
Following is code for reference.
class Solution {
public:
int threeSumClosest(vector<int>& nums, int target) {
sort(nums.begin(), nums.end());
int n = nums.size();
int diff = INT_MAX;
int ans = 0;
int left = 0, right = n - 1;
while (left < right) {
int sum = nums[left] + nums[right];
// zone to updat the diff
if (abs(target - sum) < diff) {
diff = abs(target - sum);
}
if (sum < target) {
left++;
} else {
right--;
}
}
return ans;
}
};
16. 3Sum Closest
https://leetcode.com/problems/3sum-closest/description
Given an integer array nums
of length n
and an integer target
, find three integers in nums
such that the sum is closest to target
.
Return the sum of the three integers.
You may assume that each input would have exactly one solution.
Example 1:
Input: nums = [-1,2,1,-4], target = 1
Output: 2
Explanation: The sum that is closest to the target is 2. (-1 + 2 + 1 = 2).
Approach: Use two pointer approach
- We first need to sort
nums
to apply two pointers approach - First iterate
nums
to explorec=nums[i]
- Now use
left
andright
as two pointers to explorea=nums[left]
andb=nums[right]
- We have
sum = a + b + c
- Let’s find the
diff = abs(sum — target)
- Let’s keep the global
distance=Inf
andans
variable - If
diff < distance
then update thediff
andans
- If
sum < target
then explore right - Otherwise explore left
Following is code for reference.
class Solution {
public:
int threeSumClosest(vector<int>& nums, int target) {
sort(nums.begin(), nums.end());
int n = nums.size();
int diff = INT_MAX;
int ans = 0;
for (int i = 0; i < n; i++) {
int c = nums[i];
int rest = target - c;
// 2 sum closest
int left = i + 1, right = n - 1;
while (left < right) {
int sum = nums[left] + nums[right];
// zone to updat the diff
if (abs(rest - sum) < diff) {
diff = abs(rest - sum);
ans = sum + c; // update ans
}
if (sum < rest) {
left++;
} else {
right--;
}
}
}
return ans;
}
};
658. Find K Closest Elements
https://leetcode.com/problems/find-k-closest-elements/description
Given a sorted integer array arr
, two integers k
and x
, return the k
closest integers to x
in the array. The result should also be sorted in ascending order.
An integer a
is closer to x
than an integer b
if:
|a - x| < |b - x|
, or|a - x| == |b - x|
anda < b
Example 1:
Input: arr = [1,2,3,4,5], k = 4, x = 3
Output: [1,2,3,4]
Approach #1: Apply custom sorting
We can apply custom sorting followed by slicing the first k numbers. Following is code for reference.
class Solution {
public:
vector<int> findClosestElements(vector<int>& arr, int k, int x) {
auto compare = [x](int a, int b) {
if ((abs(a - x) == abs(b - x) && a < b) ||
abs(a - x) < abs(b - x)) {
return true;
}
return false;
};
sort(arr.begin(), arr.end(), compare);
vector<int> ans(k);
copy(arr.begin(), arr.begin() + k, ans.begin());
sort(ans.begin(), ans.end());
return ans;
}
};
Time complexity: O(nlogn)
Approach#2: Binary Search + Two pointers
- Use binary search to locate the
x
in the array. - Logically, the second closest number to x must be directly beside the first number, either to the left or right.
- Then, the third closest number to x must be either to the left of the first number or to the right of the second number.
- This pattern continues, and is true because the input is sorted.
- Using two pointers, we can maintain a sliding window that will expand to contain the
k
closest elements tox
- We should expand our window by moving the pointers either left or right depending on which number is closer to
x
.
Following is code for reference.
/**
* @param {number[]} arr
* @param {number} k
* @param {number} x
* @return {number[]}
*/
var findClosestElements = function (arr, k, x) {
const n = arr.length;
// Base case
if (n === k) {
return arr;
}
// Binary search to find the closest element to left
let left = 0;
let right = n - 1;
while (left <= right) {
const mid = left + Math.floor((right - left) / 2);
if (arr[mid] >= x) {
// Move left
right = mid - 1;
} else {
// move right
left = mid + 1;
}
}
// Apply two pointers
let start = left - 1;
let end = left;
while (end - start - 1 < k) {
// Handle out of bound
if (start === -1) {
end++;
continue;
}
if (end === n) {
start--;
continue;
}
// Expand the window towards the side with the closer number
if (Math.abs(arr[start] - x) <= Math.abs(arr[end] - x)) {
start--;
} else {
end++;
}
}
// return the window
return arr.slice(start + 1, end);
};
42. Trapping Rain Water
https://leetcode.com/problems/trapping-rain-water/description
Given n
non-negative integers representing an elevation map where the width of each bar is 1
, compute how much water it can trap after raining.
Example 1:
Input: height = [0,1,0,2,1,0,1,3,2,1,2,1]
Output: 6
Explanation: The above elevation map (black section) is represented by array [0,1,0,2,1,0,1,3,2,1,2,1]. In this case, 6 units of rain water (blue section) are being trapped.
Approach #1 (TLE): Min of max bar on left and right scanning of each elevation
A elevation can trap the water as per below.
f(i) = min (
max(0,...i-1), // max height seen in left
max(i+1,....n-1) // max height seen on right
) - height[i]
Following is code for reference.
/**
* @param {number[]} height
* @return {number}
*/
var trap = function (height) {
const n = height.length;
let ans = 0;
for (let i = 1; i < n - 1; i++) {
// find out max on left
let leftMax = 0;
for (let left = 0; left < i; left++) {
leftMax = Math.max(leftMax, height[left]);
}
// Find out max on right
let rightMax = 0;
for (let right = i + 1; right < n; right++) {
rightMax = Math.max(rightMax, height[right]);
}
const water = Math.min(leftMax, rightMax) - height[i];
if (water > 0) {
ans += water;
}
}
return ans;
};
Approach #2: Precalculate the left and right max for each elevation
Instead of calculating left max and right max every time, we can pre-calculate and reuse it.
Following is code for reference.
/**
* @param {number[]} height
* @return {number}
*/
var trap = function (height) {
const n = height.length;
let ans = 0;
const leftMax = new Array(n).fill(0);
const rightMax = new Array(n).fill(0);
// Build left max
for (let i = 1; i < n; i++) {
leftMax[i] = Math.max(leftMax[i - 1], height[i - 1])
}
// Build right max
for (let i = n - 2; i >= 0; i--) {
rightMax[i] = Math.max(rightMax[i + 1], height[i + 1]);
}
for (let i = 1; i < n - 1; i++) {
const water = Math.min(leftMax[i], rightMax[i]) - height[i];
if (water > 0) {
ans += water;
}
}
return ans;
};
Approach#3: Use two pointers to update max left/right at runtime
Instead of pre-calculate, we can also use the two pointers to keep track of max seen in left and right so far. Following are key points.
- As long as
right_max[i] > left_max[i]
, the water trapped depends upon the left max. - Similarly when
left_max[i] > right_max[i]
, the water trapped depends on right max. - If there is a larger bar at one end (say right), we are assured that the water trapped would be depends on height of bar in current direction.
- As soon as we find that bar at the other end (right) is smaller, we start iterating in opposite direction.
Following is code for reference.
class Solution {
public:
int trap(vector<int>& height) {
int n = height.size();
int left = 0, right = n - 1;
int ans = 0;
int leftmax = 0, rightmax = 0;
while (left < right) {
if (height[left] < height[right]) {
leftmax = max(leftmax, height[left]);
ans += (leftmax - height[left]);
left++;
} else {
rightmax = max(rightmax, height[right]);
ans += (rightmax - height[right]);
right--;
}
}
return ans;
}
};
407. Trapping Rain Water II
https://leetcode.com/problems/trapping-rain-water-ii/description/
TBD
11. Container With Most Water
https://leetcode.com/problems/container-with-most-water/description/
You are given an integer array height
of length n
. There are n
vertical lines drawn such that the two endpoints of the ith
line are (i, 0)
and (i, height[i])
.
Find two lines that together with the x-axis form a container, such that the container contains the most water.
Return the maximum amount of water a container can store.
Notice that you may not slant the container.
Example 1:
Input: height = [1,8,6,2,5,4,8,3,7]
Output: 49
Explanation: The above vertical lines are represented by array [1,8,6,2,5,4,8,3,7]. In this case, the max area of water (blue section) the container can contain is 49.
Approach: Two pointers
Following is code for reference.
class Solution {
public:
int maxArea(vector<int>& height) {
int n = height.size();
int left = 0, right = n - 1;
int ans = 0;
while (left < right) {
int l = height[left];
int r = height[right];
int h = min(l, r);
ans = max(ans, h * (right - left));
if (l < r) {
left++;
} else {
right--;
}
}
return ans;
}
};
Happy coding :-)