这是我参与11月更文挑战的第18天,活动详情查看:2021最后一次更文挑战
题目
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组。
示例1
输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
示例2
输入:nums = []
输出:[]
思路
题目要找给定数组 nums
中,所有可能和为 0 的三元组,且三元组不可重复
就是说 [-1, -1, 2] 和 [-1, 2, -1] 是同一三元组,且如果:[nums[i], nums[j], nums[k]] == [nums[I], nums[J], nums[K]] 其中至少有 i != I 、j != J 、k != K
一个等式成立,他们也是相同的三元组
那么大概思路就有:
暴力解法
-
先将题目给的
nums
数组,按从小到大排序 -
枚举三元组的第一个元素,再从第一个元素往后的位置,枚举三元组的第二个元素,在同理枚举第三个元素
-
保留上述符合题目条件的三元组
-
去重问题,在三元组的第一个元素
nums[i]
被枚举后,无论该元素能否符合成为题目要求的三元组成员,需要去判断nums[i + 1]
是否和nums[i]
相等,如果相等,则需将枚举的位置i + 1
,就可以保证三元组的第一个元素不会重复,同理处理第二个元素。在寻找第三个元素的过程中,如果寻找到符合要求的元素,则可以直接退出第三层循环,保证第三个元素不重复 -
几个提前结束枚举的条件
-
如果第一个元素枚举之后,就符合
nums[i] > 0
,可以直接返回结果(因为一个大于0的数,加上任何比他大的数,都会大于0) -
如果第一个元素枚举之后,第二个元素初始化位置
j = i + 1
,符合nums[i] + nums[j] > 0
, 也可直接返回结果 -
在枚举第二个元素的过程中,如果有
nums[i] + nums[j] + nums[n - 1] < 0
,说明j
位置的元素不符合要求,可以跳过j
到j + 1
-
代码如下
var threeSum = function(nums) {
let ans = [], n = nums.length;
// 从小到大排序
nums.sort((a, b) => a - b);
for(let i = 0; i < n - 2; i++) {
// 提前结束枚举 1
if(nums[i] > 0) {
return ans;
}
let j = i + 1;
// 提前结束枚举 2
if(nums[i] + nums[j] > 0) {
return ans;
}
for(j; j < n - 1; j++) {
// 提前结束枚举 3
if(nums[i] + nums[j] + nums[n - 1] < 0) {
continue;
}
let k = j + 1;
while(k < n) {
// 只寻找一次 第三位置上的元素
if(nums[i] + nums[j] + nums[k] == 0) {
ans.push([nums[i], nums[j], nums[k]]);
break;
}
k++;
}
// 第二位置去重
while(j + 1 < n - 1 && nums[j] == nums[j + 1]) {
j++;
}
}
// 第一位置去重
while(i + 1 < n - 2 && nums[i] == nums[i + 1]) {
i++;
}
}
return ans;
}
排序加双指针
对于已排序的数组,在数组中寻找两个数符合某些条件的时候,可以用双指针,因为可以明确的知道指针的移动规则
在寻找三元组,第二、第三元素的过程中,优化上述解法:
-
定义双指针
left = i + 1
,right = n - 1
-
当
left < right
时,判断三数之和sum = nums[i] + nums[left] + nums[right]
-
sum == 0
,将[nums[i], nums[left], nums[right]]
放入结果,同时将left
向右移动到下一个不等于nums[left]
的位置(去重
),将right
向左移动到下一个不等于nums[right]
的位置,再次比较 -
sum > 0
,将right
向左移动(结果变小) -
sum < 0
,将left
向右移动(结果变大)
-
-
重复上述步骤
代码如下
/**
* @param {number[]} nums
* @return {number[][]}
*/
var threeSum = function(nums) {
let ans = [];
nums.sort((a,b) => {return a - b});
const n = nums.length;
for(var i = 0; i < n; i++) {
if(nums[i] > 0) break;
if(i > 0 && nums[i] === nums[i - 1]) continue;
let left = i + 1;
let right = n - 1;
while(left < right) {
const sums = nums[i] + nums[left] + nums[right];
if(sums == 0) {
ans.push([nums[i], nums[left], nums[right]]);
while(left < right && nums[left] === nums[left + 1]) left++;
while(left < right && nums[right] === nums[right - 1]) right--;
left++;
right--;
} else if(sums > 0) {
right--;
} else {
left++;
}
}
}
return ans;
};
小结
复杂问题,要学会分解;分解之后说不定可以双指针 😂😂😂
LeetCode 👉 HOT 100 👉 三数之和 - 中等题 ✅
LeetCode 👉 HOT 100 👉 盛最多水的容器 - 中等题
✅
LeetCode 👉 HOT 100 👉 最长回文子串 - 中等题
✅
LeetCode 👉 HOT 100 👉 无重复字符的最长子串 - 中等题
✅