一、三数之和
1、题目
https://leetcode.cn/problems/3sum/description/
给你一个整数数组
nums
,判断是否存在三元组[nums[i], nums[j], nums[k]]
满足i != j
、i != k
且j != k
,同时还满足nums[i] + nums[j] + nums[k] == 0
。请你返回所有和为0
且不重复的三元组。**注意:**答案中不可以包含重复的三元组。
示例 1:
输入:nums = [-1,0,1,2,-1,-4] 输出:[[-1,-1,2],[-1,0,1]] 解释: nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。 nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。 nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。 不同的三元组是 [-1,0,1] 和 [-1,-1,2] 。 注意,输出的顺序和三元组的顺序并不重要。
示例 2:
输入:nums = [0,1,1] 输出:[] 解释:唯一可能的三元组和不为 0 。
示例 3:
输入:nums = [0,0,0] 输出:[[0,0,0]] 解释:唯一可能的三元组和为 0 。
2、解题思路
(1)朴素
排序+循环+set去重
我们需要先将这个数组从小到大排序,然后定义三个嵌套循环i、j、k,让每个循环变量的初始值等于上一个循环变量+1的值。这是为了防止每个循环之间有交叉,导致i=j or i=k or j=k。然后将满足条件的i,j,k下标对应的数添加到set里面进行去重。
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> ans; // 存放答案
set<vector<int>> ret; // 进行去重
sort(nums.begin(), nums.end()); // 对原有数组排序
int n = nums.size();
// 循环
for (int i = 0; i < n; i++)
{
for (int j = i + 1; j < n; j++)
{
for (int k = j + 1; k < n; k++)
{
if (nums[i] + nums[j] + nums[k] == 0)// 判断条件
{
ret.insert({ nums[i], nums[j], nums[k] });// 去重
}
}
}
}
// 将结果存放至ans中
for (auto& e : ret)/
{
ans.push_back(e);
}
return ans;
}
};
(2)双指针算法
对于上述的朴素算法,时间复杂度是O(n³),那如何能将这个时间复杂度降下来呢?首先,我们对这个数组进行排序,那这个数组就会有单调性。假如我们先固定一个数a,假设这个数是答案的一部分,然后再去寻找另外两个数的和是0 - a。具体如图。
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> ans;
sort(nums.begin(), nums.end());
for (int i = 0; i < nums.size() - 2;) {
int l = i + 1, r = nums.size() - 1;
while (l < r) {
if (nums[i] + nums[l] + nums[r] == 0) {
ans.push_back({nums[i], nums[l], nums[r]});
l++;
r--;
// 去重left 和 right
while (l < r && nums[l] == nums[l - 1])
l++;
while (l < r && nums[r] == nums[r + 1])
r--;
} else if (nums[i] + nums[l] + nums[r] > 0)
r--;
else
l++;
}
// 去重固定数
i++;
while (i < nums.size() - 2 && nums[i] == nums[i - 1])
i++;
}
return ans;
}
};
二、四数之和
1、题目
https://leetcode.cn/problems/4sum/description/
给你一个由
n
个整数组成的数组nums
,和一个目标值target
。请你找出并返回满足下述全部条件且不重复的四元组[nums[a], nums[b], nums[c], nums[d]]
(若两个四元组元素一一对应,则认为两个四元组重复):
0 <= a, b, c, d < n
a
、b
、c
和d
互不相同nums[a] + nums[b] + nums[c] + nums[d] == target
你可以按 任意顺序 返回答案 。
示例 1:
输入:nums = [1,0,-1,0,-2,2], target = 0 输出:[[-2,-1,1,2],[-2,0,0,2],[-1,0,0,1]]
示例 2:
输入:nums = [2,2,2,2,2], target = 8 输出:[[2,2,2,2]]
提示:
1 <= nums.length <= 200
-109 <= nums[i] <= 109
-109 <= target <= 109
2、解题思路
(1)朴素算法
同样的有四个嵌套for循环来进行遍历,找到答案,然后用set进行去重。(和三数之和类似,就不写了)
时间复杂度:O(n⁴)
(2)双指针算法
这个也和三数之和的双指针算法类似,唯一不同的就是需要用到两个for循环进行固定数,然后用一个双指针算法查找剩余数。
时间复杂度:O(n³)
class Solution {
public:
vector<vector<int>> fourSum(vector<int>& nums, int target) {
vector<vector<int>> ans;
if (nums.size() < 4) return ans;
sort(nums.begin(), nums.end());
for (int a = 0; a <= nums.size() - 4; a++)
{
// 要注意这个题的样例中的数有可能会超过int
long long target_t = (long long)target - nums[a];
for (int b = a + 1; b <= nums.size() - 3; b++)
{
int left = b + 1, right = nums.size() - 1;
while (left < right)
{
if (nums[left] + nums[right] < target_t - nums[b])
{
left++;
}
else if (nums[left] + nums[right] > target_t - nums[b])
{
right--;
}
else
{
ans.push_back({ nums[a], nums[b], nums[left], nums[right] });
left++;
right--;
while (left < right && nums[left] == nums[left - 1]) left++;
while (left < right && nums[right] == nums[right + 1]) right--;
}
}
while (b < nums.size() - 3 && nums[b] == nums[b + 1]) b++;
}
while (a < nums.size() - 4 && nums[a] == nums[a + 1]) a++;
}
return ans;
}
};
while (b < nums.size() - 3 && nums[b] == nums[b + 1]) b++;
}
while (a < nums.size() - 4 && nums[a] == nums[a + 1]) a++;
}
return ans;
}
};