Leetcode—回溯算法

news2025/4/13 16:31:37

回溯算法的本质

是穷举,穷举所有可能,然后选出合适的答案,一般用于解决以下类型的问题:

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

回溯算法模板:

返回值一般为void

void backtravel(参数)

终止条件

if (终止条件)
{
    存放结果
    return;
}

回溯搜索过程

for(循环条件)//实现横向遍历
{
    处理节点;
    backtravel();//自己调用自己,进行递归,实现纵向遍历
    回溯,撤销处理结果
}
//本质上实现了穷举

第77题 组合

在这里插入图片描述在这里插入图片描述
解题思路

class Solution {
public:

    vector<vector<int>> result;//存放符合条件结果的集合(大集合)
    vector<int> res;//存放符合条件的结果集合(小集合)
    void backtravel(int n,int k,int start)
    {
        if(res.size() == k)//终止条件,当存放结果满足条件,放入大集合
        {
            result.push_back(res);
            return;
        }
        for(int i = start;i <= n;i++)
        {
            res.push_back(i);//把挑选的第一个数放入res中
            //开始挑选第二个数
            backtravel(n,k,i+1);
            //找到满足的结果之后,再找下一个满足条件的组合,需要把刚刚的数剔除
            res.pop_back();
        }
    }
    vector<vector<int>> combine(int n, int k) {
        //直接用for循环暴力求解会有一个问题,即k为多少就需要多少层for循环,
        //当k特别大的时候,本身代码实现就是不可能的,
        //因此采用回溯,精简代码量,但本质上还是穷举,时间复杂度上没有多少精简

        //回溯三部曲
        //定义回溯函数
        backtravel(n,k,1);//第三个参数表示开始挑选元素的位置
        
        return result;

    }
};

第216题 组合总和Ⅲ

在这里插入图片描述
在这里插入图片描述
解题思路:与上一题类似,同样是使用回溯,这里面加了一个剪枝操作,即当剩余待选数字已经凑不够k个数,就没有必要再选了。

class Solution {
public:
vector<vector<int>> result;//存放大结果
vector<int> res;//存放小结果
void backTravel(int start, int k, int n,int sum)
{
	//若当前求和已经大于n了,直接结束本次递归
	if (sum > n)
		return;
	//当组合大小满足条件时,判断求和是否满足
	if (res.size() == k)
	{
		if (sum == n)
			result.push_back(res);//若满足,直接存下当前组合
		return;
	}

	for (int i = start; i <= 9 - (k - res.size()) + 1; i++)//剪枝操作
	{
		res.push_back(i);
		sum = sum + i;
		backTravel(i + 1, k, n,sum);
		//开始回溯
		res.pop_back();
		sum = sum - i;
	}
}
vector<vector<int>> combinationSum3(int k, int n) {
	//从1开始递归选择,sum初值为0
	backTravel(1, k, n, 0);
	return result;
}
};

第17题 电话号码的字母组合

在这里插入图片描述在这里插入图片描述
**解题思路:**根源上还是组合问题,主要是将每个按键对应的字母实现列出来,然后递归组合就行,注意细节问题,刚开始输入的是字符串,要将字符转换成整型变量。

class Solution {
public:
    vector<string> result;
    string res;
    const string letterMap[10] = {
                "", // 0
                "", // 1
                "abc", // 2
                "def", // 3
                "ghi", // 4
                "jkl", // 5
                "mno", // 6
                "pqrs", // 7
                "tuv", // 8
                "wxyz", // 9
    };
    void backTravel(string digits, int len, int start)
    {
        if (res.size() == len)//当组合长度满足len,存入结果集
        {
            result.push_back(res);
            return;
        }
        //计算当前按的数字
        int num = digits[start] - '0';
        //找这个数字对应的所有字母
        string str = letterMap[num];

        for (int i = 0; i < str.size(); i++)
        {
            //开始递归加回溯
            res.push_back(str[i]);
            backTravel(digits, len, start + 1);
            res.pop_back();
        }
    }
    vector<string> letterCombinations(string digits) {
        if (digits.size() == 0)
            return result;
        int len = digits.size();
        //从digits的第0位开始组合
        backTravel(digits, len, 0);
        return result;
    }
};

第39题 组合总和

在这里插入图片描述在这里插入图片描述在这里插入图片描述
**解题思路:**整体思路不变,注意可以重复选值,所以进入递归的时候不用进行i+1操作。由于是求和操作,累加结果会越来越大,为了降低时间复杂度,我们可以事先将数组按照升序排序,这样当累加值大于target是,就可以不用继续往下递归了。

class Solution {
public:
    vector<vector<int>> result;
    vector<int> res;
    void backTravel(vector<int> candidates,int target,int start,int sum)
    {
        if(sum > target)
            return;
        else if(sum == target)
            result.push_back(res);
        else
        {
            for (int i = start; i < candidates.size() && sum + candidates[i] <= target; i++)//”剪枝操作“
            {
                res.push_back(candidates[i]);
                //因为可以重复选,所以每次递归都可以从当前位置开始挑选
                sum += candidates[i];
                backTravel(candidates,target,i,sum);
                sum -= candidates[i];
                res.pop_back();
            }
        }

    }
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        sort(candidates.begin(), candidates.end());//对数组排个序
        backTravel(candidates,target,0,0);
        return result;
    }
};

第40题 组合总和Ⅱ

在这里插入图片描述在这里插入图片描述在这里插入图片描述
**解题思路:**这个题难点在于有重复数据,但是不能有重复组合,即【1,2,5】不能出现两次,因此,在上一题的基础上,我们要加一个控制条件,即在数组整体排好序的情况下,在for循环(横向选数)中,若当前数据与前一次循环的数据一样是,就代表这个数已经做过组合选择了,就不需要再对他进行选择,直接跳过本次循环。例如上述数组排好序为【1 1 2 5 6 7 10】,当我们对第一个1递归选择组合结束之后,来到第二个1,这时候发现跟前面的数一样,那就不需要对它递归进行二次组合了,这样就实现了去重。(注意,再回溯算法中,for循环控制的是横向选择,递归控制的是纵向选择,对横向选择进行控制不会影响纵向的选择,因此 【1 1 6】这个组合不会被筛掉,因为这里面的1 1是通过递归选择的,而 【1 2 5】 【1 2 5】这里面的两个1是在for循环中,1被挑选了两次,这是不可以的,所以要加以控制)。

class Solution {
public:
    vector<vector<int>> result;
    vector<int> res;
    void backTravel(vector<int> candidates,int target,int start,int sum)
    {
        if(sum > target)
            return;
        else if(sum == target)
            result.push_back(res);
        else
        {
            for (int i = start; i < candidates.size() && sum + candidates[i] <= target; i++)
            {
                //去重操作
                if(i>start && candidates[i] == candidates[i-1])
                    continue;
                res.push_back(candidates[i]);
                //hmap[candidates[i]] -= 1;
                //因为不可以重复选,所以每次递归都不能从当前位置开始挑选
                sum += candidates[i];
                backTravel(candidates,target,i+1,sum);
                sum -= candidates[i];
                //hmap[candidates[i]] += 1;
                res.pop_back();
            }
        }

    }
    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        sort(candidates.begin(), candidates.end());
        map<int, int> hmap;
        backTravel(candidates,target,0,0);
        return result;
    }
};

第131题 分割回文串

在这里插入图片描述在这里插入图片描述
解题思路: 切割问题:切割一个a之后,在bcdef中再去切割第二段,切割b之后在cdef中再切割第三段…对于字符串“aab”,事先切割出来“a”,“a”是回文串,从下一个位置开始切割“a”,同样,又是一个回文串,再继续切割,“b”是回文串,此时,整个字符串遍历完毕,回溯;从第二个a开始,“ab”不是回文,此次递归结束,回溯;回到第一个a,本次切割就是“aa”了,“aa”是回文,再切,只剩“b”了,还是回文,此次切割结束,回溯;最后一次切割就是“aab”了,不是回文,全部切割结束。

class Solution {
public:
    vector<vector<string>> result;
    vector<string> res;//存放小结果
    void backTravel(const string& s,int start)
    {
        if(start >= s.size())//当起始位置超过字符串长度
        {
            result.push_back(res);//把当前的切割结果放入result
            return;
        }
        for(int i = start;i<s.size();i++)
        {
            //获取[start,i]之间的字符串是否是回文串
            if(isPalindrome(s,start,i))//若当前切割的字串是回文串
            {
                string str = s.substr(start,i-start+1);
                res.push_back(str);
            }
            else //若不是回文串,跳过当前切割
                continue;
            backTravel(s,i+1);//寻找i+1起始位置的字串
            res.pop_back();
        }
    }
    bool isPalindrome(const string& s,int start,int end)
    {
        for(int i = start,j = end;i<j;i++,j--)
        {
            if(s[i] != s[j])
                return false;
        }
        return true;
    }
    vector<vector<string>> partition(string s) {
        backTravel(s,0);
        return result;
    }
};

第93题 复原IP地址

在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述
解题思路: 整体思路跟上一题差不多,都是切割,这里要注意的是在切割的过程中要加分隔符 “.”,所以采用了insert函数追加字符和erase函数删除字符。此外,每次切割都要判断当前子串是否符合要求,具体代码细节在下文都做了注释。

class Solution {
public:
    vector<string> result;//记录结果
    //判断字符串s的子串【start,end】是否合法
    bool isValid(const string& s,int start,int end)
    {
        if(start > end)
            return false;
        if(s[start] == '0' && start != end)
            return false;//以0开头的数字是不合法的
        int num = 0;
        for(int i = start;i<=end;i++)
        {
            num = num * 10 + (s[i] - '0');
            if(num > 255)
                return false;
        }
        return true;
    }
    void backTravel(string& s,int start,int pointNum)
    {
        if(pointNum == 3)//当字符串中有3个逗点时,代表已经被切割成4段,直接判断第四段是否合法,合法的话,直接放入result
        {
            if(isValid(s,start,s.size()-1))
                result.push_back(s);
            return;
        }
        for(int i = start;i<s.size();i++)
        {
            if(isValid(s,start,i))//判断【start,i】这段是否符合要求
            {
                s.insert(s.begin()+i+1,'.');//记得要加分隔符
                pointNum++;
                backTravel(s,i+2,pointNum);
                pointNum--;
                s.erase(s.begin()+i+1);
            }
            else
                break;
        }
    }
    vector<string> restoreIpAddresses(string s) {
        if (s.size() < 4 || s.size() > 12) return result; // 算是剪枝了
        backTravel(s, 0, 0);
        return result;
    }
};

第78题 子集

在这里插入图片描述在这里插入图片描述在这里插入图片描述
**解题思路:**其实就是很简单的组合问题,递归结束条件是子集个数达到要求就停止。

class Solution {
public:
    vector<vector<int>> result;
    vector<int> res;
    void backTravel(vector<int>& nums,int start,int Num)
    {
        if(result.size()<=Num)
            result.push_back(res);

        for(int i = start;i<nums.size();i++)
        {
            res.push_back(nums[i]);
            backTravel(nums,i+1,Num);
            res.pop_back();
        }

    }
    vector<vector<int>> subsets(vector<int>& nums) {
        int len = nums.size();
        int Num = pow(2,len);
        backTravel(nums,0,Num);
        return result;
    }
};

第90题 子集Ⅱ

在这里插入图片描述在这里插入图片描述在这里插入图片描述
**解题思路:**跟第40题一样,需要去去重,去重之前需要先对原数组排序,由于有重复数据,所以子集的个数就不是2^len个,直接通过for循环默认结束递归(因为也是全部遍历,for循环结束,整个递归就应该结束了)。

class Solution {
public:
    vector<vector<int>> result;
    vector<int> res;
    void backTravel(vector<int>& nums,int start)
    {
        result.push_back(res);

        for(int i = start;i<nums.size();i++)
        {
            if(i>start && nums[i] == nums[i-1])
                continue;
            res.push_back(nums[i]);
            backTravel(nums,i+1);
            res.pop_back();
        }
    }
    vector<vector<int>> subsetsWithDup(vector<int>& nums) {
        int len = nums.size();
        sort(nums.begin(),nums.end());
        backTravel(nums,0);
        return result;
    }
};

第491题 递增子序列

在这里插入图片描述在这里插入图片描述在这里插入图片描述
**解题思路:**这道题麻烦在于既要去重,还不能对原来数组进行排序,若一排序,整个结果就变了。对于最复杂的例子【1,2,3,1,1,1,1】直接用原来的去重方法“i>start && nums[i] == nums[i-1]”就不好用了,因为数组没有排序,最前面的1会和后面的1进行组合,等遍历到后面的第一个1的时候,他们又会组合,这样就会有重复项。为了去重,引入set,记录横向遍历使用过的数字,当这个数字再次出现的时候,就不能用它了,注意是横向遍历的时候判断,不会影响纵向组合!!!

class Solution {
public:
    vector<vector<int>> result;
    vector<int> res;
    void backTravel(vector<int> nums,int start)
    {
        if(res.size()>1)
            result.push_back(res);
        unordered_set<int> st;//引入set集合
        for(int i = start;i<nums.size();i++)
        {
            if((!res.empty() && nums[i] < res.back()) || st.find(nums[i]) != st.end())//因为要求递增序列,所以当前数字必须大于等于原子序列的最后一个数字
                continue;
            st.insert(nums[i]);
            res.push_back(nums[i]);
            backTravel(nums,i+1);
            res.pop_back();
        }
    }
    vector<vector<int>> findSubsequences(vector<int>& nums) {
        backTravel(nums,0);
        return result;
    }
};

第46题 全排列

在这里插入图片描述在这里插入图片描述在这里插入图片描述
**解题思路:**这是个排列问题,和组合的区别是,在组合中【1,2】和【2,1】是一个组合,但是在排列中就是两种排列了。所以我们每次挑选数据的时候,就要从头开始,找那些没有被选过的数据。因此需要借助一个used数组,来判断那些数被用了,哪些没有被用。

class Solution {
public:
    vector<vector<int>> result;
    vector<int> res;
    void backTravel(vector<int>& nums,vector<bool>& used)
    {
        //当收集元素的个数等于nums数组长度,这一组结束
        if(res.size() == nums.size())
        {
            result.push_back(res);
            return;
        }
        for(int i = 0;i<nums.size();i++)
        {
            if(used[i] == true)
                continue;
            used[i] = true;
            res.push_back(nums[i]);
            backTravel(nums,used);
            res.pop_back();
            used[i] = false;
        }
    }
    vector<vector<int>> permute(vector<int>& nums) {
        vector<bool> used(nums.size(),false);
        backTravel(nums,used);
        return result;
    }
};

第47题 全排列Ⅲ

在这里插入图片描述在这里插入图片描述
**解题思路:**这里既要去重,又要排列组合。涉及到去重,就需要对原数组进行排序,因为在排列中,每次都是从头挑选,所以去重的时候要注意,当前这个数和前一个数一样,并且used[i-1] == false才表示真正的去重,因为进入到当前这个数字的时候,之前的数字使用情况都被置false了,所以表示的意思是跟他相同数字已经在前边使用过了,本次循环直接跳过就行。

class Solution {
public:
    vector<vector<int>> result; 
    vector<int> res;
    void backTravel(vector<int>& nums,vector<bool>& used)
    {
        //当收集元素的个数等于nums数组长度,这一组结束
        if(res.size() == nums.size())
        {
            result.push_back(res);
            return;
        }
        for(int i = 0;i<nums.size();i++)
        {
        	// 如果同一树层nums[i - 1]使用过则直接跳过
            if((i>0 && nums[i] == nums[i-1]) && (used[i-1] == false))
                continue;
            if(used[i] == false)
            {
                used[i] = true;
                res.push_back(nums[i]);
                backTravel(nums,used);
                res.pop_back();
                used[i] = false;
            }
        }
    }
    vector<vector<int>> permuteUnique(vector<int>& nums) {
        vector<bool> used(nums.size(),false);
        sort(nums.begin(),nums.end());
        backTravel(nums,used);
        return result;
    }
};

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Day4——数据库基础1

Day4——数据库基础 数据库基础--基于phpstudy自带的MySQL数据库&#xff08;下载了PHPstudy后就无需下载额外的MySQL&#xff09; 一、数据库概念1、为什么要学习数据库&#xff1f;2、什么是数据库&#xff1f;3、数据库的访问方式4、数据管理技术经历的三个阶段5、关系型数据…

Three.js深度冲突(模型闪烁)与解决方案

Mesh面重合渲染测试 下面代码创建两个重合的矩形平面Mesh&#xff0c;通过浏览器预览&#xff0c;当你旋转三维场景的时候&#xff0c;你会发现模型渲染的时候产生闪烁。 这种现象&#xff0c;主要是两个Mesh重合&#xff0c;电脑GPU分不清谁在前谁在后&#xff0c;这种现象&a…

Debug | Litemall项目上线

文章目录 1. litemall资源1.1 Litemall项目开源网址1.2 Litemall网盘地址 2. 项目上线2.1 项目框架2.2 项目配置2.3 依赖工具安装2.4 数据库导入2.5 启动小商城和管理后台的后台服务2.6 启动管理后台的前台服务 3项目展示&#xff1a; 1. litemall资源 1.1 Litemall项目开源网…

webpack 自定义loader

文章目录 项目目录搭建项目my-raw-loader参数schema-utils tpl-loader 项目目录 让我们实现一些简易的loader&#xff0c;从大量的简易loader的实现过程中学习编写如何 webpack loader ├── loaders # loader目录 ├── src …

数据库sql语句-----游标和存储过程

关键词&#xff1a; create procedure xxx&#xff08;&#xff09;as.......go 查询&#xff1a; exec ... 从例子中感悟一下&#xff1a; create table cartoon( linenum int, name varchar(50) not null, line varchar(100) not null )insert into cartoon values(1,灰太…

基于SpringBoot+Vue+MybatisPlus的智慧校园系统

智慧校园系统 1. 项目简介2. 项目模块3. 技术栈4. 软件环境4.1 安装数据库4.2 安装数据库客户端Navicat工具4.4 安装IDEA4.4 安装Maven 5. 系统页面5.1 首页登录页5.2 系统功能模块5.3 Swagger2接口文档查阅5.4 运行截图 6. 源代码下载 1. 项目简介 智慧校园管理系统是一个基于…

从零开始Vue3+Element Plus后台管理系统(十三)——富文本编辑器、Markdown编辑器、代码编辑器

早就想着要放几个编辑器的Demo到项目中&#xff0c;这也是项目开始就立下的flag。 今天专门挑选了几款主流编辑器&#xff0c;包括绕不开的富文本编辑器&#xff0c;码农最爱的markdown编辑器&#xff0c;还有用途相对少的代码编辑器。 时间有限的情况下&#xff0c;仅引入4个…

去哪儿酒店数据下载

字段内容包含&#xff1a; id int(11) NOT NULL AUTO_INCREMENT, hotelid varchar(50) DEFAULT NULL, url varchar(200) DEFAULT NULL, hotelname2 varchar(100) DEFAULT NULL, name varchar(100) DEFAULT NULL, province varchar(50) DEFAULT NULL, d…

zabbix安装完成后,无法找到zabbix/bin目录

问题 zabbix安装完成后&#xff0c;无法找到zabbix/bin目录 详细问题 笔者安装zabbix后&#xff0c;自定义item key进行测试。需在zabbix-server 端 切换目录&#xff1a; cd /usr/local/zabbix/bin 执行查询命令&#xff1a; ./zabbix_get -s 192.168.174.132 -p 10050 -k …

Bat批处理中的 FINDSTR用法

linux中的grep等同于win中的findstr命令。可以在txt文本中截取到有特定关键字的行&#xff0c;并显示出来。 grep也可以通过关键字&#xff0c;在一个文件夹下查找多个有这些关键字的文件&#xff0c;并生成结果。 一、findstr命令介绍 findstr是Windows系统自带的命令,简单来说…

chatgpt赋能Python-python_end的用法

简介 Python是一种广泛使用的编程语言&#xff0c;被广泛应用于数据科学&#xff0c;机器学习&#xff0c;网络编程&#xff0c;Web开发等领域。Python内置了许多有用的操作符和关键字&#xff0c;其中包括end。end是Python中一个非常有用的操作符&#xff0c;它可以在将多个输…

C001--Visual C++ 6.0集成开发环境的下载与安装并运行简单的C语言程序

visual c的下载安装过程不复杂&#xff0c;只需要运行setup.exe程序&#xff0c;然后按照安装程序的提示信息进行操作&#xff0c;也可以指定系统文件存放的路径。 目录 一&#xff0c;下载安装集成开发环境 1&#xff0c;流程 2&#xff0c;熟悉visual c工作界面 二&am…

Element-UI介绍:主题定制、自定义组件和插件扩展

部分数据来源&#xff1a;ChatGPT 什么是Element-UI Element-UI是一款简单好用的前端UI库&#xff0c;基于Vue.js开发。它提供了常用的组件和样式&#xff0c;可以帮助我们快速地构建美观、实用的交互界面。 在使用Element-UI开发项目过程中&#xff0c;我们有时遇到了一些特…

chatgpt赋能Python-python_dim

Python dim&#xff1a;将维度降至极致 在机器学习和数据分析领域&#xff0c;我们经常需要处理高维数据。然而&#xff0c;高维数据不仅处理起来麻烦&#xff0c;而且往往也不利于数据分析和模型训练。因此&#xff0c;数据科学家和工程师需要一个有效的方法来降低数据维度。…

【数据结构与算法】- 期末考试

课程链接: 清华大学驭风计划 代码仓库&#xff1a;Victor94-king/MachineLearning: MachineLearning basic introduction (github.com) 驭风计划是由清华大学老师教授的&#xff0c;其分为四门课&#xff0c;包括: 机器学习(张敏教授) &#xff0c; 深度学习(胡晓林教授), 计算…

python笔记 第一章

学习用到的资源链接&#xff1a;https://pan.baidu.com/s/1Ftptx_9iH9xFYj3NbugMrg?pwd1234 提取码&#xff1a;1234 文章目录 1.1 简介Python版本 1.2 解释器下载Python解释器安装Python解释器验证是否安装 扩展 1.3 PyCharm安装PyCharm基本使用新建项目设置只看项目文件运行…

【深度学习】- 作业6: 图像自然语言描述生成

课程链接: 清华大学驭风计划 代码仓库&#xff1a;Victor94-king/MachineLearning: MachineLearning basic introduction (github.com) 驭风计划是由清华大学老师教授的&#xff0c;其分为四门课&#xff0c;包括: 机器学习(张敏教授) &#xff0c; 深度学习(胡晓林教授), 计算…

2023 华为 Datacom-HCIE 题库 04--含解析

单选题 1.[试题编号&#xff1a;189921] &#xff08;单选题&#xff09;防火墙双机热备场景下&#xff0c;当VGMP工作在负载分担模式时&#xff0c;为了避免在来回路径不一致的场景下回程流量因没有匹配到会话表项而丢弃的现象&#xff0c;防火墙需要启开一下那些功能&#x…

【vim】从入门到放弃(“四种”模式、常用命令、正则表达式、文件属性、插件安装)

文章目录 一、vim简介二、vim操作2.1 三种模式及其切换2.2 常用命令2.21 命令模式下常用命令2.22 底行模式下常用命令 三、vim进阶3.1 进阶操作3.11 可视化模式3.12 正则表达式3.13 结合其他文本处理命令3.14 修改文件属性&#xff08;编码、格式、权限&#xff09; 3.2 进阶配…

cpu压力测试、平均负载、切换上下文(linux)

和windows下有很多图形化测试工具不同&#xff0c;linux下的压力测试通常需要命令行 一、平均负载 1.查看命令 uptime会给出类似如下的信息 2.说明 三个数值代表1分钟&#xff0c;5分钟&#xff0c;15分钟的平均进程数。 换成更容易理解但不准确的说法就是几个核满载 比如…