We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
There was an error while loading. Please reload this page.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
给定一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?找出所有满足条件且不重复的三元组。 注意:答案中不可以包含重复的三元组。 例如, 给定数组 nums = [-1, 0, 1, 2, -1, -4], 满足要求的三元组集合为: [ [-1, 0, 1], [-1, -1, 2] ]
给定一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?找出所有满足条件且不重复的三元组。 注意:答案中不可以包含重复的三元组。
例如, 给定数组 nums = [-1, 0, 1, 2, -1, -4], 满足要求的三元组集合为: [ [-1, 0, 1], [-1, -1, 2] ]
例如, 给定数组 nums = [-1, 0, 1, 2, -1, -4], 满足要求的三元组集合为:
[ [-1, 0, 1], [-1, -1, 2] ]
参考两数之和的思路,加上用集合的思路去重,可以得出一个超时的解法(正确与否未知)如下
class Solution { public List<List<Integer>> threeSum(int[] nums) { List<List<Integer>> ret = new ArrayList<List<Integer>>(); Set<Set<Integer>> existSet = new HashSet<Set<Integer>>(); for(int i = 0; i < nums.length; i++) { Integer f = nums[i]; Map<Integer, Integer> twoSum = new HashMap<>(); for(int j = i + 1; j < nums.length; j++) { if(twoSum.keySet().contains(nums[j])) { // 找到一个组合 List<Integer> p = new ArrayList<Integer>(); p.add(nums[i]); p.add(twoSum.get(nums[j])); p.add(nums[j]); Set<Integer> e = new HashSet<Integer>(); e.add(nums[i]); e.add(twoSum.get(nums[j])); e.add(nums[j]); if (existSet.contains(e)) { continue; } else { existSet.add(e); } ret.add(p); } else { // map 中的 key value 表示 (期待的,已有的) twoSum.put(0 - nums[i] - nums[j], nums[j]); } } } return ret; } }
不管是否正确,反正是超时了,第一直觉往往是不准的。
第一直觉超时之后,我翻了翻评论,发现大家都先排序了一下,那么如果能排序,排序之后带来了哪些好处呢?排序之后的一个好处就是我们可以通过从两端分别取数来合成0。如果最小的数大于0或者最大的数小于0,那么就可以直接返回。经过分析,初步可以写出如下代码:
0
public List<List<Integer>> threeSum(int[] nums) { List<List<Integer>> res = new ArrayList<>(); Arrays.sort(nums); System.out.println(Arrays.toString(nums)); // 一些特殊情况,直接退出 if (nums.length <= 2 || nums[0] > 0 || nums[nums.length - 1] < 0) { return res; } List<List<Integer>> r = new ArrayList<>(); int pre = Integer.MAX_VALUE; for (int i = 1; i < nums.length; i++) { int low = 0; int high = nums.length - 1; if (nums[i] == pre) { low = i - 1; } while (low < i && high > i) { int v = nums[low] + nums[i] + nums[high]; if (v == 0) { List<Integer> ans = new ArrayList<>(); ans.add(nums[low]); ans.add(nums[i]); ans.add(nums[high]); r.add(ans); int oldHighValue = nums[high]; while (high > i && oldHighValue == nums[high]) { high--; } int oldLowValue = nums[low]; while (low < i && oldLowValue == nums[low]) { low++; } } else if (v > 0) { high--; } else { low++; } } pre = nums[i]; } return r; }
这个代码对于一些简单的例子,都是可以通过的,但是对于包含重复的数字的例子,就会出现重复的答案,如果再用一个集合去去重,那么时间上的开销就又大了很多,而且代码也会显的比较杂乱。
继续查阅了一些评论,我发现很多其他人的解法还是很巧妙的,完美的避免了我的上面的方法的重复问题,其实做法很简单:就是把我的以中间点作为锚点的思路,改成第一个点作为锚点。为什么这样可以呢?我们来看一个例子,比如
[-6,-5,-2, -2, -2, 2, 3, 4]
当我们走到第二个-2的时候,按照我的思路,第一个-2之前的数字(-6,-5)就不再遍历了(避免重复),但是这里很要命的一点是,它居然有三个-2,这样一来,走到第三个-2时,又会发现一个[-2,-2,4]是符合要求的,但是我们在处理第二个-2时其实已经发现过一次了!之所以发生这种情况,是因为我们让充当过一个中间值的数字又充当了第一位的值。为了避免这种情况,我们需要记录当前这个值是否在前面使用过,新的代码如下:
-2
-6,-5
[-2,-2,4]
public List<List<Integer>> threeSum(int[] nums) { List<List<Integer>> res = new ArrayList<>(); Arrays.sort(nums); // System.out.println(Arrays.toString(nums)); // 一些特殊情况,直接退出 if (nums.length <= 2 || nums[0] > 0 || nums[nums.length - 1] < 0) { return res; } List<List<Integer>> r = new ArrayList<>(); int pre = nums[0]; boolean preUsed = false;// 用于记录前一个相等的值是否使用过 for (int i = 1; i < nums.length; i++) { int low = 0; int high = nums.length - 1; if (nums[i] == pre) { low = i - 1; if (preUsed) // 如果它和前一个数字相等,而且前一个数字已经使用过,则跳过 continue; } else { preUsed = false; } while (low < i && high > i) { int v = nums[low] + nums[i] + nums[high]; if (v == 0) { List<Integer> ans = new ArrayList<>(); ans.add(nums[low]); ans.add(nums[i]); ans.add(nums[high]); r.add(ans); int oldHighValue = nums[high]; while (high > i && oldHighValue == nums[high]) { high--; } int oldLowValue = nums[low]; while (low < i && oldLowValue == nums[low]) { low++; } if (nums[i] == pre) // 标记使用过 preUsed = true; } else if (v > 0) { high--; } else { low++; } } pre = nums[i]; } return r; }
我们上面的解法之所以需要考虑的特殊情况比较多,是因为我们选择了中间数作为锚点,而这个中间数有有可能在别的组合中扮演第一位数,所以出现了这种复杂的情况。那么我们可以换一个锚点,即以第一位数作为锚点,这样当我们发现重复时(第二个-2),就可以直接跳过,因为此时的这个重复点作为第一个点在上一个点就处理过了,再次把它作为第一个点的话,出现将都是重复的结果了。
public List<List<Integer>> threeSum(int[] nums) { List<List<Integer>> res = new ArrayList<>(); Arrays.sort(nums); if (nums.length <= 2 || nums[0] > 0 || nums[nums.length - 1] < 0) { return res; } List<List<Integer>> r = new ArrayList<>(); for (int i = 0; i < nums.length - 1; i++) { if (i > 0 && nums[i] == nums[i - 1]) { continue; } int low = i + 1; int high = nums.length - 1; while (low < high) { int v = nums[i] + nums[low] + nums[high]; if (v == 0) { List<Integer> ans = new ArrayList<>(); ans.add(nums[i]); ans.add(nums[low]); ans.add(nums[high]); r.add(ans); int oldHighValue = nums[high]; while (high > low && oldHighValue == nums[high]) { high--; } int oldLowValue = nums[low]; while (low < high && oldLowValue == nums[low]) { low++; } } else if (v > 0) { high--; } else { low++; } } } return r; }
The text was updated successfully, but these errors were encountered:
No branches or pull requests
Uh oh!
There was an error while loading. Please reload this page.
题目
第一直觉
参考两数之和的思路,加上用集合的思路去重,可以得出一个超时的解法(正确与否未知)如下
不管是否正确,反正是超时了,第一直觉往往是不准的。
先排个序
第一直觉超时之后,我翻了翻评论,发现大家都先排序了一下,那么如果能排序,排序之后带来了哪些好处呢?排序之后的一个好处就是我们可以通过从两端分别取数来合成
0
。如果最小的数大于0
或者最大的数小于0
,那么就可以直接返回。经过分析,初步可以写出如下代码:这个代码对于一些简单的例子,都是可以通过的,但是对于包含重复的数字的例子,就会出现重复的答案,如果再用一个集合去去重,那么时间上的开销就又大了很多,而且代码也会显的比较杂乱。
处理一些特殊情况
继续查阅了一些评论,我发现很多其他人的解法还是很巧妙的,完美的避免了我的上面的方法的重复问题,其实做法很简单:就是把我的以中间点作为锚点的思路,改成第一个点作为锚点。为什么这样可以呢?我们来看一个例子,比如
当我们走到第二个
-2
的时候,按照我的思路,第一个-2
之前的数字(-6,-5
)就不再遍历了(避免重复),但是这里很要命的一点是,它居然有三个-2
,这样一来,走到第三个-2
时,又会发现一个[-2,-2,4]
是符合要求的,但是我们在处理第二个-2
时其实已经发现过一次了!之所以发生这种情况,是因为我们让充当过一个中间值的数字又充当了第一位的值。为了避免这种情况,我们需要记录当前这个值是否在前面使用过,新的代码如下:其他解法
我们上面的解法之所以需要考虑的特殊情况比较多,是因为我们选择了中间数作为锚点,而这个中间数有有可能在别的组合中扮演第一位数,所以出现了这种复杂的情况。那么我们可以换一个锚点,即以第一位数作为锚点,这样当我们发现重复时(第二个
-2
),就可以直接跳过,因为此时的这个重复点作为第一个点在上一个点就处理过了,再次把它作为第一个点的话,出现将都是重复的结果了。The text was updated successfully, but these errors were encountered: