LeetCode第15题思悟——三数之和(3sum)
知识点预告
- 预处理数据的常见手段:排序;
- 双指针遍历数组,在范围内寻找两个数;
- 数组去重处理;
题目要求
给定一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?找出所有满足条件且不重复的三元组。
注意:答案中不可以包含重复的三元组。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/3sum
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
示例
给定数组 nums = [-1, 0, 1, 2, -1, -4],
满足要求的三元组集合为:
[
[-1, 0, 1],
[-1, -1, 2]
]来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/3sum
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
我的思路
纯小白解法,考虑到数组中有重复数字,我们首先统计每个数字出现的次数,使用Hashmap做统计,数字大小做key,出现次数为value;然后将key排序;之后,确定第一个数字,遍历key的数组,确定第二个数字;然后根据和为0,确定第三个数字,判断第三个数字是否可用——应该不小于第二个数字,并且出现次数大于等于1;
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> results=new ArrayList<>();
if(nums.length<3) {
return results;
}
List<Integer> resultItem;
HashMap<Integer,Integer> numberContainer=new HashMap<>();
ArrayList<Integer> differentKeyArray=new ArrayList<>();
for(int i=0;i<nums.length;i++){
if(numberContainer.containsKey(nums[i])){
numberContainer.put(nums[i],numberContainer.get(nums[i])+1);
}else{
numberContainer.put(nums[i],1);
differentKeyArray.add(nums[i]);
}
}
Collection.sort(differentKeyArray);
int differentKeyLength=differentKeyArray.size();
if(differentKeyArray.get(0)>0||differentKeyArray.get(differentKeyLength-1)<0){
return results;
}
int firstNum=differentKeyArray.get(0);
int firstNumIndex=0;
int secondNum;
int currentNumberSize;
int thirdNum;
while(firstNum<=0){//只要第一个数小于等于0,就可能寻找到一组合理解
numberContainer.put(firstNum,numberContainer.get(firstNum)-1);//锁定第一个数
for(int i=firstNumIndex;i<differentKeyLength;i++){//开始寻找第二个数
secondNum=differentKeyArray.get(i);
currentNumberSize=numberContainer.get(secondNum);
if(currentNumberSize>0){//当前该数是可用的
numberContainer.put(secondNum,currentNumberSize-1);//锁定第二个数
thirdNum=(firstNum+secondNum)*-1;
if(thirdNum>=secondNum){
if(numberContainer.containsKey(thirdNum)&&numberContainer.get(thirdNum)>0){//找到了一组解
resultItem=new ArrayList<>();
resultItem.add(firstNum);
resultItem.add(secondNum);
resultItem.add(thirdNum);
results.add(resultItem);
}
}
numberContainer.put(secondNum,currentNumberSize);//释放第二个数
}
}
firstNumIndex++;
if(firstNumIndex<differentKeyLength){
firstNum=differentKeyArray.get(firstNumIndex);
}else{
break;
}
}
return results;
}
实际上,算是排序之后再暴力解之;
纯粹的“人脑算法”,毫无算法技巧可言,从前的我,嗯,有点蠢;不过,题还是能解的;
优秀解法
//解法A
public List<List<Integer>> threeSum(int[] nums) {
Arrays.sort(nums);
List<List<Integer>> res = new ArrayList<>();
for(int i=0;i<nums.length;i++){
//如果i与i+1值相同,则跳过,去重
if(i==0||(i>0)&&nums[i]!=nums[i-1]){
//定义左右指针,左指针i+1,右指针为数组末尾
int l = i+1,r = nums.length-1;
while(l<r){
if(nums[i]+nums[l]+nums[r]==0){
//找到i,l,r加入res
res.add(Arrays.asList(nums[i],nums[l],nums[r]));
//如果l有重复值都向右跳过
while(l<r&&nums[l]==nums[l+1]) l++;
//如果r有重复值都向左跳过
while(l<r&&nums[r]==nums[r-1]) r--;
l++;r--;
//i值定,r值最大,只能把l往右移
}else if(nums[i]+nums[l]+nums[r]<0){
while(l<r&&nums[l]==nums[l+1]) l++;
l++;
//i值定,l值最小,只能把r往左移
}else{
while(l<r&&nums[r]==nums[r-1]) r--;
r--;
}
}
}
}
return res;
}
//解法B
public List<List<Integer>> threeSum(int[] nums) {
if (nums == null && nums.length<3){
return null;
}
List<List<Integer>> ans = new ArrayList();
Arrays.sort(nums);
threeSum(nums, 0, ans);
return ans;
}
private void threeSum(int [] nums, int target, List<List<Integer>> ans){
int len = nums.length;
if(nums == null || len < 3) return;
for (int i = 0; i < len-2 ; i++) {
if(nums[i] > target) break; // 如果当前数字大于0,则三数之和一定大于0,所以结束循环
if(i > 0 && nums[i] == nums[i-1]) continue; // 去重
int L = i+1;
int R = len-1;
while(L < R){
int sum = nums[i] + nums[L] + nums[R];
if(sum == target){
ans.add(Arrays.asList(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 if (sum > 0) R--;
}
}
}
解法A和B都采用排序作为预处理手段;之后又都采用双指针遍历数组的方法;从解题思路上来看,A和B是相同的,但是一些细节之处,实现略有不同:
- A中没有退出前置检查,B有:if(nums[i] > target) break; // 如果当前数字大于0,则三数之和一定大于0,所以结束循环;
- A中的去重代码略显重复,而B中在没有找到目标解的时候,其去重是在for循环中退出检查之后进行的;代码更为简洁;
实际上,A和B是两种主流的解法,而B解法效果更优;
差异分析
感觉,这道题的差异分析可以再单独开篇博客了,一个小节都说不完啊!A和B的对比在上一节中已经介绍,所以这里我们就说说自己解法的缺点吧。(又到了自我批评的时刻,老脸一红,发现事情并不简单…)
- 预处理手段不伦不类:感觉刻意使用了LeetCode第1题思悟一文提到的优秀解法。好处呢,就是也算学以致用,但是使用却并不得当;使用了不恰当的数组结构和处理思路,做了很多无用功;
- 采用暴力解法,效率低下:没有活用“双指针遍历”来寻找两个数;
- 代码丑陋
知识点小结
- 预处理数据的常见手段:排序;
- 双指针遍历数组,在范围内寻找两个数;
- 数组去重处理;