题目描述
原题链接:15. 三数之和
解题思路
本题的难点在于去重,针对两种不同的方式:双指针和Hash采用不同的去重判定条件。
1、去重的目标
要明确,去重的是重复三元组,而不是三元组里重复的数。
2、去重初步思路
为了方便去重,第一步是先排序,可保证 nums[i]≤nums[j]≤nums[k] 。当按次顺序从前往后依次遍历时,不会出现因异位而出现的重复三元组,例如[1,1,2]
和[1,2,1]
。而可能会出现因数组中本身存在的相同元素导致出现记录重复三元组,例如[0,0,0]
和[-1,-1,2]
等。
因此我们的目标便是:对于含有重复元素的数,每次仅对比记录一次,若后续再次出现重复元素,则将其跳过。
一、排序+双指针法
1、双指针思路
双指针的思路是从前往后,确定第一个元素nums[i]
后,再在该元素的后续集合中的首和尾分别设置指针l
和r
。利用数组元素中的单调性,找到nums[i] + nums[l] + nums[r] == 0
时的三个数。
(1)若nums[i] + nums[l] + nums[r] < 0
,说明三者相加的数小了,l++
;
(2)若nums[i] + nums[l] + nums[r] > 0
,说明三者相加的数大了,r--
;
(3)若nums[i] + nums[l] + nums[r] == 0
,则找到目标数,记录到结果集res
中。
2、去重思路
- 对于第一个元素
nums[i]
,为了保证第一次出现的重复元素被记录并且还不会记录后续重复元素,去重条件设为nums[i] == nums[i - 1]
,当满足该条件时,认为有重复元素,应跳过本次遍历,执行continue
。 - 对于第二个元素
nums[l]
,我们会在之前就将第一次出现并满足三者相加等于0的元素,加入到结果集res
中。因此,去重条件为nums[l] == nums[l + 1]
,确保后续重复元素不再被记录。 - 对于第三个元素
nums[k]
,理由同上,去重条件为nums[r] == nums[r - 1]
,确保后续重复元素不再被记录。
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
int n = nums.size();
vector<vector<int>> res;
sort(nums.begin(), nums.end()); // 先排序
for(int i = 0; i < n - 2; i++) { // 对i从0~n-2,留出两个位置用作l和r
if(nums[i] > 0) break; // 当遍历的第一个数大于0,则后续相加的两个数一定不会为0
if(i > 0 && nums[i] == nums[i - 1]) continue; // 去重。与i-1相比,若之前i已有相等数加入其中,则次数若再加入,则会出现重复三元组。不和i+1进行比较的原因是避免出现漏记元素
int l = i + 1, r = n - 1; // 双指针分别从i后的首和尾开始遍历
while(l < r) {
while(l < r && nums[i] + nums[l] + nums[r] < 0) l++; // 比0小,说明需要增大,l++
while(l < r && nums[i] + nums[l] + nums[r] > 0) r--; // 比0大,说明需要减小,r--
if(l < r && nums[i] + nums[l] + nums[r] == 0) { // 找到相加等于0时
res.push_back(vector<int>{ nums[i], nums[l], nums[r]}); // 先将目标结果加入其中
while(l < r && nums[l] == nums[l + 1]) l++; // 去重。因已有nums[l]加入结果中,如果下一个数与之相等,那么会出现重复三元组
while(l < r && nums[r] == nums[r - 1]) r--; // 理由同上
l++, r--; // 上述while只是保证了后续不出现重复元素,还需要更新指针的值,指向后续元素
}
}
}
return res;
}
};
时间复杂度
O
(
n
2
)
O(n^2)
O(n2)
空间复杂度
O
(
l
o
g
n
)
O(log n)
O(logn) (忽略存储答案的空间,额外的排序的空间复杂度为
O
(
l
o
g
n
)
O(log n)
O(logn))
二、排序+hash表法(不方便)
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
int n = nums.size();
vector<vector<int>> res;
sort(nums.begin(), nums.end()); // 先对元素排序
for(int i = 0; i < n; i++) {
if(nums[i] > 0) break; // 如果起始遍历的元素大于0,那么后续的元素再相加一定不会为0
if(i > 0 && nums[i] == nums[i - 1]) continue; // 去重nums[i]
unordered_set<int> record;
for(int j = i + 1; j < n; j++) {
// 去重nums[j]
if(j > i + 2 && nums[j] == nums[j - 1] && nums[j - 1] == nums[j - 2])
continue;
int gap = 0 - (nums[i] + nums[j]); // 找到相加为0的nums[k]
if(record.find(gap) != record.end()) {
res.push_back({ nums[i], nums[j], gap});
record.erase(gap); // 去重nums[k]
} else {
record.insert(nums[j]);
}
}
}
return res;
}
};
参考文章: 第15题. 三数之和、三数之和