[回溯法] -- 40 - 组合总和Ⅱ - Java +Python

115 阅读2分钟

给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。candidates 中的每个数字在每个组合中只能使用一次。

说明:

  • 所有数字(包括目标数)都是正整数
  • 解集不能包含重复的组合
示例 1:

输入: candidates = [10,1,2,7,6,1,5], target = 8,
所求解集为:
[
  [1, 7],
  [1, 2, 5],
  [2, 6],
  [1, 1, 6]
]
示例 2:

输入: candidates = [2,5,2,1,2], target = 5,
所求解集为:
[
  [1,2,2],
  [5]
]

这道题和 [回溯法] – 39 - 组合总和 - Python + Java原理上是一样的,只不过要求候选数组中的数字只能被使用一次,且解集中不能包含重复的组合。因此,首先不同之处在于下一次选择的位置是当前索引的下一个位置,即 i + 1。而且只在最后保存合法结果的时候判断一下,它在当前的解集中是否已经存在。如果已有,则直接进行下面的枚举过程;如果没有,则保存当前结果到解集,然后再进行下面的枚举。

Java解题代码:

class Solution {
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        List<List<Integer>> results = new LinkedList<>();
        LinkedList<Integer> track = new LinkedList<>();

        Arrays.sort(candidates);
        if (candidates.length == 0 || candidates[0] > target){
            return results;
        }

        dfs(track, candidates, target, results, 0);
        return results;
    }
    
    public void dfs(LinkedList<Integer> track, int[] candidates, int target, List<List<Integer>> results, int start) {
        if(target == 0){
            if (!results.contains(track)) {
                results.add(new LinkedList<>(track));
                return;
            }
        }

        for (int i = start; i < candidates.length; i++) {
            if (i == candidates.length || candidates[i] > target){
                break;
            }
            track.add(candidates[i]);
            dfs(track, candidates, target - candidates[i], results, i + 1);
            track.removeLast();
        }
    }
}

Python解题代码

class Solution:
    def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]:
        if candidates == [] or min(candidates) > target: 
            return []
        l = len(candidates)
        results = []
        def backrtrack(candidates, target, track):
            if target == 0 and track not in results:
                results.append(track.copy())
                return 
            
            for i in range(len(candidates)):
                if i == l or candidates[i] > target:
                    break

                track.append(candidates[i])
                backrtrack(candidates[i + 1:], target - candidates[i], track)
                track.pop()

        candidates = sorted(candidates)
        backrtrack(candidates, target, [])
        
        return results

如果采用在最后进行结果判重的操作进行剪枝,那么时间复杂度相对较高。因为,只要得到了一个合法的结果就需要进行一次判断。更好的方式是在选择之前进行判断,执行剪枝操作。

class Solution {
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        List<List<Integer>> results = new LinkedList<>();
        LinkedList<Integer> track = new LinkedList<>();

        Arrays.sort(candidates);
        if (candidates.length == 0 || candidates[0] > target){
            return results;
        }

        dfs(track, candidates, target, results, 0);
        return results;
    }
    
    public void dfs(LinkedList<Integer> track, int[] candidates, int target, List<List<Integer>> results, int start) {

        if(target == 0){
            results.add(new LinkedList<>(track));
            return;
        }

        for (int i = start; i < candidates.length; i++) {
            
            if (i > start && candidates[i] == candidates[i - 1]){
                continue;
            }

            if (i == candidates.length || candidates[i] > target){
                break;
            }

            // 做出选择
            track.add(candidates[i]);
            dfs(track, candidates, target - candidates[i], results, i + 1);
            track.removeLast();
        }
    }
}

Python的实现代码同理

class Solution:
    def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]:
        if candidates == [] or min(candidates) > target: 
            return []
        l = len(candidates)
        results = []
        def backrtrack(candidates, target, track):
            if target == 0:
                results.append(track.copy())
                return 

            
            for i in range(len(candidates)):
                if i > 0 and candidates[i] == candidates[i -1]:
                    continue
                if i == l or candidates[i] > target:
                    break

                track.append(candidates[i])
                backrtrack(candidates[i + 1:], target - candidates[i], track)
                track.pop()

        candidates = sorted(candidates)
        backrtrack(candidates, target, [])
        
        return results