15. 3Sum
Given an array nums of n integers, are there elements a, b, c in nums 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.
Example:
Given array nums = [-1, 0, 1, 2, -1, -4],
A solution set is:
[
[-1, 0, 1],
[-1, -1, 2]
]
看了半天没啥想法,时间复杂度不要了,暴力解之~
1 public static List<List<Integer>> threeSum(int[] nums) { 2 int len = nums.length; 3 List<List<Integer>> result = new ArrayList<>(); 4 //3个for循环找和为零的组合(要求是n个和就n个嵌套for循环,汗!!!) 5 for (int i = 0; i < len; i++) { 6 for (int j = i + 1; j < len; j++) { 7 for (int k = len - 1; k > j; k--) { 8 if (nums[i] + nums[j] + nums[k] == 0) { 9 List<Integer> member = Arrays.asList(nums[i], nums[j], nums[k]); 10 result.add(member); 11 } 12 } 13 } 14 } 15 16 //获得要去重的元素的索引 17 List<Integer> removeList = new ArrayList<>(); 18 for (int i = 0; i < result.size(); i++) { 19 List<Integer> tempi = result.get(i); 20 for (int j = i + 1; j < result.size(); j++) { 21 List<Integer> tempj = result.get(j); 22 Collections.sort(tempi); 23 Collections.sort(tempj); 24 for (int k = 0; k < 3; k++) { 25 if (tempi.get(k) != tempj.get(k)) { 26 break; 27 } 28 if (k == 2) { 29 removeList.add(i); 30 } 31 } 32 } 33 } 34 35 //直接remove会引起索引值变化 36 for (int i = 0; i < result.size(); i++) { 37 for (Integer integer : removeList) { 38 if (i == integer) { 39 result.set(i, null); 40 } 41 } 42 } 43 for (Iterator<List<Integer>> iterator = result.iterator(); iterator.hasNext();) { 44 List<Integer> list = (List<Integer>) iterator.next(); 45 if (list == null) { 46 iterator.remove(); 47 } 48 } 49 return result; 50 }
测试效果
public static void main(String[] args) { int[] nums = { -1, 0, 1, 2, -1, -4 }; List<List<Integer>> result = threeSum(nums); for (List<Integer> list : result) { System.out.println(list.toString()); } }
结果貌似也是对的,但是以我对leetcode的了解,必然timeOut,提交试试
内心毫无波澜,百度之,看了一圈发现,可以从1. Two Sum问题入手,三个数求和先拿出一个后再继续两个求和的过程,即 target - C = A + b。去看自己Two Sum的提交
1. Two Sum
Given an array of integers, return indices of the two numbers such that they add up to a specific target.
You may assume that each input would have exactly one solution, and you may not use the same element twice.
Example:
Given nums = [2, 7, 11, 15], target = 9,
Because nums[0] + nums[1] = 2 + 7 = 9,
return [0, 1].
public int[] twoSum(int[] nums, int target) { int result[] = new int[2]; for (int i = 0; i < nums.length; i++) { for (int j = i + 1; j < nums.length; j++) if ((nums[i] + nums[j]) == target) { result[0] = i; result[1] = j; } } return result; }
额,万能暴力法,又一个O(n2),竟然还AC了,尴尬。网上看了一圈,找到个O(n)思路,通过HashMap存值和索引,可以将查找过程降到O(1)复杂度,自己实现如下:
public static int[] twoSum3(int[] nums, int target) { int result[] = new int[2]; Map<Integer, Integer> map = new HashMap<Integer, Integer>(); for (int i = 0; i < nums.length; i++) { int cur = target - nums[i]; if (map.containsKey(cur)) { result[1] = i; result[0] = map.get(cur); break; } else { map.put(nums[i], i); } } return result; }
利用HashMap结构虽然可以很快找到Two Sum的答案,但是在3SUM中涉及到的去重问题不好解决,采用排序法正好可以把Two Sum 和 3Sum完美结合起来,这里先简单介绍一下用排序解决Two Sum问题的思路。
1、将整个数组增序排列。
2、设置头尾两个指针。
3、指针对应元素相加,大于target,则尾指针左移;小于target,则头指针右移;等于,则继续此时的元素。
这样循环O(n)的复杂度即可找到想要的target,但是排序需要O(nlogn)的时间复杂度,所以整体时间复杂度为O(nlogn)。
下面直接上3Sum的AC代码:
public static List<List<Integer>> threeSum1(int[] nums) { List<List<Integer>> result = new ArrayList<>(); if (null == nums || nums.length < 3) { return result; } Arrays.sort(nums); int len = nums.length; for (int i = 0; i < len - 2; i++) { if (nums[i] > 0) { break; // nums是递增排列的,nums[i]是第一个数,如果大于0,后面两数不小于nums[i],和不可能为0 } if (i > 0 && nums[i] == nums[i - 1]) { continue;// 去重处理,nums[i-1]遍历过一次的情况下,如果nums[i]相等,找到的结果一定重复。 // i > 0 为了避免 i = 0时 i - 1导致数组越界。 } int twoSum = -nums[i]; int left = i + 1;// 去重处理,如果left从0开始,找到答案的在数组中的排列是 // left,nums[i],right, // 那么在这之前一定有相同答案排列为nums[i],left,right。 int right = len - 1; while (left < right) { if (twoSum == nums[left] + nums[right]) { List<Integer> member = Arrays.asList(nums[i], nums[left], nums[right]); result.add(member); while (left < right && nums[left++] == nums[left]) { } while (left < right && nums[right--] == nums[right]) { } // 去重处理,更新left和right的值后继续寻找答案 } else if (nums[left] + nums[right] > twoSum) { while (left < right && nums[right--] == nums[right]) { } } else { while (left < right && nums[left++] == nums[left]) { } } } } return result; }
这样在排序后,直接在循环遍历过程中解决了题目要求的去重问题,避免本文开始的暴力解法里又单独去重的步骤。时间复杂度上排序为nlogn,后续遍历过程为n*n,所以整体时间复杂度为O(n2)。