Regular readers of LeetCode will no doubt be aware of the famous twoSum problem, the twoSum problem that was at the heart of our previous article, which analyzed several variations of twoSum.
But in addition to the twoSum problem, LeetCode also has the 3Sum problem, the 4Sum problem, so I’m guessing we’ll have a 5Sum problem in the future, and 6Sum is not out of the question.
So, is there a good way to solve this problem with routines? In this paper, from shallow to deep, layer by layer, with a function to solve all types of nSum problems.
TwoSum question
In the last article, I wrote the twoSum problem on the forcingtwosum. The title required the return of the index. Here I will make up a twoSum problem:
If you take an array of nums and a target and return the values of the two elements in nums that make up the target, for example, nums = [1,3,5,6], target = 9, then the algorithm returns two elements [3,6]. We can assume that only one and only one pair of elements can make target.
We can sort nums first, and then use the left and right two-pointer technique we wrote earlier, from both ends of the line:
vector<int> twoSum(vector<int>& nums, int target) {
// Sort the array first
sort(nums.begin(), nums.end());
// Left and right Pointers
int lo = 0, hi = nums.size() - 1;
while (lo < hi) {
int sum = nums[lo] + nums[hi];
// Move the left pointer based on the comparison between sum and target
if (sum < target) {
lo++;
} else if (sum > target) {
hi--;
} else if (sum == target) {
return{nums[lo], nums[hi]}; }}return {};
}
Copy the code
That will solve the problem, but let’s go ahead and make it a little more general, a little more difficult:
Nums may have multiple pairs of elements whose sum is equal to target. Your algorithm returns all pairs of elements whose sum is target.
The function signature is as follows:
vector<vector<int>> twoSumTarget(vector<int>& nums, int target);
Copy the code
For example, if nums = [1,3,1,2,2,3] and target = 4, the algorithm will return the result: [[1,3],[2,2]].
For the modified problem, the key difficulty is that there may be multiple pairs of and as target, which cannot be repeated. For example, [1,3] and [3,1] in the above example can be repeated only once.
First, the basic idea is definitely still sort plus double pointer:
vector<vector<int>> twoSumTarget(vector<int>& nums, int target {
// Sort the array first
sort(nums.begin(), nums.end());
vector<vector<int>> res;
int lo = 0, hi = nums.size() - 1;
while (lo < hi) {
int sum = nums[lo] + nums[hi];
// Move the left pointer based on the comparison between sum and target
if (sum < target) lo++;
else if (sum > target) hi--;
else {
res.push_back({lo, hi}); lo++; hi--; }}return res;
}
Copy the code
However, this implementation would duplicate the result, such as nums = [1,1,1,2,2,3,3], target = 4, and the result [1,3] would definitely duplicate.
The problem is the if branch of the target condition sum ==. When adding the res once, lo and hi should not change by 1 and should also skip all repeating elements:
So, we can make the following changes to the two-pointer while loop:
while (lo < hi) {
int sum = nums[lo] + nums[hi];
// Record the initial values of indexes LO and hi
int left = nums[lo], right = nums[hi];
if (sum < target) lo++;
else if (sum > target) hi--;
else {
res.push_back({left, right});
// Skip all repeating elements
while (lo < hi && nums[lo] == left) lo++;
while(lo < hi && nums[hi] == right) hi--; }}Copy the code
This ensures that an answer is added only once, and that duplicate results are skipped to get the correct answer. However, inspired by this idea, the first two if branches can also do a little efficiency optimization, skipping the same elements:
vector<vector<int>> twoSumTarget(vector<int>& nums, int target) {
// The numS array must be ordered
sort(nums.begin(), nums.end());
int lo = 0, hi = nums.size() - 1;
vector<vector<int>> res;
while (lo < hi) {
int sum = nums[lo] + nums[hi];
int left = nums[lo], right = nums[hi];
if (sum < target) {
while (lo < hi && nums[lo] == left) lo++;
} else if (sum > target) {
while (lo < hi && nums[hi] == right) hi--;
} else {
res.push_back({left, right});
while (lo < hi && nums[lo] == left) lo++;
while(lo < hi && nums[hi] == right) hi--; }}return res;
}
Copy the code
So we have a general twoSum function, make sure you understand the logic of this algorithm, and we’ll reuse this function when we solve for 3Sum and 4Sum.
The time complexity of this function is very easy to see, even though there are so many while loops in the two-pointer part, the time complexity is O(NlogN), and the time complexity of sorting is O(NlogN), so this function is O(NlogN).
PS: I have carefully written more than 100 original articles and brushed 200 force button topics hand by hand, all of which were published in labuladong’s algorithm cheat sheet and updated continuously. Suggested collection, in accordance with the order of my article brush topic, master all kinds of algorithm set to re-enter the sea of questions like a duck in water.
2. The 3Sum problem
This is the sum of three numbers in question 15 of li Kou:
Return all possible triples of the three elements neutralized by nums.
vector<vector<int>> threeSum(vector<int>& nums);
Copy the code
So, let’s generalize the problem again. Instead of just summing the triples of 0, let’s count the triples of target, like the twoSum above, and not allow duplicate results:
vector<vector<int>> threeSum(vector<int>& nums) {
// A triplet summing 0
return threeSumTarget(nums, 0);
}
vector<vector<int>> threeSumTarget(vector<int>& nums, int target) {
// Enter the array nums to return all triples with sum as target
}
Copy the code
How to solve this problem? It’s easy. I’ll do it all. Now we want to find three numbers that sum is target, so for the first number, what might it be? Every element in nums[I] is possible!
So, once you’ve determined the first number, what can the other two numbers be? For target-nums [I], the twoSum function solves the problem 🤔
We can write the code directly, but we need to modify the twoSum function slightly to reuse it:
/* From nums[start], calculate all binary groups in nums and target */
vector<vector<int>> twoSumTarget(
vector<int>& nums, int start, int target) {
// Change the left pointer to start, otherwise unchanged
int lo = start, hi = nums.size() - 1;
vector<vector<int>> res;
while (lo < hi) {
...
}
return res;
}
/* Computes all triples in the array nums whose sum is target */
vector<vector<int>> threeSumTarget(vector<int>& nums, int target) {
// The array must be sorted
sort(nums.begin(), nums.end());
int n = nums.size(a); vector<vector<int>> res;
// enumerate the first number of threeSum
for (int i = 0; i < n; i++) {
// Calculate twoSum for target-nums [I]
vector<vector<int>>
tuples = twoSumTarget(nums, i + 1, target - nums[i]);
// If there is a binary that satisfies the condition, nums[I] is the resulting triplet
for (vector<int>& tuple : tuples) {
tuple.push_back(nums[i]);
res.push_back(tuple);
}
// Skip the case where the first number is repeated, otherwise the result is repeated
while (i < n - 1 && nums[i] == nums[i + 1]) i++;
}
return res;
}
Copy the code
For example, nums = [1,1,1,2,3] and target = 6.
The key is that you don’t want the first number to be repeated, and for the next two numbers, our twoSum function that we reuse ensures that they are not repeated. So the code must use a while loop to ensure that the first element in 3Sum is not repeated.
At this point, the 3Sum problem is solved, the time complexity is easy to calculate, the sorting complexity is O(NlogN), the double pointer operation in the twoSumTarget function is O(N), The threeSumTarget function calls twoSumTarget in the for loop so the total time complexity is O(NlogN + N^2) = O(N^2).
PS: I have carefully written more than 100 original articles and brushed 200 force button topics hand by hand, all of which were published in labuladong’s algorithm cheat sheet and updated continuously. Suggested collection, in accordance with the order of my article brush topic, master all kinds of algorithm set to re-enter the sea of questions like a duck in water.
The 4Sum problem
This is the sum of four numbers in question 18 of Li Kou:
The function signature is as follows:
vector<vector<int>> fourSum(vector<int>& nums, int target);
Copy the code
At this point, 4Sum can be used exactly the same way: enumerating the first number, then calling 3Sum to calculate the remaining three numbers, and finally combining the sum as the target quad.
vector<vector<int>> fourSum(vector<int>& nums, int target) {
// The array needs to be sorted
sort(nums.begin(), nums.end());
int n = nums.size(a); vector<vector<int>> res;
// enumerate the first term of fourSum
for (int i = 0; i < n; i++) {
// Calculate threeSum for target-nums [I]
vector<vector<int>>
triples = threeSumTarget(nums, i + 1, target - nums[i]);
// If there are any triples that satisfy the condition, nums[I] is the resulting quad
for (vector<int>& triple : triples) {
triple.push_back(nums[i]);
res.push_back(triple);
}
// The first term of fourSum cannot be repeated
while (i < n - 1 && nums[i] == nums[i + 1]) i++;
}
return res;
}
/* From nums[start], count all triples in nums with sum as target */
vector<vector<int>>
threeSumTarget(vector<int>& nums, int start, int target) {
int n = nums.size(a); vector<vector<int>> res;
// I start from start, everything else is the same
for (int i = start; i < n; i++) {
...
}
return res;
Copy the code
The for loop calls threeSumTarget, so the total time is O(N^3).
å››, 100Sum problem?
In LeetCode, 4Sum ends there, but if you think back to writing 3Sum and 4Sum, it actually follows the same pattern. I’m sure you can reuse and solve the 5Sum problem by modifying the 4Sum function a little bit, and then solve the 6Sum problem…
So, what if I asked you to solve the 100Sum problem? In fact, we can observe the above solutions and unify the nSum function:
/* Note: nums must be sorted before calling this function */
vector<vector<int>> nSumTarget(
vector<int>& nums, int n, int start, int target) {
int sz = nums.size(a); vector<vector<int>> res;
// At least 2Sum, and the array size should not be smaller than n
if (n < 2 || sz < n) return res;
// 2Sum is the base case
if (n == 2) {
// Double pointer operation
int lo = start, hi = sz - 1;
while (lo < hi) {
int sum = nums[lo] + nums[hi];
int left = nums[lo], right = nums[hi];
if (sum < target) {
while (lo < hi && nums[lo] == left) lo++;
} else if (sum > target) {
while (lo < hi && nums[hi] == right) hi--;
} else {
res.push_back({left, right});
while (lo < hi && nums[lo] == left) lo++;
while(lo < hi && nums[hi] == right) hi--; }}}else {
// calculate (n-1)Sum recursively when n > 2
for (int i = start; i < sz; i++) {
vector<vector<int>>
sub = nSumTarget(nums, n - 1, i + 1, target - nums[i]);
for (vector<int>& arr : sub) {
// (n-1)Sum plus nums[I] is nSum
arr.push_back(nums[i]);
res.push_back(arr);
}
while (i < sz - 1 && nums[i] == nums[i + 1]) i++; }}return res;
}
Copy the code
Well, it looks very long, but it’s actually a combination of the twoSum solutions, the two-pointer solution for n == 2, and the recursive call for n > 2 to compute the (n-1)Sum and assemble the answer.
It is important to sort the numS array before calling nSum, because nSum is a recursive function. If the sorting function is called in nSum, the recursive sort will be unnecessary and will be inefficient.
For example, let’s write the 4Sum problem in LeetCode:
vector<vector<int>> fourSum(vector<int>& nums, int target) {
sort(nums.begin(), nums.end());
// n is 4, counting from nums[0] and is a quad for target
return nSumTarget(nums, 4.0, target);
}
Copy the code
Or LeetCode’s 3Sum problem, find the triplet target == 0:
vector<vector<int>> threeSum(vector<int>& nums) {
sort(nums.begin(), nums.end());
// n = 3, starting with nums[0] and summing triples of 0
return nSumTarget(nums, 3.0.0);
}
Copy the code
So, if you want to calculate the 100Sum problem, you just call this function and you’re done.
_____________
Click on my home page for more quality articles.