【回溯算法经典题目解析】

1. 什么是回溯算法

回溯算法是⼀种经典的递归算法,通常用于解决组合问题、排列问题和搜索问题等。

回溯算法的基本思想:从一个初始状态开始,按照⼀定的规则向前搜索,当搜索到某个状态⽆法前进时,回退到前⼀个状态,再按照其他的规则搜索。回溯算法在搜索过程中维护一个状态树,通过遍历状态树来实现对所有可能解的搜索。

回溯算法的核心思想:“试错”,即在搜索过程中不断地做出选择,如果选择正确,则继续向前搜 索;否则,回退到上一个状态,重新做出选择。回溯算法通常⽤于解决具有多个解,且每个解都需要 搜索才能找到的问题。

2. 回溯算法的模板

void backtrack(vector<int>& path, vector<int>& choice, ...) {
	// 满足结束条件
	if (/*满足结束条件*/) {
		// 将路径添加到结果集中
			res.push_back(path);
		return;
	}
	// 遍历所有选择
	for (int i = 0; i < choices.size(); i++) {
		// 做出选择

			path.push_back(choices[i]);
		// 做出当前选择后继续搜索
	}
	backtrack(path, choices);
	// 撤销选择
	path.pop_back();
}

其中, path 表⽰当前已经做出的选择, choices 表⽰当前可以做的选择。在回溯算法中,我们需 要做出选择,然后递归地调⽤回溯函数。如果满⾜结束条件,则将当前路径添加到结果集中;否则, 我们需要撤销选择,回到上⼀个状态,然后继续搜索其他的选择。 回溯算法的时间复杂度通常较高,因为它需要遍历所有可能的解。但是,回溯算法的空间复杂度较低,因为它只需要维护⼀个状态树。在实际应用中,回溯算法通常需要通过剪枝等方法进⾏优化,以减少搜索的次数,从⽽提高算法的效率。

3. 回溯算法的应用

  • 组合问题

  • 排列问题

  • 子集问题

总结

回溯算法是⼀种⾮常重要的算法,可以解决许多组合问题、排列问题和搜索问题等。回溯算法的核心思想是搜索状态树,通过遍历状态树来实现对所有可能解的搜索。回溯算法的模板非常简单,但是实现起来需要注意⼀些细节,比如如何做出选择、如何撤销选择等。

4. 全排列

首先我们看到这个题目,立马就会想到使用穷举来解决,依次选取一个位置来枚举,但是假如我们这个题目给的数组是100个,那么岂不要写100个for循环来枚举,太麻烦了,我们使用dfs来解决,使用dfs之前我们首先要画出我们的决策树,我们以实例一为例。

 此时就要用到我们的递归方法,当我们使用递归的时候,我们仅需关心某一个节点在干什么事情,只要我们当前的节点没有使用过,我们就可以添加进去,同时还要注意几个细节问题,

那我们就可以直接来写代码了。

class Solution {
    vector<int> path;
    vector<vector<int>> ret;
    bool check[7] = {false}; // 标记下标是否已经使用
public:
    vector<vector<int>> permute(vector<int>& nums) {
        dfs(nums);
        return ret;
    }

    void dfs(vector<int>& nums)
    {
        if(path.size() == nums.size())
        {
            ret.push_back(path);
            return;
        }

        for(int i = 0; i < nums.size(); i++)
        {
            if(check[i] == false)
            {
                path.push_back(nums[i]);
                check[i] = true;
                dfs(nums);
                // 回溯 -> 恢复现场
                path.pop_back();
                check[i] = false;
            }
        }
    }
};

5. 子集

经过之前的全排列我们可以知道,要想解决一个递归的题目,我们就必须要画好我们的决策树,决策树画的越详细,题目越容易做出来,这个题目有两种决策树,我们分别画出来分别写一下代码。第一个决策树是根据元素选与不选来抉择的。

此时我们设计dfs函数的时候,有两个参数dfs(nums, i),nums就表示数组,而i就是表示是否选择这个下标对应的值,当我们选择的时候,就可以直接将nums[i]添加到path中,然后递归到dfs(nums, i+1)下一层,如果不选,就直接递归dfs(nums, i+1)下一层,当我们到叶子节点的时候,就可以返回啦!

class Solution {
    vector<vector<int>> ret;
    vector<int> path;
public:
    vector<vector<int>> subsets(vector<int>& nums) {
        dfs(nums, 0);
        return ret;
    }

    void dfs(vector<int>& nums, int pos)
    {
        if(pos == nums.size())
        {
           ret.push_back(path);
           return;
        }
        // 选
        path.push_back(nums[pos]);
        dfs(nums, pos + 1);
        path.pop_back(); // 恢复现场

        // 不选
        dfs(nums, pos + 1);
    }
};

我们在来画出另外一种决策树,依据每层元素的个数来画,每次选完当前元素时,就从后面元素继续选择。

根据上面的决策树,每一层的节点都是枚举后面的数,所以我们的函数设计时dfs(nums, pos),pos当前层枚举的数,此时我们就要来一个for循环解决,同时我们还能发现此时我们不需要bool数组来检查元素是否被使用过,因为我们通过pos控制每一层的节点都是枚举后面的数,不会美剧到已经使用过的值,那什么时候统计结果呢?在进入for循环之前就可以直接统计结果啦。

class Solution {
    vector<vector<int>> ret;
    vector<int> path;
public:
    vector<vector<int>> subsets(vector<int>& nums) {
        dfs(nums, 0);
        return ret;
    }

    void dfs(vector<int>& nums, int pos)
    {
        ret.push_back(path);
        for(int i = pos; i < nums.size(); i++)
        {
            path.push_back(nums[i]); 
            dfs(nums, i + 1);
            path.pop_back(); // 恢复现场
        }
    }
};

6. 找出所有子集的异或总和再求和

这个题目其实就是子集题目的变种题目,我们依然是使用的dfs来求出子集,只不过此时我们不需要再创建数组存储每个子集,只需要使用一个变量path存储子集的异或结果和一个变量ret存储所有子集的疑惑和结果即可。

class Solution {
    int ret = 0;
    int path = 0;
public:
    int subsetXORSum(vector<int>& nums) {
        dfs(nums, 0);
        return ret;
    }
    void dfs(vector<int>& nums, int pos)
    {
        ret += path;
        for(int i = pos; i < nums.size(); i++)
        {
            path ^= nums[i];
            dfs(nums, i + 1);
            path ^= nums[i]; // 恢复现场
        }
    }
};

7. 全排列 Ⅱ

首先我们这个题目是全排列的一个变种题目,这个题目中要求全排列之后不能出现重复的值,因此我们可以按照上面求全排列的方法求出结果,然后再利用set容器进行去重来解决这个题目,但是比较繁琐,我们可以利用剪枝来求解,我们先画出我们的决策树。

此时我们就可以有两种判断方法,只关心合法的分支,合法我们才去dfs,合法的肯定是当前位置并没有被使用,所以check[i] == false,同时我们还要满足,nums[i]不能和nums[i-1]位置的元素相等,但是我们可以看到左边的分支,如果check[i-1]的位置是已经被使用了,那么虽然nums[i]和nums[i-1]位置的元素相等,我们仍然可以使用这个元素,因为这两个元素不在同一层,最后还有一个条件,如果i==0,那么此时我们肯定是合法的,此时可以直接添加。

此时我们就可以来直接写代码啦!

class Solution {
    vector<vector<int>> ret;
    vector<int> path;
    bool check[8] = { false };
public:
    vector<vector<int>> permuteUnique(vector<int>& nums) {
        sort(nums.begin(), nums.end()); // 先排序
        dfs(nums);
        return ret;
    }
    void dfs(vector<int>& nums)
    {
        if(path.size() == nums.size())
        {
            ret.push_back(path);
        }
        for(int i = 0; i < nums.size(); i++)
        {
            // 剪枝
            if((i == 0 || nums[i-1] != nums[i] || check[i - 1] == true) && check[i] == false)
            {
                path.push_back(nums[i]);
                check[i] = true;
                dfs(nums);
                // 恢复现场
                path.pop_back();
                check[i] = false;
            }
        }
    }
};

8. 电话号码的字母组合

看到这个题目,我们首先想到的就是for循环去解决问题,但是如果传入的digits特别多,那么就要使用很多的for循环,因此这个题目我们也可以使用dfs来解决,首先我们要做的就是画出我们的决策树。

首先我们就需要根据电话进行数字与字母的映射,我们这里直接创建一个数组即可,随后的工作就和全排列的思路差不多,直接上手代码。

class Solution {
    string hash[10] = {"","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
    string path;
    vector<string> ret;
public:
    vector<string> letterCombinations(string digits) {
        if(digits.empty())
            return ret;
        dfs(digits, 0);
        return ret;
    }

    void dfs(string& digits, int pos)
    {
        if(pos == digits.size())
        {
            ret.push_back(path);
            return;
        }
        string tmp = hash[digits[pos] - '0'];
        for(int i = 0; i < tmp.size(); i++)
        {
            path += tmp[i];
            dfs(digits, pos + 1);
            path.pop_back(); // 恢复现场
        }
    }
};

9. 括号生成

对于这样的问题,我们首先要了解的就是什么是有效的括号组合呢?第一就是左括号的数量等于右括号的数量,其次是从头开始的任意一个字串,左括号的数量大于右括号的数量。接下来我们就要做的就是画出决策树,决策树画的越详细,题目越容易做出来。

随后我们就可以直接来手写代码啦!!!

class Solution {
    vector<string> ret;
    string path;
    int left = 0;
    int right = 0;
    int n1 = 0;
public:
    vector<string> generateParenthesis(int n) {
        n1 = n;
        dfs();
        return ret;
    }

    void dfs()
    {
        if(n1 == right)
        {
            ret.push_back(path);
            return;
        }
        // 选左括号
        // 左括号的数量小于n
        if(left < n1)
        {
            path += '(';
            left++;
            dfs();
            path.pop_back(); // 恢复现场
            left--;
        }

        // 选右括号
        // 右括号的数量小于左括号的数量
        if(left > right)
        {
            path += ')';
            right++;
            dfs();
            path.pop_back(); // 恢复现场
            right--;
        }
    }
};

10. 组合

这个题目其实就是上面的子集的变种题目,我们依然是先来画出我们的决策树。

随后我们就直接来写代码啦。

class Solution {
    vector<vector<int>> ret;
    vector<int> path;
    int n1, k1;
public:
    vector<vector<int>> combine(int n, int k) {
        n1 = n;
        k1 = k;
        dfs(0);
        return ret;
    }

    void dfs(int pos)
    {
        if(k1 == path.size())
        {
            ret.push_back(path);
            return;
        }
        for(int i = pos; i < n1; i++)
        {
            path.push_back(i + 1);
            dfs(i + 1);
            path.pop_back(); // 恢复现场
        }
    }
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/784323.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

背包问题转换

如何转换成背包问题呢&#xff0c;我们可以把每个质数当成一个重量 #define _CRT_SECURE_NO_WARNINGS #include<bits/stdc.h> using namespace std;#define int long long int record[1005]; void fun() {//record[2] 1;for (int i 2; i < 1000; i) {if (!record[…

JDBC和数据库连接池

1 JDBC概述 1.1 数据持久化 持久化(persistence)&#xff1a;把数据保存到可掉电式存储设备中以供之后使用。大多数情况下&#xff0c;特别是企业级应用&#xff0c;数据持久化意味着将内存中的数据保存到硬盘上加以”固化”&#xff0c;而持久化的实现过程大多通过各种关系数…

鸿蒙语言基础类库:【@ohos.url (URL字符串解析)】

URL字符串解析 说明&#xff1a; 本模块首批接口从API version 7开始支持。后续版本的新增接口&#xff0c;采用上角标单独标记接口的起始版本。开发前请熟悉鸿蒙开发指导文档&#xff1a;gitee.com/li-shizhen-skin/harmony-os/blob/master/README.md点击或者复制转到。 导入…

第一百四十九节 Java数据类型教程 - Java子字符串、字符串转换

Java数据类型教程 - Java子字符串 获取子字符串 我们可以使用substring()方法来获取字符串的子部分。 我们可以将开始索引作为参数&#xff0c;并返回一个从开始索引开始到字符串结尾的子串。 我们还可以将开始索引和结束索引作为参数。 它返回从开始索引开始的子字符串和小…

使用预加载库优化 PostgreSQL 函数#postgresql认证

在 POSTGRESQL 中执行函数和过程 为了理解 PostgreSQL 的工作原理&#xff0c;我们首先要看一个简单的函数调用。下一个清单显示了一些简单的PostGIS代码&#xff1a; PgSQL test# timing Timing is on. test# SELECT * FROM hans.points WHERE id 1;id │ …

【工具分享】零零信安攻击面管理平台

文章目录 00SEC-ASM™功能介绍功能演示 最近闲来无事&#xff0c;到处网上冲浪&#xff0c;无意间发现了长亭云图攻击面管理平台&#xff0c;无奈需要授权才能使用&#xff0c;于是就找到了平替&#xff1a;零零信安攻击面管理平台。 长亭云图攻击面管理平台&#xff1a;https:…

代码随想录-Day50

1143. 最长公共子序列 给定两个字符串 text1 和 text2&#xff0c;返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 &#xff0c;返回 0 。 一个字符串的 子序列 是指这样一个新的字符串&#xff1a;它是由原字符串在不改变字符的相对顺序的情况下删除某些…

Kotlin linkedMapOf filterKeys

Kotlin linkedMapOf filterKeys fun main(args: Array<String>) {val lhm linkedMapOf<String, Any>(Pair("name", "phil"), //因为key相同都为 name&#xff0c;被后面的覆盖。Pair("year", 2024),Pair("name", "f…

【TB作品】51单片机 Proteus仿真 00013红外proteus仿真循迹避障小车

实验报告&#xff1a;智能小车系统设计与实现 一、背景介绍 本实验旨在设计并实现一个基于STC89C52单片机控制的智能小车系统。该系统通过超声波传感器进行避障&#xff0c;通过红外接收器实现远程控制&#xff0c;同时具备循迹功能。整个系统的核心是单片机&#xff0c;它通…

初识c++(命名空间,缺省参数,函数重载)

一、命名空间 1、namespace的意义 在C/C中&#xff0c;变量、函数和后面要学到的类都是大量存在的&#xff0c;这些变量、函数和类的名称将都存在于全 局作用域中&#xff0c;可能会导致很多冲突。使用命名空间的目的是对标识符的名称进行本地化&#xff0c;以避免命名 冲突…

python对象

类 我们目前所学习的对象都是Python内置的对象但是内置对象并不能满足所有的需求&#xff0c;所以我们在开发中经常需要自定义一些对象类&#xff0c;简单理解它就相当于一个图纸。在程序中我们需要根据类来创建对象类就是对象的图纸&#xff01;我们也称对象是类的实例&#…

caeses软件许可优化解决方案

Caeses软件介绍 CAESES是一款十分很不错的三维建模仿真的软件。它功能很大、优化效率高、可以自动化优化、分析工具快速 CAESES拥有多种不同的试验设计及单目标、多目标优化算法&#xff0c;能够根据仿真计算评估的结果。软件可以帮助用户轻松的打造出自各种船舶、汽车、航空航…

grafana数据展示

目录 一、安装步骤 二、如何添加喜欢的界面 三、自动添加注册客户端主机 一、安装步骤 启动成功后 可以查看端口3000是否启动 如果启动了就在浏览器输入IP地址&#xff1a;3000 账号密码默认是admin 然后点击 log in 第一次会让你修改密码 根据自定义密码然后就能登录到界面…

1-3分钟爆款视频素材在哪找啊?这9个热门爆款素材网站分享给你

在如今快节奏的时代&#xff0c;短视频已成为吸引观众注意力的黄金手段。然而&#xff0c;要制作出1-3分钟的爆款视频&#xff0c;除了创意和剪辑技巧外&#xff0c;选择合适的素材至关重要。那么&#xff0c;哪里可以找到那些能让你的视频脱颖而出的爆款素材呢&#xff1f;不用…

顶会FAST24最佳论文|阿里云块存储架构演进的得与失-1.引言

今年早些时候&#xff0c;2月份举办的全球计算机存储顶会USENIX FAST 2024&#xff0c;最佳论文来自阿里云&#xff0c;论文名称《What’s the Story in EBS Glory: Evolutions and Lessons in Building Cloud Block Store》 &#xff0c;论文详尽地探讨了阿里云在过去十年中开…

ASAN排查程序中内存问题使用总结

简介 谷歌有一系列Sanitizer工具&#xff0c;可用于排查程序中内存相关的问题。常用的Sanitizer工具包括&#xff1a; Address Sanitizer&#xff08;ASan&#xff09;&#xff1a;用于检测内存使用错误。Leak Sanitizer&#xff08;LSan&#xff09;&#xff1a;用于检测内存…

YOLOv9:一个关注信息丢失问题的目标检测

本文来自公众号“AI大道理” 当前的深度学习方法关注的是如何设计最合适的目标函数&#xff0c;使模型的预测结果最接近地面的真实情况。同时&#xff0c;必须设计一个适当的体系结构&#xff0c;以方便获取足够的预测信息。 现有方法忽略了一个事实&#xff0c;即输入数据在逐…

docker安装以及简单使用

如何安装安装 yum install -y yum-utils yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo # 列出可用的版本 yum list docker-ce.x86_64 --showduplicates | sort -r yum install -y docker-ce-23.0.6-1.el8 #开机自动启动 …

Yolov10训练,转化onnx,推理

yolov10对于大目标的效果好&#xff0c;小目标不好 一、如果你训练过yolov5&#xff0c;yolov8&#xff0c;的话那么你可以直接用之前的环境就行 目录 一、如果你训练过yolov5&#xff0c;yolov8&#xff0c;的话那么你可以直接用之前的环境就行 二、配置好后就可以配置文件…

DBA 数据库管理

数据库&#xff1a;存储数据的仓库 数据库服务软件&#xff1a; 关系型数据库&#xff1a; 存在硬盘 &#xff0c;制作表格的 数据库的参数 [rootmysql50 ~]# cat /etc/my.cnf.d/mysql-server.cnf 主配置文件 [mysqld] datadir/var/lib/mysql 存放数据库目录…