回溯算法理解

回溯算法的模板

void backtracking(参数) {
    if (终止条件) {
        存放结果;
        return;
    }
 
    for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {//横向遍历
        处理节点;//这里一般可能进行剪枝操作
        backtracking(路径,选择列表); // 递归,纵向遍历
        回溯,撤销处理结果
    }
}

是一个基本的回溯算法模板,其中的关键点包括:

  1. 定义终止条件:当满足终止条件时,表示找到了一个解或者不再需要继续搜索,可以进行相应的操作,如输出结果或返回。
  2. 遍历选择列表:遍历所有可能的选择,通常使用循环结构,对于每个选择,进行相应的操作。
  3. 做出选择:根据当前选择,更新状态或路径,表示对问题的一次选择。
  4. 递归进入下一层决策树:根据当前选择,进入下一层决策树,即进行下一步的选择。
  5. 撤销选择:在回溯到上一层之前,需要撤销当前选择,恢复状态或路径,以便进行下一个选择。
  6. 在实际应用中,根据具体问题的不同,模板中的代码需要进行相应的修改和扩展,以适应问题的特点和约束条件。同时,通过剪枝、优化等技巧,可以对模板进行改进,提高算法的效率。

需要注意的是,回溯算法是一种暴力搜索的方法,解空间的规模很大时,可能会导致算法效率低下。因此,在使用回溯算法时,需要根据问题的规模和特点进行合理的优化和剪枝,以提高算法的性能。

示例:组合和排序

组合

77. 组合 - 力扣(LeetCode)

给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。

你可以按 任何顺序 返回答案。

示例 1:

输入:n = 4, k = 2
输出:
[
  [2,4],
  [3,4],
  [2,3],
  [1,2],
  [1,3],
  [1,4],
]

示例 2:

输入:n = 1, k = 1
输出:[[1]]

代码:

class Solution {
     public List<List<Integer>> combine(int n, int k) {
        List<Integer>  outList =new ArrayList<>();
        List<List<Integer>> res =new ArrayList<>();
        List<Integer>  cur =new ArrayList<>();
        for(int i =0;i<n;i++){
            cur.add(i+1);
        }
        backtring(res,cur,outList,n,k,0);
        return res;
    }

    public void backtring(List<List<Integer>> res,List<Integer> cur ,List<Integer> outList,int n,int k,int index){
        if(outList.size()==k){
            res.add(new ArrayList<Integer> (outList));//深拷贝
            return;
        }
        for(int i=index;i<n;i++){
            outList.add(cur.get(i));
            backtring(res,cur,outList,n,k,i+1);
            outList.remove(outList.indexOf(cur.get(i)));
        }
    }
}

优化

public List<List<Integer>> combine(int n, int k) {
        List<Integer>  outList =new ArrayList<>();
        List<List<Integer>> res =new ArrayList<>();
        backtring(res,outList,n,k,1);
        return res;
    }

    public void backtring(List<List<Integer>> res ,List<Integer> outList,int n,int k,int index){
         // 剪枝:如果剩余数字数量不够补满 k 个,直接返回
        if (outList.size() + (n - index + 1) < k) return;
        if(outList.size()==k){
            res.add(new ArrayList<Integer> (outList));
            return;
        }
        for(int i=index;i<=n;i++){
            outList.add(i);
            backtring(res,outList,n,k,i+1);
            outList.remove(outList.size()-1);
        }
    }

全排列

46. 全排列---回溯-优快云博客

class Solution {
   public List<List<Integer>> permute(int[] nums) {
        List<List<Integer>> res = new ArrayList<>();
        List<Integer> cur = new ArrayList<>();
        for(int i=0;i<nums.length;i++){
            cur.add(nums[i]);
        }

        backtraing(res,cur,nums.length,0);
        return res;
    }

    public void backtraing(List<List<Integer>> res,List<Integer> cur,int n,int index){

        if(index==n){
            res.add(new ArrayList<>(cur));
            return;
        }

        for(int i= index;i<n;i++){
            Collections.swap(cur,i,index);
            backtraing(res,cur,n,index+1);
            Collections.swap(cur,i,index);
        }
    }
}

为什么组合用 i+1,而排列用 index+1?

组合(Combination)是“选 k 个”,顺序不重要

比如从 [1,2,3,4] 选出两个:[1,4] 和 [4,1] 是同一个组合

排列(Permutation)是“排 n 个”,顺序重要

比如 [1,4][4,1] 是两个不同的排列。


1️⃣ 组合(choose k)回溯逻辑

  • 每个数字只能选或不选

  • 每条路径是 升序选择(保证不重复)

  • 所以用 start/index 来限制下一次选择从哪里开始

🔥 下一层从 i+1 开始(不能再选择前面的数字,不然会重复!)

2️⃣ 排列(permute)回溯逻辑

  • 每个数字的位置都要尝试

  • 需要交换数字来“固定”当前 index 位置

  • 下一层必须处理 index+1(不是 i+1)

🔥 排列永远用 index+1,而不是 i+1!

因为你是在固定 index 的数,然后进入处理下一个 index。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值