主要有两种解题思路:哈希表+双指针
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].
分析题目——利用哈希表:
我们优化到线性的时间复杂度来解决问题,那么就是说只能遍历一个数字,那么另一个数字呢,我们可以事先将其存储起来,使用一个HashMap,来建立数字和其坐标位置之间的映射,HashMap是常数级的查找效率,这样,我们在遍历数组的时候,用target减去遍历到的数字,就是另一个需要的数字了,直接在HashMap中查找其是否存在即可,注意要判断查找到的数字不是第一个数字,比如target是4,遍历到了一个2,那么另外一个2不能是之前那个2,先判断是否存在,则避免了这个问题.
代码
class Solution {
public int[] twoSum(int[] nums, int target) {
Map<Integer, Integer> map = new HashMap<>();
for(int i=0; i<nums.length; i++) {
if(map.containsKey(target - nums[i])) {
return new int[]{map.get(target - nums[i]), i};
}
map.put(nums[i], i);
}
return null;
}
}
3Sum
题目
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:
Elements in a triplet (a,b,c) must be in non-descending order. (ie, a ≤ b ≤ c)
The solution set must not contain duplicate triplets.
For example, given array S = {-1 0 1 2 -1 -4},
A solution set is:
(-1, 0, 1)
(-1, -1, 2)
分析题目——双指针思路
这种方法是最常用的方法(leetcode上AC的代码大多都是这种方法),主要的思想是:必须先对数组进行排序(不排序的话,就不能利用双指针的思想了,所以说对数组进行排序是个大前提),每次固定i的位置,并利用两个指针j和k,分别指向数组的i+1位置和数组的尾元素,通过判断num[j]+num[k]与-num[i]的大小,来决定如何移动指针j和k,和leetcode上最大容器的拿到题目的思想类似。
本题求三个数的和为0
如果先fix一个数,然后另外的两个数使用Two Sum那种HashMap的解法,但是会有重复结果出现.所以此题并不是考我们Two Sum的解法。
那么我们来分析一下这道题的特点,要我们找出三个数且和为0,那么除了三个数全是0的情况之外,肯定会有负数和正数,我们还是要先fix一个数,然后去找另外两个数,使三个数的和为0.如果能更有效的定位呢?我们肯定不希望遍历所有两个数的组合,所以如果数组是有序的,那么我们就可以用双指针以线性时间复杂度来遍历所有满足题意的两个数组合。
具体做法:
-1.我们首先对原数组进行排序,然后开始遍历排序后的数组,这里注意不是遍历到最后一个停止,而是到倒数第三个就可以了。
-2 先做个剪枝优化,就是当遍历到正数的时候就break,因为我们的数组现在是有序的了,如果第一个要fix的数就是正数了,那么后面的数字就都是正数,就永远不会出现和为0的情况了。
-3 然后我们还要加上重复就跳过的处理,处理方法是从第二个数开始,如果和前面的数字相等,就跳过,因为我们不想把相同的数字fix两次。我们用两个指针分别指向fix数字之后开始的数组首尾两个数,如果与fix的数相加的和current正好为0,则将这两个数和fix的数一起存入结果中。
-4.然后就是跳过重复数字的步骤了,两个指针都需要检测重复数字。如果current < 0,则我们将左边那个指针j右移一位,使得指向的数字增大一些。同理,如果两数之和大于0,则我们将右边那个指针k左移一位,使得指向的数字减小一些.时间复杂度o(n2).代码如下:
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
Arrays.sort(nums);
List<List<Integer>> res = new ArrayList<List<Integer>>();
for(int i = 0; i < nums.length - 2; i++) {
if (i > 0 && nums[i] == nums[i - 1]) continue;
int j = i + 1, k = nums.length - 1;
while(j < k) {
int current = nums[i] + nums[j] + nums[k];
if (current == 0) {
res.add(Arrays.asList(nums[i], nums[j], nums[k]));
while(j < k && nums[j] == nums[j + 1]) j++;
while(j < k && nums[k] == nums[k - 1]) k--;
j++;
k--;
} else if (current < 0) {
j++;
} else {
k--;
}
}
}
return res;
}
}
4sum
题目
Given an array S of n integers, are there elements a, b, c, and d in S such that a + b + c + d = target? Find all unique quadruplets in the array which gives the sum of target.
Note:
Elements in a quadruplet (a,b,c,d) must be in non-descending order. (ie, a ≤ b ≤ c ≤ d)
The solution set must not contain duplicate quadruplets.
For example, given array S = {1 0 -1 0 -2 2}, and target = 0.
A solution set is:
(-1, 0, 0, 1)
(-2, -1, 1, 2)
(-2, 0, 0, 2)
分析题目
这道题要求跟3Sum差不多,只是需求扩展到四个的数字的和了。我们还是可以按照3Sum中的解法,只是在外面套一层循环,相当于求n次3Sum。我们知道3Sum的时间复杂度是O(n2),所以如果这样解的总时间复杂度是O(n3)。代码如下:
class Solution {
public List<List<Integer>> fourSum(int[] nums, int target) {
int len = nums.length;
List<List<Integer>> res = new ArrayList<>();
Arrays.sort(nums);
for (int i = 0; i < len - 3; i++) {
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
for (int j = i + 1; j < len - 2; j++) {
if (j > 1 && nums[j] == nums[j - 1] && j - i > 1) {
continue;
}
int left = j + 1, right = len - 1;
while (left < right) {
int _4sum = nums[i] + nums[j] + nums[left] + nums[right];
if (_4sum == target) {
res.add(Arrays.asList(nums[i], nums[j], nums[left], nums[right]));
while (left < right && nums[left] == nums[left + 1]) left++;
while (left < right && nums[right] == nums[right - 1]) right--;
left++;
right--;
} else if (_4sum > target)
right--;
else
left++;
}
}
}
return res;
}
}