15.三数之和
题目
给你一个整数数组 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]]
注意,输出的顺序和三元组的顺序并不重要。
示例 2:
输入:nums = [0,1,1]
输出:[]
示例 3:
输入:nums = [0,0,0]
输出:[[0,0,0]]
思路
暴力循环:直接三重循环涵盖所有组合。
但是我们注意到,因为题目要求答案不重复,那么我们就应该想到排序。此外,除开第一层循环,剩下的两层循环就是双指针寻找固定值。
java代码
原有代码
报错说内存超出限制。
class Solution {public List<List<Integer>> threeSum(int[] nums) {int len=nums.length;List<List<Integer>>ans = new ArrayList<List<Integer>>();if(len<3) return ans;Arrays.sort(nums);int target=0;int j=0;int k=len-1;//第一个数从左到右遍历,第二个数从第一个数的右边开始遍历,第三个数从数组最右侧开始遍历。for(int i=0;i<len;i++){//target为第二个数和第三个数的理想目标值。如果此时就已经<0了,又因为数组是从小到大排序的。那么第二个数和第三个数怎么遍历都不可能加起来<0,所以退出循环。target=0-nums[i];if(target<0) break;//因为答案要求不重复,那么第一层循环需要跳过重复的元素。while(i<len&&nums[i]==nums[i+1]) i++;System.out.println(target);j=i+1;k=len-1;while(j<k){//因为答案要求不重复,那么第二层和第三层循环需要跳过重复的元素。while(j<k&&nums[k-1]==nums[k]) k--;while(j<k&&nums[j+1]==nums[j]) j++;//temp为第二个数和第三个数实际的值。int temp=nums[j]+nums[k];if(temp<target) j++;else if(temp>target) k--;else{List<Integer> list = new ArrayList<Integer>();list.add(nums[i]);list.add(nums[j]);list.add(nums[k]);ans.add(list);j++;k--;}} }return ans;}
}
问题指出与改进
提交后显示内存超出限制,实际上依旧是逻辑有问题。
首先
while(i<len&&nums[i]==nums[i+1]) i++;
\\不应该这么写,而应该这么写
if(i>0&&nums[i]==nums[i-1]) continue;```
以下的话能看懂就看,看不懂就看样例解释
这两种都会跳过计算重复的i值。但是第一种是先跳过,再去计算j和k对应的情况。那么就会导致漏掉j也为重复的i值对应的答案。而第二种是先计算j和k对应的情况,下一个i来的时候再看是否与上次的i重复,以此判断跳不跳过。
样例解释:
nums = [-1,0,1,2,-1,-4],排序后为[-4,-1,-1,0,1,2]
第一种碰到第一个-1时就会判断下一位是否也是-1,因为下一位确实是-1,所以它的i直接从第二个-1开始,那么就导致j只能从第二个-1后面的0开始。
而实际上答案包括i为第一个-1,j为第二个-1,k为2的情况。也就是j与i的值是重复的。所以不能提前跳。
其次
while(j<k&&nums[k-1]==nums[k]) k--;
while(j<k&&nums[j+1]==nums[j]) j++;
以下的话能看懂就看,看不懂就看样例解释
判断第二重循环和第三重循环时,因为不像是i的移动会限制j一样,他们跳过重复的值与不跳,都是在i和数组结尾之间的范围移动(其实就是第二重循环和第三重循环是并行的另一种体现),所以这里可以用while循环。
但是这个while循环不应该在答案加入j和k之前就判断,而是应该在加入答案后再判断。因为会漏掉j和k相等的答案情况。
样例解释:
nums=[-4,-2,1,-5,-4,-4,4,-2,0,4,0,-2,3,1,-5,0],
排序后就是 -5 -5 -4 -4 -4 -2 -2 -2 0 0 0 1 1 3 4 4
结果应该输出[[-5,1,4],[-4,0,4],[-4,1,3],[-2,-2,4],[-2,1,1],[0,0,0]]
但是如果while循环在答案加入j和k之前就判断,那么当k移动到第二个1的时候,它就会自动跳到第一个1,那么答案的[-2,1,1]就会丢失。j的移动同理。
改进后的代码
class Solution {public List<List<Integer>> threeSum(int[] nums) {int len=nums.length;List<List<Integer>>ans = new ArrayList<List<Integer>>();if(len<3) return ans;Arrays.sort(nums);int target=0;int j=0;int k=len-1;//第一个数从左到右遍历,第二个数从第一个数的右边开始遍历,第三个数从数组最右侧开始遍历。for(int i=0;i<len-1;i++){//target为第二个数和第三个数的理想目标值。如果此时就已经<0了,又因为数组是从小到大排序的。那么第二个数和第三个数怎么遍历都不可能加起来<0,所以退出循环。target=0-nums[i];if(target<0) break;//因为答案要求不重复,那么第一层循环需要跳过重复的元素。// 注意出现了nums[i-1],那么要加上i>0的判断if(i>0&&nums[i]==nums[i-1]) continue;j=i+1;k=len-1;while(j<k){//temp为第二个数和第三个数实际的值。int temp=nums[j]+nums[k];if(temp<target) j++;else if(temp>target) k--;else{//因为答案要求不重复,那么第二层和第三层循环需要跳过重复的元素。ans.add(Arrays.asList(nums[i], nums[j],nums[k]));while(j<k&&nums[k-1]==nums[k]) k--;while(j<k&&nums[j+1]==nums[j]) j++;k--;j++;}} }return ans;}
}