初识回溯算法——(一)组合问题

💫目录

回溯算法:

算法模板:

小试牛刀——Leetcode 组合、组合总和问题


回溯算法:

回溯法是一种选优搜索法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法。

回溯法并不高效,顶多可以剪枝优化,本质仍是穷举,是基于递归实现的。

回溯法一般可以解决以下问题:

  • 组合问题:N个数里面按一定规则找出k个数的集合

  • 排列问题:N个数按一定规则全排列,有几种排列方式

  • 切割问题:一个字符串按一定规则有几种切割方式

  • 子集问题:一个N个数的集合里有多少符合条件的子集

  • 棋盘问题:N皇后,解数独等等

算法模板:

// result 用于存放结果 path 表示路径选择

public void backtrack(参数列表:){

if(满足结束条件){

result.add(路径);

return;

}

fori(遍历所有可能的选择){

if(满足条件){

path.push();   // 处理(添加)节点

} else {

continue;

}

backtrack(参数列表); //回溯

path.pop();  // 撤销处理的结点

}

}

小试牛刀——Leetcode 组合、组合总和问题

1.Leetcode第77题 组合

77. 组合 - 力扣(LeetCode) (leetcode-cn.com)

//给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合
//你可以按 任何顺序 返回答案。(所以 1 3 和 3 1 是一种组合)
public class Leetcode_T77 {
    List<List<Integer>> res = new ArrayList<>();
    public List<List<Integer>> combine(int n, int k) {
        Deque<Integer> list = new ArrayDeque<>();
        backtracking(n,k,list,1);
        return res;
    }
    public void backtracking(int n, int k, Deque<Integer> list, int first){
//        边界
        if(list.size() == k){
            res.add(new ArrayList<>(list));
            return;
        }
//        i< 这里进行了剪枝
        for (int i = first; i <= n - (k - list.size()) + 1; i++) {
            list.addLast(i);
            backtracking(n,k,list,i + 1);
//            深度优先遍历有回头的过程,因此递归之前做了什么,递归之后需要做相同操作的逆向操作
//            比如k = 3, 当找到 1 2 3 时,回退3,变为1 2 ,再继续找 1 2 4
            list.removeLast();
        }
    }
}

2.Leetcode第39题 组合总和

39. 组合总和 - 力扣(LeetCode) (leetcode-cn.com)

class Solution {
 List<List<Integer>> res = new ArrayList<>();
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
       int n = candidates.length;
        if (n == 0) {
            return res;
        }
//        排序便于后续提前结束循环
      Arrays.sort(candidates);
//        用栈来存放每一种组合
        Deque<Integer> stack = new LinkedList<>();
        dfs(candidates, target, 0, stack);
        return res;
    }

//    index 是每次搜索起点
    public void dfs(int[] candidates, int target, int index, Deque<Integer> stack) {
        if (target == 0) {
            res.add(new ArrayList<>(stack));
            return;
        }
        for (int i = index; i < candidates.length; i++) {
            if (target - candidates[i] >= 0) {
                stack.addLast(candidates[i]);
                dfs(candidates, target - candidates[i], i, stack);
                stack.removeLast();
            }
        }
}
}

3. Leetcode第40题 组合总和||

40. 组合总和 II - 力扣(LeetCode) (leetcode-cn.com)

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

    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        Arrays.sort(candidates);
        backtracking(candidates, target, 0, new ArrayDeque<>());
        return res;
    }

    public void backtracking(int[] candidates, int target, int first, Deque<Integer> stack) {
        if (target == 0) {
            res.add(new ArrayList<>(stack));
            return;
        }
        for (int i = first; i < candidates.length; i++) {
//            只比39题多这一行代码,遇到重复的continue
            if(i > first && candidates[i] == candidates[i - 1]) continue;
            if(target - candidates[i] >= 0) {
                stack.addLast(candidates[i]);
//                first也变为 + 1
                backtracking(candidates, target - candidates[i], i + 1, stack);
                stack.removeLast();
            }
        }

    }
}

可以看到,这三道题解法基本一样,只有边界递归条件不一样或者具体小细节

第一道是找够个数

第二道却是目标值减小为0并且元素可以重复

第三道也是目标值减小为0但元素不允许重复(遇到重复的跳过即可)

组合总和两道题,先排序可以达到剪枝,因为递增,所以只要遇到大于目标值的,那么后面都不用找了,都不符合

下期更新回溯算法的其他类型题

  • 12
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 18
    评论
评论 18
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

笨笨在努力

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值