kSum问题总结

[LeetCode]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.

测试案例

Given nums = [2, 7, 11, 15], target = 9,

Because nums[0] + nums[1] = 2 + 7 = 9,
return [0, 1].

思路一

  1. 先将数组排序。
  2. 从左至右依次遍历每个元素,同时在其右边的子序列中采用二分法查找 target 与当前元素的差值。

代码如下

class Solution {
    public int[] twoSum(int[] nums, int target) {
        int n = nums.length, pos;
        int[] res = new int[2];
        Arrays.sort(nums);
        for(int i = 0; i < n - 1; i++){
            if((pos = Arrays.binarySearch(nums, i + 1, n, target - nums[i])) > -1){
                res[0] = nums[i];
                res[1] = nums[pos];
                break;
            }
        }
        return res;
    }
}

思路二

  1. 先将数组排序
  2. 从左至右依次遍历每个元素,同时在其右边的子序列中采用二分法查找 target 与当前元素的差值。由于元素值依次递增,从而,下次二分查找的范围必定在上次二分查找的范围内。所以,可以保存上次二分查找返回的下标。作为下次二分查找的右边界。

代码如下

class Solution {
    public int[] twoSum(int[] nums, int target) {
        int n = nums.length, pos = n;
        int[] res = new int[2];
        Arrays.sort(nums);
        for(int i = 0; i < n - 1; i++){
            if((pos = Arrays.binarySearch(nums, i + 1, pos, target - nums[i])) > -1){
                res[0] = nums[i];
                res[1] = nums[pos];
                break;
            }
            //当pos = i + 1 时,说明后面的元素均不满足
            else if((pos = -(pos + 1)) == i + 1){
                break;
            }
        }
        return res;
    }
}

思路三

  1. 先将数组排序。

  2. 定义两个下标,start 和 end,初始时,\(start = 0, end = n - 1\)

  3. 比较 start 和 end 位置处的元素之和与 target 的大小。

    当 nums[start] + nums[end] == target 时,循环结束,找到结果。

    当 nums[start] + nums[end] < target 时,start++。

    当 nums[start] + nums[end] > target 时,end--。

代码如下

class Solution {
    public int[] twoSum(int[] nums, int target) {
        int n = nums.length, temp, start = 0, end = n - 1;
        int[] res = new int[2];
        Arrays.sort(nums);
        while(start < end){
            if((temp = nums[start] + nums[end]) > target){
                end--;
            }
            else if(temp == target){
                res[0] = nums[start];
                res[1] = nums[end];
                break;
            }
            else{
                start++;
            }
        }
        return res;
    }
}

[LeetCode 15] 3Sum

题目

//题目被简化掉了返回所有组合的结果,仅返回一组。原题在后面
Given an array nums of n integers, are there elements a, b, c in nums such that a + b + c = 0?

测试案例

Given array nums = [-1, 0, 1, 2, -1, -4],
A solution set is: [-1, 0, 1]

思路

利用2Sum,先将数组进行排序,然后从左至右依次遍历每个元素,对于每个元素,在其右边的子数组中调用2Sum。

代码如下

class Solution {
    public List<Integer> threeSum(int[] nums) {
        int n = nums.length;
        Arrays.sort(nums);
        List<Integer> res = null;
        for(int i = 0; i < n -2; i++){
            if((res = twoSum(nums, i + 1, n - 1, -nums[i])) != null){
                res.add(nums[i]);
                break;
            }
        }
        return res;
    }
    List<Integer> twoSum(int[] nums, int start, int end, int target){
        int temp;
        List<Integer> res = null;
        while(start < end){
            if((temp = nums[start] + nums[end]) < target){
                start++;
            }
            else if(temp > target){
                end--;
            }
            else{
                res = new ArrayList<>(3);
                res.add(nums[start]);
                res.add(nums[end]);
                break;
            }
        }
        return res;
    }
}

原题

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.

测试案例

Given array nums = [-1, 0, 1, 2, -1, -4],

A solution set is:
[
  [-1, 0, 1],
  [-1, -1, 2]
]

思路

  1. 在上面3sum的基础上输出所有结果,同时要去重。
  2. 去重的关键在于从左至右遍历每个元素时,如果与前面的元素相同,则不考虑当前元素。
  3. 同时为了输出所有结果,在2sum中,也需要输出所有满足条件的结果及去重。
  4. 在2sum中输出所有结果的思路是:当找到一对结果时,左右下标同时移动。
  5. 在2sum中去重的思路是:每次下标移动时,如果和前继元素相同,则继续移动。

这是常用的去重思路。

代码如下

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        int n = nums.length;
        List<List<Integer>> res = new LinkedList<>(), temp;
        Arrays.sort(nums);
        for(int i = 0; i < n - 2; i++){
            //3sum去重
            if(i == 0 || nums[i] != nums[i - 1]){
                res.addAll(twoSum(nums, i + 1, n - 1, -nums[i]));
            }
        }
        return res;
    }
    List<List<Integer>> twoSum(int[] nums, int start, int end, int target){
        int temp;
        List<Integer> list;
        List<List<Integer>> res = new LinkedList<>();
        while(start < end){
            if((temp = nums[start] + nums[end]) == target){
                list = new ArrayList<>(3);
                list.add(nums[start]);
                list.add(nums[end]);
                list.add(-target);
                res.add(list);
            }
            //2sum去重
            if(temp <= target){
                while(++start < end && nums[start] == nums[start - 1]);
            }
            //2sum去重
            if(temp >= target){
                while(--end > start && nums[end] == nums[end + 1]);
            }
        }
        return res;
    }
}

[LeetCode 18] 4Sum

题目

Given an array nums of n integers and an integer target, are there elements a, b, c, and d in nums such that a + b + c + d = target? Find all unique quadruplets in the array which gives the sum of target.

Note:
The solution set must not contain duplicate quadruplets.

测试案例

Given array nums = [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]
]

思路1

仿照3sum的思路,在3sum外面加一个循环。时间复杂度为 \(O(n^3)\)

代码

class Solution {
    List<List<Integer>> result = new LinkedList<>();
    public List<List<Integer>> fourSum(int[] nums, int target) {
        Arrays.sort(nums);        
        int length = nums.length, pre;
        for(int i = 0;i < length - 3; i++){
            if(i > 0 && nums[i] == nums[i - 1]){
                continue;
            }
            threeSum(nums,target - nums[i], i + 1, nums[i]);
        }
        return result;
    }    
    public void threeSum(int[] nums, int target, int start, int num1){
        int n = nums.length;        
        for(int i = start; i < n - 2; i++){
            if(i > start && nums[i] == nums[i - 1]){
                continue;
            }
            twosum(nums, i + 1, target - nums[i], num1, nums[i]);
        }        
    }
    void twosum(int[] nums, int start, int target, int num1, int num2){
        int temp, end = nums.length - 1;
        List<Integer> list;
        while(start < end){
            if((temp = nums[start] + nums[end]) == target){
                list = new ArrayList<>(4);
                list.add(num1);
                list.add(num2);
                list.add(nums[start]);
                list.add(nums[end]);
                result.add(list);
            }
            if(temp >= target){
                while(--end > start && nums[end] == nums[end + 1]);
            }
            if(temp <= target){
                while(++start < end && nums[start] == nums[start - 1]);
            }
        }
    }
}

思路2

  1. 将任意两点的和作为key,value为一个 list ,里面存放和值相同的两点的下标,放入 HashMap 中。时间复杂度为 \(O(n^2)\)
  2. 然后两层循环遍历每一对元素,从 target - 和值 对应的 value 中找出满足要求的结点。在 list 中查找的时间复杂度未知。

kSum

题目

Given an unsorted array, determine if there are K elements that sum up to SUM.

思路1

延续上面思路,在 2sum 外面加 \(k - 2\) 层循环,时间复杂度为 \(O(n^{k - 1})\)

思路2

将 ksum 问题看作一个二维背包问题。一个约束为 sum,另一个约束为 k。且要求出同时满足的所有所有情况。

代码如下

class Solution{
    int min;
    int n;
    boolean[][][] record;
    LinkedList<Integer> stack = new LinkedList<>();
    List<List<Integer>> res = new LinkedList<>();
    public List<List<Integer>> kSum(int[] nums, int k, int sum){
        n = nums.length;
        Arrays.sort(nums);
        min = nums[0];
        sum -= min * k;
        //转变为非负数
        int index = 0;
        while(index < n && (nums[index] - min <= sum)){
            nums[index++] -= min;
        }
        n = index;
        record = new boolean[n + 1][k + 1][sum + 1];
        record[0][0][0] = true;
        for(int i = 1; i <= n; i++){
            for(int j = 0; j <= k; j++){
                for(int p = 0; p <= sum; p++){
                    record[i][j][p] = record[i - 1][j][p];
                    if(!(record[i][j][p]) && j > 0 && p >= nums[i - 1]){
                        record[i][j][p] = record[i - 1][j - 1][p - nums[i - 1]];
                    }
                }
            }
        }
        rebuild(nums, n, k, sum);
        return res;
    }
    void rebuild(int[] nums, int index, int k, int sum){
        if(k <= 0){
            res.add(new LinkedList<>(stack));
            return;
        }
        if(record[index - 1][k][sum]){
            rebuild(nums, index - 1, k, sum);
        }
        //选择当前结点,需要进行过滤
        if(sum >= nums[index - 1] && record[index - 1][k - 1][sum - nums[index - 1]]){
            stack.push(nums[index - 1] + min);
            rebuild(nums, index - 1, k - 1, sum - nums[index - 1]);
            stack.pop();
        }
    }
}
posted @ 2018-09-04 16:38  Echie  阅读(182)  评论(0编辑  收藏  举报