当前位置: 首页 > news >正文

回溯-day65

回溯

什莫事回溯

回溯法也可以叫做回溯搜索法,它是一种搜索的方式

回溯的本质是穷举,穷举所有可能,然后选出我们想要的答案,如果想让回溯法高效一些,可以加一些剪枝的操作,但也改不了回溯法就是穷举的本质

回溯法并不高效为什么还要用它呢?
因为没得选,一些问题能暴力搜出来就不错了,撑死了再剪枝一下,还没有更高效的解法

回溯法,一般可以解决如下几种问题:

  • 组合问题:N个数里面按一定规则找出k个数的集合
  • 切割问题:一个字符串按一定规则有几种切割方式
  • 子集问题:一个N个数的集合里有多少符合条件的子集
  • 排列问题:N个数按一定规则全排列,有几种排列方式
  • 棋盘问题:N皇后,解数独等等

试错与回退
回溯法从解空间树的根节点出发,按深度优先策略逐层探索。若当前路径不满足约束条件或无法达到目标,则回退到上一状态(撤销最后一步选择),继续尝试其他分支

剪枝优化
在搜索过程中,通过预判当前路径的有效性,提前终止无效分支的探索(例如检测到冲突时),避免不必要的计算

递归算法!

  1. 确定递归函数的参数和返回值:
  2. 确定终止条件:
  3. 确定单层递归的逻辑:

回溯算法!

/*
vector<vector<int>> result;  // 存储所有解
vector<int> path;            // 当前路径
*/

void backtracking(参数) {
    if (终止条件) {
        存放结果;                   //result.push_back(path);
        return;
    }

    for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
        处理节点;                   // if (剪枝条件) continue; // 跳过无效分支
                                   // path.push_back(选择);   // 记录选择
                                       
        backtracking(路径,选择列表); // 递归
        回溯,撤销处理结果             // path.pop_back(); 
    }
}

组合

77. 组合 - 力扣(LeetCode)

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

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

直接的算法是for循环 ,遍历每个位数可以选择的数😒
当k=2时 ,用两个for循环

for(int i=0;i<=n;i++){
for(int y=i+1;y<=n;y++) //因为是组合不是排列,不用在选出现过的数
 cout<<i<< " "<<y<<endl; 
}

如果n为100,k为50呢,如何写那么多for循环呢,while …?
虽然想暴力搜索,但是用for循环嵌套好像写不出来🤔

要解决 n为100,k为50的情况,暴力写法需要嵌套50层for循环,
那么回溯法就用递归来解决嵌套层数的问题

递归来做层叠嵌套(可以理解是开k层for循环),每一次的递归中嵌套一个for循环,那么递归就可以用于解决多层嵌套循环的问题了

由于回溯法解决的问题都可以抽象为树形结构(N叉树),用树形结构来理解回溯就容易多了🙂
![[Pasted image 20250414204405.png]]

图中可以发现n相当于树的宽度,k相当于树的深度
图中每次搜索到了叶子节点,我们就找到了一个结果
相当于只需要把达到叶子节点的结果收集起来,就可以求得 n个数中k个数的组合集合。

剪枝优化
如果for循环选择的起始位置之后的元素个数 已经不足 我们需要的元素个数了,那么就没有必要搜索了,如图4不用了

优化之后的for循环是:

for (int i = startIndex; i <= n - (k - path.size()-1); i++) // i为本次搜索的起始位置
class Solution {
private:
    vector<vector<int>> result;
    vector<int> path;
    void backtracking(int n, int k, int startIndex) {
        if (path.size() == k) {
            result.push_back(path);
            return;
        }
        for (int i = startIndex; i <= n - (k - path.size()) + 1; i++) { // 优化的地方
            path.push_back(i); // 处理节点
            backtracking(n, k, i + 1);
            path.pop_back(); // 回溯,撤销处理的节点
        }
    }
public:

    vector<vector<int>> combine(int n, int k) {
        backtracking(n, k, 1);
        return result;
    }
};

组合总和III

找出所有相加之和为 nk 个数的组合,且满足下列条件:

  • 只使用数字1到9
  • 每个数字 最多使用一次
    返回 所有可能的有效组合的列表 。该列表不能包含相同的组合两次,组合可以以任何顺序返回。
    216. 组合总和 III - 力扣(LeetCode)

😍

class Solution {
public:
   vector<vector<int>> result;
    vector<int> path;
    int sum=0;
    void backtracking(int n, int k, int startIndex) {
        if (path.size() == k&&sum==n) {
            result.push_back(path);
            return;
        }
        for (int i = startIndex; i <= 9 ; i++) {
            path.push_back(i);
            sum+=i;
            backtracking(n, k, i + 1);
            sum-=i;
            path.pop_back();
        }
    }
    vector<vector<int>> combinationSum3(int k, int n) {
        backtracking( n,  k, 1);
        return result;
    }
};

剪枝优化 😕

其实这里sum这个参数也可以省略,每次targetSum (n)减去选取的元素数值,然后判断是否targetSum为0了

for循环选择的起始位置之后的元素个数 已经不足 我们需要的元素个数了,那么就没有必要搜索了

for (int i = startIndex; i <= n - (k - path.size()-1); i++) // i为本次搜索的起始位置

已选元素总和如果已经大于n(图中数值为4)了,那么往后遍历就没有意义了

if (sum > n) { // 剪枝操作
    return;
}

确定终止条件
k其实就已经限制树的深度,因为就取k个元素,树再往下深了没有意义。
所以如果path.size() 和 k相等了,就终止

优化结果🆗

class Solution {
private:
    vector<vector<int>> result; 
    vector<int> path; 
    void backtracking(int targetSum, int k, int sum, int startIndex) {
        if (sum > targetSum) { // 剪枝操作
            return; 
        }
        if (path.size() == k) {
            if (sum == targetSum) result.push_back(path);
            return; 
        }
        for (int i = startIndex; i <= 9 - (k - path.size()) + 1; i++) { // 剪枝
            sum += i; 
            path.push_back(i); 
            backtracking(targetSum, k, sum, i + 1); 
            sum -= i; 
            path.pop_back(); 
        }
    }

public:
    vector<vector<int>> combinationSum3(int k, int n) {
        backtracking(n, k, 0, 1);
        return result;
    }
};

电话号码的字母组合

17. 电话号码的字母组合 - 力扣(LeetCode)
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。

给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。

确定回溯函数参数

  • 字符串s来收集叶子节点的结果
  • k是记录遍历第几个数字

确定终止条件

  • 遍历第几个数字等于 输入的数字个数
  • 或已存入数字个数等于 输入的数字个数

确定单层遍历逻辑

  • for循环
    本题每一个数字代表的是不同集合,也就是求不同集合之间的组合,
    而77. 组合 (opens new window)和216.组合总和III (opens new window)都是求同一个集合中的组合!
class Solution {
public:
const string letterMap[10] = {
    "", // 0
    "", // 1
    "abc", // 2
    "def", // 3
    "ghi", // 4
    "jkl", // 5
    "mno", // 6
    "pqrs", // 7
    "tuv", // 8
    "wxyz", // 9
};
    vector<string> result;
    string path;
    int sum=0;
    void backtracking(string digits, int k) {
        if (path.size() ==digits.size()) {
            result.push_back(path);
            return;
        }
        int digit = digits[k] - '0';        
        string letters = letterMap[digit];
            for(auto t:letterMap[digit]){
                path.push_back(t);
                backtracking(digits, k+1);
                path.pop_back();
            }
    }
    vector<string> letterCombinations(string digits) {
         if (digits.size() == 0) {
            return result;
        }
        backtracking(digits, 0);
        return result;
    }
};

相关文章:

  • Neovim安装及lazy配置
  • ADI的BF561双核DSP怎么做开发,我来说一说(十六)触摸屏的设计
  • QT Sqlite数据库-教程002 查询数据-下
  • 操作系统导论——第19章 分页:快速地址转换(TLB)
  • Vue.js 项目中 vue.config.js 常用配置项解析
  • bash的特性-命令和文件自动补全
  • Linux - 系统服务管理(Systemd)
  • qt中的正则表达式
  • 【记录】Docker 镜像
  • Java-面向对象
  • ffprobe是如何处理命令行参数的.
  • BFD:网络链路检测与联动配置全攻略
  • 易境通WMS系统代理仓解决方案:让代理仓管理无后顾之忧!
  • 07软件测试需求分析案例-修改用户信息
  • 手机端可部署的开源大模型; 通义千问2.5训练和推理需要的内存和外存
  • 【DDR 内存学习专栏 1.4 -- DDR 的 Bank Group】
  • 机器学习:让数据开口说话的科技魔法
  • 网络基础和socket
  • 面试宝典(C++基础)-01
  • AlexNet神经网络详解及VGGNet模型和
  • 专访|前伊核谈判顾问:伊朗不信任美国,任何核协议都会有中俄参与
  • 杭州萧山区两宗地块收金约44.73亿元,最高溢价率74.4%
  • 人民网评“我愿意跟他挨着”热搜第一:充满温暖力量的七个字
  • 民生访谈|电动自行车换新补贴会优化吗?今年汛期情况如何?市应急局回应
  • 周继红连任中国跳水协会主席
  • 现货黄金价格站上3400美元,今年迄今累涨逾29%