回溯算法的本质理解

回溯算法就是用递归代替可能无限嵌套的for循环。

例1:

这么说可能不好理解,让我们以一道经典题目77. 组合来引入。

我们来看示例1 ,需要从1~4中,找出所有可能的2个数的组合,很自然从数学的角度 想到的顺序[1,2],[1,3],[1,4],[2,3],[2,4],[3,4],从代码的角度 其实就是2层for循环,外层从i=1到3,内层从i+1到4。其实就是要找的k个数的组合对应k层for循环

我们会发现k不是固定的,也就是for循环的层数是不确定的,就算真的确定,当k取20时,又不能真的写20层for循环。

所以我们用递归来代替

我们知道递归的本质,是把一个大问题拆分成同类型的小问题。这样"分解"的关系,天然就形成了一棵树:所以几乎所有的递归过程都可以抽象成一棵树来表示[1](#1)

以下就是树的表示。(感谢代码随想录

你会发现递归的每一层,本质就是一层for循环(第一层取i=1到4,第二层取i+1到4)

这里给出回溯的模版

text 复制代码
void backtracking(参数) {
    if (终止条件) {
        存放结果;
        return;
    }

    for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {//横向
        处理节点;
        backtracking(路径,选择列表); // 递归(纵向)
        回溯,撤销处理结果
    }
}

题解

c++ 复制代码
class Solution {
public:
    vector<int> path;//存路径
    vector<vector<int>> res;//存结果
    //回溯模版
    void backtracking(int n,int k,int startIndex){
        //终止条件:满足k个数的大小
        if(path.size()==k){
            res.push_back(path);
            return;
        }
        //横向选取数:是1234还是234由startIndex决定
        for(int i=startIndex;i<=n-(k-path.size())+1;i++){
            path.push_back(i);//加入路径
            backtracking(n,k,i+1);//回溯
            path.pop_back();//删除元素
        }
    }
    
    vector<vector<int>> combine(int n, int k) {
        backtracking(n,k,1);
        return res;
    }
};

这里有一个非常常见的剪枝优化,就是把i<=n改成i<=n-(k-path.size())+1,这里就不过多叙述


例2:

我们再来看一道题17. 电话号码的字母组合

依旧借助树形结构理解

c++ 复制代码
class Solution {
public:
    vector<string> res;
    string path;
    //输入的电话号对应的字母
    const string phoneMap[10]={"","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
    
    void backtracking(string digits,int index){//index表示遍历到第几个数字
        //终止条件
        if(digits.size()==index){
            res.push_back(path);
            return;
        }
        
        int digit=digits[index]-'0';//字母变数字
        string letter=phoneMap[digit];
        for(int i=0;i<letter.size();i++){
            path.push_back(letter[i]);
            backtracking(digits,index+1);
            path.pop_back();
        }
    }
    vector<string> letterCombinations(string digits) {
        backtracking(digits,0);
        return res;
    }
};

总结

以上就是回溯算法中的组合问题,分别从一个集合中选取元素和多个集合中选取元素。


  1. 从二叉树到回溯算法的过渡是及其自然的,我们先从图形化便于理解的树形,了解到它的多种递归遍历方法;再接触回溯算法,从递归逻辑中自己抽象出树形结构去解题。也就是我们从树形接触递归,再从递归中抽象出树形,是不是很有意思 ↩︎
相关推荐
minji...1 天前
Linux 线程同步与互斥(三) 生产者消费者模型,基于阻塞队列的生产者消费者模型的代码实现
linux·运维·服务器·开发语言·网络·c++·算法
语戚1 天前
力扣 968. 监控二叉树 —— 贪心 & 树形 DP 双解法递归 + 非递归全解(Java 实现)
java·算法·leetcode·贪心算法·动态规划·力扣·
skywalker_111 天前
力扣hot100-7(接雨水),8(无重复字符的最长子串)
算法·leetcode·职场和发展
bIo7lyA8v1 天前
算法稳定性分析中的输入扰动建模的技术9
算法
CoderCodingNo1 天前
【GESP】C++三级真题 luogu-B4499, [GESP202603 三级] 二进制回文串
数据结构·c++·算法
sinat_286945191 天前
AI Coding 时代的 TDD:从理念到工程落地
人工智能·深度学习·算法·tdd
炽烈小老头1 天前
【 每天学习一点算法 2026/04/12】x 的平方根
学习·算法
ASKED_20191 天前
从排序到生成:腾讯广告算法大赛 2025 baseline解读
人工智能·算法
田梓燊1 天前
leetcode 160
算法·leetcode·职场和发展