Combination Sum II

Given a collection of candidate numbers (C) and a target number (T), find all unique combinations in C where the candidate numbers sums to T.

Each number in C may only be used once in the combination.

Note:

  • All numbers (including target) will be positive integers.
  • Elements in a combination (a1, a2, … , ak) must be in non-descending order. (ie, a1 ≤ a2 ≤ … ≤ ak).
  • The solution set must not contain duplicate combinations.

 

For example, given candidate set 10,1,2,7,6,1,5 and target 8
A solution set is: 
[1, 7] 
[1, 2, 5] 
[2, 6] 
[1, 1, 6] 

解题思路:

这道题看似和上一题很像,只是答案里每个元素只能用一次了,(上一题是每个元素可以用多次)。其实还有一个比较大的变化,就是上一题的数组是set,而这一题是collection。也就是说,这里,可能给定的数组里,元素就有重复。

所以开始我只想到,上一题递归的时候,是从当前元素开始往后搜,现在只需要改成从当前元素的下一个就开始了。现在当前元素的下一个元素可能也与当前元素相同,当然不行了。

于是又写了一个前面做过的题目的removeDuplicate的方法,想把元素组去重,结果当然是错误的,上面的例子里[1, 1, 6]这个答案就没了。

代码还是给一下吧,错误。

public class Solution {

    public List<List<Integer>> combinationSum2(int[] num, int target) {

        List<List<Integer>> resultList = new LinkedList<List<Integer>>();

        List<Integer> currentList = new LinkedList<Integer>();

        //原题没说数组已经排序了,必须先排序

        Arrays.sort(num);

        num = removeDuplicate(num);

        dfs(resultList, currentList, 0, 0, num, target);

        return resultList;

    }

    

    public int[] removeDuplicate(int[] num){

        if(num.length == 0){

            return new int[] {};

        }

        

        int swapIndex = 1;

        int pre = num[0];

        for(int i = 1; i < num.length; i++){

            if(num[i] > pre){

                pre = num[i];

                num[swapIndex] = num[i];

                swapIndex++;

            }

        }

        return Arrays.copyOf(num, swapIndex);

    }

    

    public void dfs(List<List<Integer>> resultList, List<Integer> currentList, int currentSum, int step, int[] num, int target){

        if(currentSum == target){

            resultList.add(new LinkedList(currentList));

            return;

        }

        if(currentSum > target){

            return;

        }

        //避免结果重复的方法:i从当前元素的后一个元素往后开始,所以要把当前元素作为参数传进方法

        for(int i = step; i < num.length; i++){

            currentList.add(num[i]);

            currentSum += num[i];

            dfs(resultList, currentList, currentSum, i + 1, num, target);

            currentList.remove(currentList.size() - 1);

            currentSum -= num[i];

        }

    }

}

后来我看了一下List的equals方法,是重写好的,会自动去判断每个元素是否是值相等。所以我可以放心的调用List.contains()的方法了,只有不含有当前答案时,才塞进去。否则[1,1],1,的时候,会塞入两个1。

public class Solution {

    public List<List<Integer>> combinationSum2(int[] num, int target) {

        List<List<Integer>> resultList = new LinkedList<List<Integer>>();

        List<Integer> currentList = new LinkedList<Integer>();

        //原题没说数组已经排序了,必须先排序

        Arrays.sort(num);

        dfs(resultList, currentList, 0, 0, num, target);

        return resultList;

    }

    

    public void dfs(List<List<Integer>> resultList, List<Integer> currentList, int currentSum, int step, int[] num, int target){

        if(currentSum == target){

            if(!resultList.contains(currentList)){

                resultList.add(new LinkedList(currentList));

            }

            return;

        }

        if(currentSum > target){

            return;

        }

        //避免结果重复的方法:i从当前元素的后一个元素往后开始,所以要把当前元素作为参数传进方法

        for(int i = step; i < num.length; i++){

            currentList.add(num[i]);

            currentSum += num[i];

            dfs(resultList, currentList, currentSum, i + 1, num, target);

            currentList.remove(currentList.size() - 1);

            currentSum -= num[i];

        }

    }

}

可是上面解法的时间复杂度偏高,要350ms。能不能像上面那样,在搜索元素的时候就避免重复的结果?

联想到前面很多去重的例子,比如Search in Rotated Sorted Array II,比如3Sum,一般都是比较相邻两个元素,如果相等,都略过当前元素,指针前移。这是一个很重要的去重手段,远比最后比较结果是否重复来的有效!

于是我写了下面的代码,遇到了如下错误。

Input: [1,1], 2
Output: []
Expected: [[1,1]]

public class Solution {

    public List<List<Integer>> combinationSum2(int[] num, int target) {

        List<List<Integer>> resultList = new LinkedList<List<Integer>>();

        List<Integer> currentList = new LinkedList<Integer>();

        //原题没说数组已经排序了,必须先排序

        Arrays.sort(num);

        dfs(resultList, currentList, 0, 0, num, target);

        return resultList;

    }

    

    public void dfs(List<List<Integer>> resultList, List<Integer> currentList, int currentSum, int step, int[] num, int target){

        if(currentSum == target){

            resultList.add(new LinkedList(currentList));

            return;

        }

        if(currentSum > target){

            return;

        }

        //避免结果重复的方法:i从当前元素的后一个元素往后开始,所以要把当前元素作为参数传进方法

        for(int i = step; i < num.length; i++){

            if(i > 0 && num[i] == num[i - 1]){

                continue;

            }

            currentList.add(num[i]);

            currentSum += num[i];

            dfs(resultList, currentList, currentSum, i + 1, num, target);

            currentList.remove(currentList.size() - 1);

            currentSum -= num[i];

        }

    }

}

为什么?递归到第二个元素,也就是step==1时,因为当前num[1]==num[0],直接被跳过了!这属于什么样的处理?也就是说当前元素和上一元素相等,就跳过。上一元素是刚刚被加入到currentList里的元素。也就是说,这是和currentList里的元素比。如果题目要求,每个组合内的元素都不能重复,才应该这样写。

而这道题的要求是,每个元素只能使用一次!并不是元素的值不能重复,因为num内本来就可能有重复的值。

所以for循环第一句的判断应该是

if(i > step && num[i] == num[i - 1]){

      continue;

}

这句话的意思是,当前选取的元素如果在本回合内已经被选择过,就不要再加入了。而不是,currentList如果已经有了,就不要加入了。这是完全不同的!

想象一下1,1,1,1,2,从[1]到[1, 1]的过程。step==1时,第一次加入1是可以的,第二次再加入1就不行了。因为DFS,前面的基础是一样的,加入同样的元素必然导致同样的结果。这就是避免重复结果的原理。

public class Solution {

    public List<List<Integer>> combinationSum2(int[] num, int target) {

        List<List<Integer>> resultList = new LinkedList<List<Integer>>();

        List<Integer> currentList = new LinkedList<Integer>();

        //原题没说数组已经排序了,必须先排序

        Arrays.sort(num);

        dfs(resultList, currentList, 0, 0, num, target);

        return resultList;

    }

    

    public void dfs(List<List<Integer>> resultList, List<Integer> currentList, int currentSum, int step, int[] num, int target){

        if(currentSum == target){

            resultList.add(new LinkedList(currentList));

            return;

        }

        if(currentSum > target){

            return;

        }

        //避免结果重复的方法:i从当前元素的后一个元素往后开始,所以要把当前元素作为参数传进方法

        for(int i = step; i < num.length; i++){

            if(i > step && num[i] == num[i - 1]){

                continue;

            }

            currentList.add(num[i]);

            currentSum += num[i];

            dfs(resultList, currentList, currentSum, i + 1, num, target);

            currentList.remove(currentList.size() - 1);

            currentSum -= num[i];

        }

    }

}

这么一改,时间只需要270ms了。

leetcode 100题完成。

你可能感兴趣的:(com)