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(n2)。
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;
    }
posted @ 2018-08-25 21:03  顾杰伟  阅读(416)  评论(0编辑  收藏  举报