[leetcode]39. 组合总和(回溯做法)




题目

39. 组合总和

img

思路

套模板,回溯法

def backtrack(选择列表,路径):
    if 满足结束条件:
        操作res
        return
        
    for 选择 in 选择列表:
        if 选择不合法
            # 剪枝
            continue

        # 做选择 进入下一层决策树
        选择列表.remove(选择)
        路径.add(选择)
        backtrack(选择列表,路径)

        # 撤销选择
        路径.remove(选择)

代码

回溯做法

class Solution {
    List<List<Integer>> res = new LinkedList<>();

    public List<List<Integer>> combinationSum(int[] candidates, int target) {

        // 记录「路径」
        LinkedList<Integer> track = new LinkedList<>();

        backtrack(candidates, target, track);

        return res;
    }

    public void backtrack(int[] candidates, int target, LinkedList<Integer> track) {
        
        // 结束条件
        if (target == 0) {
            res.add(new LinkedList(track));
            return;
        }

        for (int candidate : candidates) {
            // 剪去不合法的分支
            if (candidate > target) {
                continue;
            }
            // 做选择
            track.add(candidate);
            
            // 进入下一层决策树
            backtrack(candidates, target - candidate, track);

            // 取消选择
            track.removeLast();
        }
    }
}

上面这段代码的输出是有问题的,尽管能拿到正确的组合,但是这里有一个顺序的问题。

如图,[2,2,3]和[2,3,2]代表的是同一个答案,但却同时出现了。

说明还需要有必要的剪枝,即进入下一次选择时,必须只能从比这次选择value大的选择中取。

img

改进一下,用一个index来记录,实现往后做选择。

public class Solution {
    List<List<Integer>> res = new LinkedList<>();

    public List<List<Integer>> combinationSum(int[] candidates, int target) {

        // 记录「路径」
        LinkedList<Integer> track = new LinkedList<>();

        backtrack(candidates, target, 0, track);

        return res;
    }

    public void backtrack(int[] candidates, int target, int index, LinkedList<Integer> track) {

        // 结束条件
        if (target == 0) {
            res.add(new LinkedList(track));
            return;
        }

        for (int i = index; i < candidates.length; i++) {
            // 剪去不合法的分支
            if (candidates[i] > target) {
                continue;
            }
            // 做选择
            track.add(candidates[i]);

            // 进入下一层决策树
            backtrack(
                    candidates,
                    target - candidates[i],
                    i,
                    track
            );

            // 取消选择
            track.removeLast();
        }
    }
}

img

动态规划

其实这道题不推荐使用动态规划来做,时间复杂度比较高,而且思路没有回溯这么清晰明确。

思路就是化为子问题,找状态转移方程嘛。

这里贴一个实现的示例。

    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        List<List<Integer>> result = new ArrayList<>();
        Map<Integer, Set<List<Integer>>> map = new HashMap<>();
        
        //对candidates数组进行排序
        Arrays.sort(candidates);
        int len = candidates.length;
        
        for (int i = 1; i <= target; i++) {
            //初始化map
            map.put(i, new HashSet<>());
            
            //对candidates数组进行循环
            for (int j = 0; j < len && candidates[j] <= target; j++) {
                if (i == candidates[j]) {
                    
                    //相等即为相减为0的情况,直接加入set集合即可
                    List<Integer> temp = new ArrayList<>();
                    temp.add(i);
                    map.get(i).add(temp);
                } else if (i > candidates[j]) {
                    //i-candidates[j]是map的key
                    int key = i - candidates[j];
                    //使用迭代器对对应key的set集合进行遍历
                    //如果candidates数组不包含这个key值,对应的set集合会为空,故这里不需要做单独判断
                    for (Iterator iterator = map.get(key).iterator(); iterator.hasNext(); ) {
                        List list = (List) iterator.next();
                        
                        //set集合里面的每一个list都要加入candidates[j],然后放入到以i为key的集合中
                        List tempList = new ArrayList<>();
                        tempList.addAll(list);
                        tempList.add(candidates[j]);
                        //排序是为了通过set集合去重
                        Collections.sort(tempList);
                        map.get(i).add(tempList);
                    }
                }
            }
        }
        result.addAll(map.get(target));
        return result;
    }

结语

  • 排列问题,讲究顺序(即 [2, 2, 3] 与 [2, 3, 2] 视为不同列表时),需要记录哪些数字已经使用过,此时用 used或者也叫visited 数组;
  • 组合问题,不讲究顺序(即 [2, 2, 3] 与 [2, 3, 2] 视为相同列表时),需要按照某种顺序搜索,此时使用 begin 变量。

比较适用回溯算法的题还有这些,冲冲冲。

39.组合总和

40. 组合总和 II

46. 全排列

47. 全排列 II

78. 子集

90. 子集 II

51. N皇后

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值