最近在刷leetcode,在Array分类中经常遇到“给定一个TargetNumber和一个Array,在Array中找出n个数,这n个数之和等于TargetNumber”的问题。随着n的值增大,题目的难度也会增大。当n取一个具体值时,该题目就会变成其他题目的变种。比如当n = 1时,就变成了简单的一维数组查找问题。本文通过对比n取不同值时的解题思路,抽象出一种通用解法。
注:本文聚焦算法本身的思想,解法中不考虑重复解问题。
1)n = 1时。此时就是典型的一维数组查找问题。推荐的解法是“排序+二分查找”。此处就不上代码了。
2)n = 2时。思路:
(1)按升序排序;
(2)ptr1和ptr2表示待搜索的2个位置,初始时将ptr1指向数组头,将ptr2指向数组尾;
(3)开始搜索,若指针所指的2数之和大于target number时,ptr2向队首移动一位,若指针所指的2数之和小于targetnumber时,ptr1向队尾移动一位,当2数之和与targetnumber相等时,2个指针所指的数即为结果。
Sample code:
vector<vector<int>> FindTwoSum(vector<int>& nums, int target)
{
vector<vector<int>> res;
int n = nums .size();
if(2 > n) return res;
sort(nums.begin(), nums.end());
int left = 0, right = nums.size() - 1;
while(left < right)
{
int sum = nums[left] + nums[right];
if(sum == target)
{
vector<int> mem = {nums[left], nums[right]};
res.push_back(mem);
++left;
}
else if(sum > target) --right;
else ++left;
}
return res;
}
3)n = 3 时。
思路:
(1) 首先依然对数组进行排序;
(2) 定义三个指针ptr1,ptr2, ptr3指向3个位置。初始化ptr1指向队首,并固定ptr1的位置,对ptr2和ptr3使用类似TwoSum的策略进行搜索;为了提升搜索运行效率,可以在搜索前通过边界判断,搜索搜索范围;
(3) 向后移动ptr1重复步骤2,所有位置均遍历后结束;
Sample code:
vector<vector<int>> findThreeSum(vector<int> nums, int target)
{
vector<vector<int>> res;
int n = nums.size();
if(3 > n) return res;
sort(nums.begin(), nums.end());
for(int ptr1 = 0; ptr1 < n - 2; ++ptr1)
{
// we need cut branches at first.
if(nums[ptr1] + nums[ptr1 + 1], nums[ptr1 + 2] > target) break;
if(nums[ptr1] + nums[n - 1] + nums[n - 2] < target) continue;
int ptr2 = ptr1 + 1, ptr3 = n - 1;
while(ptr2 < ptr3)
{
int sum = nums[ptr1] + nums[ptr2] + nums[ptr3];
if(sum == target)
{
vector<int> mem = {nums[ptr1], nums[ptr2], nums[ptr3]};
res.push_back(mem);
++ptr2; --ptr3;
}
else if(sum > target) ++ptr2;
else --ptr3;
}
}
return res;
}
4)n = 4时。
思路:
(1) 依然是对数组进行排序;
(2) 定义4个指针ptr1,ptr2,ptr3, ptr4,采用的策略和上文中的策略相似。先确定ptr1的位置,然后采用ThreeSum的策略进行搜素。
Sample code:
vector<vector<int>> findFourSum(vector<int> nums, int target)
{
vector<vector<int>> res;
int n = nums.size();
if(4 > n) return res;
sort(nums.begin(), nums.end());
for(int ptr1 = 0; ptr1 < n - 3; ++ptr1)
{
//cut branches first.
if(nums[ptr1] + nums[ptr1 + 1] + nums[ptr1 + 2] + nums[ptr1 + 3] > target) break;
if(nums[ptr1] + nums[n - 1] + nums[n - 2] + nums[n - 3] < target) continue;
for(int ptr2 = ptr1; ptr2 < n - 2; ++ptr2)
{
//cut branches second.
if(nums[ptr1] + nums[ptr2] + nums[ptr2 + 1] + nums[ptr2 + 2] > target) break;
if(nums[ptr1] + nums[ptr2] + nums[n - 1] + nums[n - 2] < target) continue;
int ptr3 = ptr2 + 1, ptr4 = n - 1;
while(ptr3 < ptr4)
{
int sum = nums[ptr1] + nums[ptr2] + nums[ptr3] + nums[ptr4];
if(sum == target)
{
vector<int> mem = {nums[ptr1], nums[ptr2], nums[ptr3], nums[ptr4]};
res.push_back(mem);
++ptr3; --ptr4;
}
else if(sum < target) ++ptr3;
else --ptr4;
}
}
}
return res;
}
总结上述解法,不难发现。除了n=1这种场景的解法把不同外,其他情况的策略都大同小异。因此当n很大时,可以考虑使用recursion+ backtracking来解。
Sample code:
void helper(vector<int>& nums, int target, int start_index, int N, vector<int>& mem, vector<vector<int>>& res)
{
if(N == 1)
{
// The implementation of binarySearch is omitted.
binarySearch(nums, target, start_index, nums.size() - 1, mem, res);
return;
}
if(N == 2)
{
// adopted the TwoSum strategy.
int ptr1 = start_index, ptr2 = nums.size() - 1;
while(ptr1 < ptr2)
{
int sum = nums[ptr1] + nums[ptr2];
if(sum == target)
{
mem.push_back(nums[ptr1]);
mem.push_back(nums[ptr2]);
res.push_back(mem);
mem.pop_back(); mem.pop_back();
++ptr1; --ptr2;
}
else if(sum > target) ++ptr1;
else --ptr2;
}
return;
}
for(int ptr = start_index; ptr < nums.size() - N + 1; ++ptr)
{
//cut branches.
int sum;
for(int i = ptr, sum = 0; i < ptr + N; ++i)
sum += nums[i];
if(sum > target) break;
for(int i = n -1, sum = nums[ptr]; i > n - N; --i)
sum += nums[i];
if(sum < target) continue;
mem.push_back(nums[ptr]);
helper(nums, target - nums[ptr], ptr + 1, N - 1, mem, res);
mem.pop_back();
}
}
vector<vector<int>> findNSum(vector<int>&nums, int target, int N)
{
vector<vector<int>> res;
if(0 >= N) return res;
int n = nums.size();
if(N > n) return res;
sort(nums.begin(), nums.end());
vector<int> mem;
helper(nums, target, 0, N, mem, res);
return res;
}