2SUM、3SUM、KSUM
2SUM、3SUM、KSUM
2SUM:
Question:
给定一个整数数组和一个目标值,找出数组中和为目标值的两个数。
你可以假设每个输入只对应一种答案,且同样的元素不能被重复利用。
Example:
给定 nums = [2, 7, 11, 15], target = 9
因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]
思路:
1.暴力求解,遍历每一种可能,不予考虑。
2.有没有什么办法够快速找到某一数的匹配序号而不用去遍历?Hash表。
class Solution {
public int[] twoSum(int[] nums, int target) {
HashMap<Integer,Integer> map=new HashMap<>(); //建立 值-位置 的键值对映射。
for(int i=0;i<nums.length;i++){
if(map.get(target-nums[i])!=null){
return new int[]{i,map.get(target-nums[i])}; //根据值找到位置
}
map.put(nums[i],i);
}
return null;
}
}
3SUM
给定一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0
?找出所有满足条件且不重复的三元组。注意:答案中不可以包含重复的三元组。
例如, 给定数组 nums = [-1, 0, 1, 2, -1, -4],
满足要求的三元组集合为:
[
[-1, 0, 1],
[-1, -1, 2]
]
思路:
1.可以实现问题的转化,我们可以先确定单个元素,比如固定 nums[0],那我们的问题就变成了 target=-nums[0] 的2SUM问题。时间复杂度为O()。
2.双游标技巧:
1.先将数组排序
2.我们采用两个游标,一个从左向右移动,另一个从右向左移动。
伪代码:
if(num[left]+num[right]==0) 得到答案;左右游标同时按规定方向移动一个;
else if(num[left]+num[right]<0) 左边游标右移一格;
else if(num[left]+num[right]>0) 右边游标左移一格;
实现代码:
public List<List<Integer>> threeSum(int[] num) {
Arrays.sort(num);
List<List<Integer>> res = new LinkedList<>();
for (int i = 0; i < num.length-2; i++) {
if (i == 0 || (i > 0 && num[i] != num[i-1])) {
int lo = i+1, hi = num.length-1, sum = 0 - num[i];
while (lo < hi) {
if (num[lo] + num[hi] == sum) {
res.add(Arrays.asList(num[i], num[lo], num[hi]));
while (lo < hi && num[lo] == num[lo+1]) lo++;
while (lo < hi && num[hi] == num[hi-1]) hi--;
lo++; hi--;
} else if (num[lo] + num[hi] < sum) lo++;
else hi--;
}
}
}
return res;
}
这个问题读者最大的疑惑可能是会不会跳过了某些组合,答案是不会。
本身数组有序,如果两个值相加 < -num[i] ,说明值过小,而右边的值是当前最大值,所以右游标向左的数值和左游标值相加必定<0,就不用考虑了,所以这时正确的做法,应该是将左边游标移动。
同理,当两数相加 > -num[i] 时,就应该是右边游标左移一格了。
当两数相加正好 = -num[i],那么就找到一个符合的组合。否则,i就取下一位。
KSUM的通解算法:
善于观察的同学应该已经发现,NSUM这种问题的一个通用的思想就是分治,4SUM可以降成3SUM,3SUM又可以激绛为2SUM,所以我们通过递归能够实现KSUM的解法。
public List<List<Integer>> fourSum(int[] nums, int target) {
Arrays.sort(nums);
return kSum(nums, 0, 4, target);
}
private List<List<Integer>> kSum (int[] nums, int start, int k, int target) {
int len = nums.length;
List<List<Integer>> res = new ArrayList<List<Integer>>();
if(k == 2) { //two pointers from left and right
int left = start, right = len - 1;
while(left < right) {
int sum = nums[left] + nums[right];
if(sum == target) {
List<Integer> path = new ArrayList<Integer>();
path.add(nums[left]);
path.add(nums[right]);
res.add(path);
while(left < right && nums[left] == nums[left + 1]) left++;
while(left < right && nums[right] == nums[right - 1]) right--;
left++;
right--;
} else if (sum < target) { //move left
left++;
} else { //move right
right--;
}
}
} else {
for(int i = start; i < len - (k - 1); i++) {
if(i > start && nums[i] == nums[i - 1]) continue;
List<List<Integer>> temp = kSum(nums, i + 1, k - 1, target - nums[i]);
for(List<Integer> t : temp) {
t.add(0, nums[i]);
}
res.addAll(temp);
}
}
return res;
}