C++三数之和
C++实现三数之和
1、三数之和
1.1、三数之和为0
题目链接
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。
注意:答案中不可以包含重复的三元组。
示例:
给定数组 nums = [-1, 0, 1, 2, -1, -4],
满足要求的三元组集合为:
[
[-1, 0, 1],
[-1, -1, 2]
]
1.2、思路
「不重复」的本质是什么?我们保持三重循环的大框架不变,只需要保证:
- 第二重循环枚举到的元素不小于当前第一重循环枚举到的元素;
- 第三重循环枚举到的元素不小于当前第二重循环枚举到的元素。
也就是说,我们枚举的三元组 (a, b, c)满足 a≤b≤c
,保证了只有 (a, b, c)这个顺序会被枚举到,而 (b, a, c)、(c, b, a)等等这些不会,这样就减少了重复。要实现这一点,我们可以将数组中的元素从小到大进行排序
,随后使用普通的三重循环就可以满足上面的要求。
同时,对于每一重循环而言,相邻两次枚举的元素不能相同,否则也会造成重复。举个例子,如果排完序的数组为
[0, 1, 2, 2, 2, 3]
^ ^ ^
我们使用三重循环枚举到的第一个三元组为 (0, 1, 2),如果第三重循环继续枚举下一个元素,那么仍然是三元组 (0, 1, 2),产生了重复。因此我们需要将第三重循环「跳到」下一个不相同的元素,即数组中的最后一个元素 3,枚举三元组 (0, 1, 3)。
nums.sort()
for first = 0 .. n-1
// 只有和上一次枚举的元素不相同,我们才会进行枚举
if first == 0 or nums[first] != nums[first-1] then
for second = first+1 .. n-1
if second == first+1 or nums[second] != nums[second-1] then
for third = second+1 .. n-1
if third == second+1 or nums[third] != nums[third-1] then
// 判断是否有 a+b+c==0
check(first, second, third)
这种方法的时间复杂度仍然为 O(N^3),毕竟我们还是没有跳出三重循环的大框架。然而它是很容易继续优化的,可以发现,如果我们固定了前两重循环枚举到的元素 a 和 b,那么只有唯一的 cc满足 a+b+c=0。当第二重循环往后枚举一个元素 b'
时,由于 b' > b
,那么满足 a+b'+c'=0
的 c’, 一定有 c' < c
,即 c' 在数组中一定出现在 c的左侧
。也就是说,我们可以从小到大枚举 b,同时从大到小枚举 c,即第二重循环和第三重循环实际上是并列的关系。
有了这样的发现,我们就可以保持第二重循环不变,而将第三重循环变成一个从数组最右端开始向左移动的指针,从而得到下面的伪代码:
nums.sort()
for first = 0 .. n-1
if first == 0 or nums[first] != nums[first-1] then
// 第三重循环对应的指针
third = n-1
for second = first+1 .. n-1
if second == first+1 or nums[second] != nums[second-1] then
// 向左移动指针,直到 a+b+c 不大于 0
while nums[first]+nums[second]+nums[third] > 0
third = third-1
// 判断是否有 a+b+c==0
check(first, second, third)
这个方法就是我们常说的「双指针」
,当我们需要枚举数组中的两个元素时,如果我们发现随着第一个元素的递增,第二个元素是递减的,那么就可以使用双指针的方法,将枚举的时间复杂度从 O(N^2) 减少至 O(N)。
为什么是 O(N) 呢?
这是因为在枚举的过程每一步中,「左指针」会向右移动一个位置(也就是题目中的 b),而「右指针」会向左移动若干个位置,这个与数组的元素有关,但我们知道它一共会移动的位置数为 O(N),均摊下来,每次也向左移动一个位置,因此时间复杂度为 O(N)。
注意到我们的伪代码中还有第一重循环,时间复杂度为 O(N),因此枚举的总时间复杂度为 O(N^2)。由于排序的时间复杂度为 O(N log N),在渐进意义下小于前者,因此算法的总时间复杂度为 O(N^2)
上述的伪代码中还有一些细节需要补充,例如我们需要保持左指针一直在右指针的左侧(即满足 b≤c
),具体可以参考下面的代码,均给出了详细的注释。
1.3、题解
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> result;
int length = nums.size();
if(length < 3)
return result;
sort(nums.begin(), nums.end());
for(int i = 0;i < length - 2;i++)
{
int L =i+1;
int R = length -1;
if(nums[i] > 0)
{
break;
}
if(i > 0 && nums[i] == nums[i - 1])
{
continue;
}
while(L < R)
{
int sum = nums[i] + nums[L] + nums[R] ;
if(sum == 0)
{
result.push_back({nums[i],nums[L],nums[R]});
while(L < R && nums[L] == nums[L+1])
L++;
while(L < R && nums[R] == nums[R-1])
R--;
L++;
R--;
}
else if(sum < 0)
L++;
else
R--;
}
}
return result;
}
};
2、最接近的三数之和
2.1、最接近的三数之和
原题链接
给定一个包括 n 个整数的数组 nums 和 一个目标值 target。找出 nums 中的三个整数,使得它们的和与 target 最接近。返回这三个数的和。假定每组输入只存在唯一答案。
示例:
输入:nums = [-1,2,1,-4], target = 1
输出:2
解释:与 target 最接近的和是 2 (-1 + 2 + 1 = 2) 。
提示:
3 <= nums.length <= 10^3
-10^3 <= nums[i] <= 10^3
-10^4 <= target <= 10^4
2.2、题解
class Solution {
public:
int threeSumClosest(vector<int>& nums, int target) {
sort(nums.begin(), nums.end());
int closestSumValue = nums[0]+ nums[1]+ nums[2];
int digit1_index, digit2_index, digit3_index;// 所选定的要求和的三个数的下标
int max_digit1_index = nums.size() - 2;
// 从左到遍历一遍数组,遍历到第i个元素时则认为该元素为第一个选定元素
// 另外两个元素是从该元素的右边选择(即从第i+1到end之间选另外两个元素)。
for (int i = 0; i < max_digit1_index; i++)
{
digit1_index = i;
digit2_index = i + 1;
digit3_index = nums.size() - 1;
int tmp_sum = nums[digit1_index] + nums[digit2_index] + nums[digit3_index];
// 第一个数是在当前时刻是指定不变了
// 下面这个循环要做的事情就是确定在第一个数是nums[i]的情况下
// 剩下两个数怎么选最合适
while (digit2_index < digit3_index)
{
if (abs(tmp_sum - target) < abs(closestSumValue - target))
closestSumValue = tmp_sum;
int difference = target - tmp_sum;
if (difference == 0)
return target;
if (difference > 0)
{
// target比tmp_sum大,所以为了缩小差距那就应该增大tmp_sum
// 但是第3个数已经是最大了那就增大第2个数
digit2_index++;
}
else {
// target比tmp_sum小,所以为了缩小差距那就应该减小tmp_sum
// 但是第2个数已经是最小了那就减小第3个数
digit3_index--;
}
tmp_sum = nums[digit1_index] + nums[digit2_index] + nums[digit3_index];
}
}
return closestSumValue;
}
};
3、数组内是否有任意个元素之和等于m
3.1、题目
描述:输入n个数,判断其中是否有任意个数的和等于m。
案例一:
输入:5 100
10 20 30 40 50
输出:1
案例二:
输入:5 100
10 40 70 86 120
输出:0
3.2、题解
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
bool canPartition(vector<int>& nums, int target)
{
int n = nums.size();
vector<bool> dp(target + 1, false);
// base case
dp[0] = true;
for (int i = 0; i < n; i++)
for (int j = target; j >= 0; j--)
if (j - nums[i] >= 0)
dp[j] = dp[j] || dp[j - nums[i]];
return dp[target];
}
int main()
{
vector<int> arr = { 10, 20 ,30, 40, 50 };
int target = 100;
if (canPartition(arr, target))
cout << 1 << endl;
else
cout << 0 << endl;
return 0;
}