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

算法-回溯算法总结

回溯与递归的区别

回溯的本质是穷举,回溯一定代表有递归
递归就一直往深处递归就好了,但是回溯还伴随着递归结束之后的”回溯操作“,例如递归中处理的+1,在回溯中要-1。

回溯的算法思路

一般都是返回void,参数不能一下子全部想定,都是边写,边设定要传入的参数

void transback(参数) {
	if (条件) {
		保存结果;
		return;
	}
	for (当下的宽度遍历) {
		递归处理;
		递归
		回溯
	}
}

遍历的范围就是宽度,迭代的次数就是深度,如此遍历下来。
在这里插入图片描述

剪枝操作

有些遍历明显不符合要求了,就可以不遍历,于是剪枝操作让这部分不遍历。

例题

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

  1. 只使用数字1到9
  2. 每个数字 最多使用一次
  3. 返回 所有可能的有效组合的列表 。该列表不能包含相同的组合两次,组合可以以任何顺序返回

思路

其实就相当于在1-9里面,找k个不重复的数的组合,并且这些组合里的数相加等于n。
本质上就是寻找需要的组合,无序,只不过在回溯的时候,可能要多处理一下关于和的问题

  1. 从宽度遍历,1-9的遍历,
  2. 处理操作,例如将这个数加入容器中,接着深度遍历,就看 这个数+1 至9之间再次遍历然后递归,直到找到了k个数,且他们的和为题目要求的,返回一个结果 (递归操作)
  3. 那么遍历递归完“内部”的数之后,需要“外部”的下一个数及其子路径的遍历,因此要将这一个数从容器中去掉,等待下一次遍历。(PS:这里的内部外部是相对于本层循环来说的,这一行就是回溯操作)
  4. 然后就是退出条件,当找到了k个之后,无论怎么样都会退出,但如果你的和是等于n,那么你的结果留下来之后,你再退出。

思路完了之后,就先确认退出条件,不一定准确,但构思一下,然后就写其中的逻辑

代码

class Solution {
public:
//宽度是从1-9
    void findsum (int maxsize, int targetsum, int basestart, int sum) {
        if (sum > targetsum) return;
        else if (temp.size() == maxsize && sum == targetsum) {
            result.push_back(temp);
            return;
        }
        if (temp.size() == maxsize) return;
        
        for (int i = basestart; i <= end; i++) {
            temp.push_back(i);
            sum += i;
            if (sum > targetsum) { // 提前剪枝
                temp.pop_back();
                sum -= i;
                return;
            }
            findsum(maxsize, targetsum, i + 1, sum);
            temp.pop_back();
            sum -= i;
        }
    }
    vector<vector<int>> combinationSum3(int k, int n) {
        findsum (k, n, start, 0);
        return result;
    }
private:
    vector<int> temp;	//定义了一个临时数组
    vector<vector<int>> result;		//定义了一个结果数组
    int start = 1;	//遍历头,因为题目中说1-9
    int end = 9;
};

问题

我自己做的时候就是剪枝这个地方出了问题
你要么不剪,要么剪完
剪了也记得要把回溯要做的东西给做了,不然会出现问题,例如这一次加的数他虽然没用了,但不做回溯处理,他就一直加在那里,会对后面结果有影响

if (sum > targetsum) { // 提前剪枝
    temp.pop_back();
    sum -= i;
    return;
}

另一道相似题目

给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。
candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。
对于给定的输入,保证和为 target 的不同组合数少于 150 个。

思路

这一题与上一题不同在于可以重复,那么为了保证有重复的数,但组合不同,可以这样:
在这里插入图片描述
每一次从几开头就从几开始遍历,避免了1->1->1->2和1->2->1->1这样重复的情况

这里有两种写法:
第一种写法,是我自己的,这个重点是要先进行排序,因为是在回溯的for里先判断是否sum > target的,然后就返回了,这样做的坏处就是,如果candidates是无序的,后面的数比前面的数要小,就会导致后面的数也不会遍历了,直接就整个setsum函数就return了。
所以必须先排序。

class Solution {
public:
    void setsum (vector<int>& candidates, int startIndex, int target, int sum) {
        if (sum == target) {
            result.push_back(temp);
            return;
        }
        for (int i = startIndex; i < candidates.size(); i++) {
            temp.push_back(candidates[i]);
            sum += candidates[i];
            // 不同之处/
            if (sum >target) {
                temp.pop_back();
                sum -= candidates[i];
                return;
            }
            setsum (candidates, i, target, sum);
            temp.pop_back();
            sum -= candidates[i];
        }
    }
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        sort(candidates.begin(), candidates.end()); 
        setsum(candidates, startIndex, target, initSum);
        return result;
    }
private:
    vector<int> temp;
    vector<vector<int>> result;
    int initSum = 0;
    int startIndex = 0;
};

第二种写法:
这里就直接在开头判断,还是能正常进行其他递归遍历的。

class Solution {
public:
    void setsum (vector<int>& candidates, int startIndex, int target, int sum) {
        if (sum == target) {
            result.push_back(temp);
            return;
        }
        //不同之处
        if(sum > target) return;
        for (int i = startIndex; i < candidates.size(); i++) {
            temp.push_back(candidates[i]);
            sum += candidates[i];
            setsum (candidates, i, target, sum);
            temp.pop_back();
            sum -= candidates[i];
        }
    }
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        setsum(candidates, startIndex, target, initSum);
        return result;
    }
private:
    vector<int> temp;
    vector<vector<int>> result;
    int initSum = 0;
    int startIndex = 0;
};

相关文章:

  • 灰色地带规避:知识产权校验API的商标库模糊匹配算法
  • android studio开发文档
  • python从入门到精通(二十六):python文件操作之Word全攻略(基于python-docx)
  • 【JAVA架构师成长之路】【电商系统实战】第11集:秒杀系统防刷实战(验证码 + 用户行为黑名单)
  • linux声音框架alsa的api学习之wav文件解析
  • DIY Tomcat:手写一个简易Servlet容器
  • Conda常用命令汇总
  • MySQL索引数据结构
  • 深度剖析 打造大模型时代的可信AI:技术创新与安全治理并重
  • 【从零开始学习计算机科学】数字逻辑(四)数字系统设计
  • CGI程序刷新共享内存视频流到HTTP
  • Ubuntu通过局域网共享文件夹实现文件夹的连接
  • FastAPI常用的组件库
  • Android JSON与对象转换工具类:支持复杂数据结构
  • 如何在unity中完整录制一段动画
  • C# 多线程编程完全指南:从基础到高级应用
  • 物联网实时数据存储方案选择
  • 山东大学计算机科学与技术学院软件工程实验日志(更新中)
  • 【蓝桥杯集训·每日一题2025】 AcWing 5538. 回文游戏 python
  • 文本Embedding
  • 一个小网站一般多少钱/百度热搜榜
  • 台州建设局招标投标网站/留号码的广告网站
  • 呼伦贝尔网站建设维护/贵州seo技术培训
  • 不良网站进入窗口/列举五种网络营销模式
  • 为什么自己做的网站别人打不开/电商seo搜索优化
  • 有哪些做投行网站/做网页设计一个月能挣多少