LeetCode第[15]题(Java):3Sum (三数之和为目标值)——Medium
题目难度:Medium
题目:
Given an array S of n integers, are there elements a, b, c in S such that a + b + c = 0? Find all unique triplets in the array which gives the sum of zero.
Note: The solution set must not contain duplicate triplets.
翻译:
给定一个n个整数的数组S,S中是否存在有a b c三个元素使得a+b+c=0?在S中找到所有的非重复组合,使它们的和为0
注意:答案集不能包含重复的数组。例如:[1,5,-6]与[-6,5,1]等数组,答案只出现其中一个
思路:三。。。个。。for循环??
(⊙x⊙;)。。。就要写!
Code:311 / 313 test cases passed. —— Time Limit Exceeded 时间复杂度:O(N3)
1 public static List<List<Integer>> threeSum1(int[] nums) { 2 Arrays.sort(nums); 3 List<List<Integer>> outList = new ArrayList<List<Integer>>(); 4 for (int i = 0; i < nums.length; i++) { 5 if ((i > 0 && nums[i] == nums[i - 1])) // 各循环起始点不需要判断重复 6 continue; // 不用i++ 的原因:避免最后的k还要增加边界判断,进入下一个循环则会自动边界判断“i < nums.length” 7 for (int j = i + 1; j < nums.length; j++) { 8 if ((j > i + 1 && nums[j] == nums[j - 1])) 9 continue; // 当有很多for和if的时候,条件取反后用continue,以此取代if的{}缩进,使代码可读性增加 10 for (int k = j + 1; k < nums.length; k++) { 11 if ((k > j + 1 && nums[k] == nums[k - 1])) 12 continue; 13 if ((nums[i] + nums[j] + nums[k] == 0)) { 14 List<Integer> inList = new ArrayList<Integer>(); 15 inList.add(nums[i]); 16 inList.add(nums[j]); 17 inList.add(nums[k]); 18 outList.add(inList); 19 break; 20 } 21 } 22 } 23 } 24 return outList; 25 }
(因为只有在尾巴进行添加并无其他功能,所以采用ArrayList比较实惠和效率)
312测试用例超时:
[82597,-9243,…………此处省略N千个数字]
不想说话,让蠢人静静。。。。
————————————————————————智商分割线————————————————————————
参考答案:74ms——beats85.12% 时间复杂度O(N2)
public List<List<Integer>> threeSum(int[] nums) { List<List<Integer>> result = new ArrayList<List<Integer>>(); Arrays.sort(nums); for (int i = 0; i < nums.length-2; i++) { int left = i + 1; int right = nums.length - 1; if (i > 0 && nums[i] == nums[i - 1]) continue; // 去掉重复的起点 while (left < right) { int sum = nums[left] + nums[right] + nums[i]; if (sum == 0) { result.add(Arrays.asList(new Integer[]{nums[i], nums[left], nums[right]})); while (left < right && nums[left] == nums[left + 1]) left++; // 去掉重复的左点 while (left < right && nums[right] == nums[right - 1]) right--; // 去掉重复的右点 right--; // 进入下一组左右点判断 left++; } else if (sum > 0) { right--; // sum>0 ,说明和过大了,需要变小,所以移动右边指针 } else { left++; // 同理,需要变大,移动左指针 } } } return result; }
精髓在于: (排序 + 去重) + 双指针移动相向定位
注意:不需要“重复的双胞胎数组”,所以是“组合”即Cn/m(从M个数里随机选出N个数有多少种情况)而不是排列An/m——考虑去重
所查找的数之间具有一定的关联(本题为:和为0),那么就应该利用这个属性,而不是简单的进行搜索。
思路解析:
首先考虑数组遍历时去重:方法一:先将数组排好序,在遍历的时候与上一个进行比较,相同则直接进入下一个
方法二:用容器Set——简单,但是同样需要排序,增加算法复杂度并且此题三个数操作不方便【在第一题TwoSum的后面有更新用Set的方法】。
所以数组遍历去重在已排序的情况下优先采取方法一。
其次考虑降低多次循环算法复杂度:
降低复杂度一般的途径就是利用已有的而未用到的条件将多余的步骤跳过或者删去!
由于三个数是具有一个特点的:和为某个定值,这个条件只是用来判断了而并没有使用
并且,由上个去重得知,后面使用的数组是已经排序好的。
此时仔细想想应该就能想到
从两端使用两个指针相向移动,两端指针所指数之和如果小于目标值,只需要移动左边的指针,否则只需要移动右边的指针!!
例如[1,1,1,4,5,7,8,8,9]中 定目标值为15,从两边开始,1+9为10,小于15,移动右边指针左移变成1+8只会更少,所以移动左边变成4+9以此类推。。
妈耶!我想不到啊!
此时为两数定位,而题目中要找3个数
………………外嵌for循环!!!又回到了我的领域 ahhhhhhh~
分析搞定!