代码随想录算法训练营第二十六天 | 回溯算法part3

目录

力扣题目

力扣题目记录

39. 组合总和

40.组合总和II

补充

131.分割回文串

优化

总结


力扣题目

用时:未知

1、39. 组合总和

2、40.组合总和II

3、131.分割回文串


力扣题目记录

39. 组合总和

  1. 可以重复,所以startIndex的处理和之前不同
  2. 剪枝的话需要先进行排序
// 版本一
class Solution {
private:vector<vector<int>> result;vector<int> path;void backtracking(vector<int>& candidates, int target, int sum, int startIndex) {if (sum > target) {return;}if (sum == target) {result.push_back(path);return;}for (int i = startIndex; i < candidates.size(); i++) {sum += candidates[i];path.push_back(candidates[i]);backtracking(candidates, target, sum, i); // 不用i+1了,表示可以重复读取当前的数sum -= candidates[i];path.pop_back();}}
public:vector<vector<int>> combinationSum(vector<int>& candidates, int target) {result.clear();path.clear();backtracking(candidates, target, 0, 0);return result;}
};
//剪枝
class Solution {
private:vector<vector<int>> result;vector<int> path;void backtracking(vector<int>& candidates, int target, int sum, int startIndex) {if (sum == target) {result.push_back(path);return;}// 如果 sum + candidates[i] > target 就终止遍历for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; i++) {sum += candidates[i];path.push_back(candidates[i]);backtracking(candidates, target, sum, i);sum -= candidates[i];path.pop_back();}}
public:vector<vector<int>> combinationSum(vector<int>& candidates, int target) {result.clear();path.clear();sort(candidates.begin(), candidates.end()); // 需要排序backtracking(candidates, target, 0, 0);return result;}
};
  • 时间复杂度: O(n * 2^n),注意这只是复杂度的上界,因为剪枝的存在,真实的时间复杂度远小于此
  • 空间复杂度: O(target)

40.组合总和II

  1. 首先进行排序,将重复的元素放到一起
  2. 用一个vector<bool>类型表示是否同一树层有重复元素
class Solution {
private:vector<vector<int>> result;vector<int> path;void backtracking(vector<int>& candidates, int target, int sum, int startIndex, vector<bool>& used) {if (sum == target) {result.push_back(path);return;}for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; i++) {// used[i - 1] == true,说明同一树枝candidates[i - 1]使用过// used[i - 1] == false,说明同一树层candidates[i - 1]使用过// 要对同一树层使用过的元素进行跳过if (i > 0 && candidates[i] == candidates[i - 1] && used[i - 1] == false) {continue;}sum += candidates[i];path.push_back(candidates[i]);used[i] = true;backtracking(candidates, target, sum, i + 1, used); // 和39.组合总和的区别1,这里是i+1,每个数字在每个组合中只能使用一次used[i] = false;sum -= candidates[i];path.pop_back();}}public:vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {vector<bool> used(candidates.size(), false);path.clear();result.clear();// 首先把给candidates排序,让其相同的元素都挨在一起。sort(candidates.begin(), candidates.end());backtracking(candidates, target, 0, 0, used);return result;}
};
  • 时间复杂度: O(n * 2^n)
  • 空间复杂度: O(n)

补充

这里直接用startIndex来去重也是可以的, 就不用used数组了。

class Solution {
private:vector<vector<int>> result;vector<int> path;void backtracking(vector<int>& candidates, int target, int sum, int startIndex) {if (sum == target) {result.push_back(path);return;}for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; i++) {// 要对同一树层使用过的元素进行跳过if (i > startIndex && candidates[i] == candidates[i - 1]) {continue;}sum += candidates[i];path.push_back(candidates[i]);backtracking(candidates, target, sum, i + 1); // 和39.组合总和的区别1,这里是i+1,每个数字在每个组合中只能使用一次sum -= candidates[i];path.pop_back();}}public:vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {path.clear();result.clear();// 首先把给candidates排序,让其相同的元素都挨在一起。sort(candidates.begin(), candidates.end());backtracking(candidates, target, 0, 0);return result;}
};

131.分割回文串

  1. 检测某个子串是否是回文串
  2. 分割其实可以理解为在某个字符后面加个分隔符
class Solution {
private:vector<vector<string>> result;vector<string> path; // 放已经回文的子串void backtracking (const string& s, int startIndex) {// 如果起始位置已经大于s的大小,说明已经找到了一组分割方案了if (startIndex >= s.size()) {result.push_back(path);return;}for (int i = startIndex; i < s.size(); i++) {if (isPalindrome(s, startIndex, i)) {   // 是回文子串// 获取[startIndex,i]在s中的子串string str = s.substr(startIndex, i - startIndex + 1);path.push_back(str);} else {                                // 不是回文,跳过continue;}backtracking(s, i + 1); // 寻找i+1为起始位置的子串path.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;}
public:vector<vector<string>> partition(string s) {result.clear();path.clear();backtracking(s, 0);return result;}
};
  • 时间复杂度: O(n * 2^n)
  • 空间复杂度: O(n^2)

优化

上面的代码还存在一定的优化空间, 在于如何更高效的计算一个子字符串是否是回文字串。上述代码isPalindrome函数运用双指针的方法来判定对于一个字符串s, 给定起始下标和终止下标, 截取出的子字符串是否是回文字串。但是其中有一定的重复计算存在:

例如给定字符串"abcde", 在已知"bcd"不是回文字串时, 不再需要去双指针操作"abcde"而可以直接判定它一定不是回文字串。

具体来说, 给定一个字符串s, 长度为n, 它成为回文字串的充分必要条件是s[0] == s[n-1]s[1:n-1]是回文字串。

大家如果熟悉动态规划这种算法的话, 我们可以高效地事先一次性计算出, 针对一个字符串s, 它的任何子串是否是回文字串, 然后在我们的回溯函数中直接查询即可, 省去了双指针移动判定这一步骤.

具体参考代码如下:

class Solution {
private:vector<vector<string>> result;vector<string> path; // 放已经回文的子串vector<vector<bool>> isPalindrome; // 放事先计算好的是否回文子串的结果void backtracking (const string& s, int startIndex) {// 如果起始位置已经大于s的大小,说明已经找到了一组分割方案了if (startIndex >= s.size()) {result.push_back(path);return;}for (int i = startIndex; i < s.size(); i++) {if (isPalindrome[startIndex][i]) {   // 是回文子串// 获取[startIndex,i]在s中的子串string str = s.substr(startIndex, i - startIndex + 1);path.push_back(str);} else {                                // 不是回文,跳过continue;}backtracking(s, i + 1); // 寻找i+1为起始位置的子串path.pop_back(); // 回溯过程,弹出本次已经添加的子串}}void computePalindrome(const string& s) {// isPalindrome[i][j] 代表 s[i:j](双边包括)是否是回文字串 isPalindrome.resize(s.size(), vector<bool>(s.size(), false)); // 根据字符串s, 刷新布尔矩阵的大小for (int i = s.size() - 1; i >= 0; i--) { // 需要倒序计算, 保证在i行时, i+1行已经计算好了for (int j = i; j < s.size(); j++) {if (j == i) {isPalindrome[i][j] = true;}else if (j - i == 1) {isPalindrome[i][j] = (s[i] == s[j]);}else {isPalindrome[i][j] = (s[i] == s[j] && isPalindrome[i+1][j-1]);}}}}
public:vector<vector<string>> partition(string s) {result.clear();path.clear();computePalindrome(s);backtracking(s, 0);return result;}
};

总结

        今天是水的一天,代码都要好好做做 

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

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

相关文章

CentOS7之开启ssh远程登录

参考&#xff1a;https://www.cnblogs.com/travis-li/p/12550370.html cd /etc/ssh/ # 修改配置 vim sshd_config# 开启服务 sudo service sshd start# 检查 ps -e | grep sshd# 开机自启 systemctl enable sshd.service# 查看(验证)开机自启服务 [rootlocalhost liangshijie]…

【已解决】Excel“打开密码”在哪里设置?

Excel可以设置“打开密码”&#xff0c;设置后只要打开表格就会提示文件有密码保护&#xff0c;需要输入密码&#xff0c;才能查看表格里面的内容。 那Excel“打开密码”在哪里设置呢&#xff1f;Excel提供了两种路径可以选择。 选择一、直接在Excel里设置 操作步骤&#xff1…

facebook广告企业户对账户有什么要求

Facebook广告企业户对账户的要求如下&#xff1a; 企业账户只能通过代理商开户&#xff0c;需提供营业执照&#xff0c;个人FB账号&#xff0c;FB主页。广告充值&#xff0c;要通过代理商充值。企业广告账户操作要通过Facebook BM平台授权&#xff0c;开户前需自己先创建BM平台…

市场复盘总结 20231226

仅用于记录当天的市场情况&#xff0c;用于统计交易策略的适用情况&#xff0c;以便程序回测 短线核心&#xff1a;不参与任何级别的调整 昨日回顾&#xff1a; SELECT CODE,成交额排名,净流入排名,代码,名称,DDE大单金额,涨幅,主力净额,DDE大单净量,CONVERT(DATETIME, 最后封…

EN 438-7-2005 高压装饰层压板(HPL)CE认证

热固性树脂浸渍纸高压装饰层压板是指用氨基树脂浸渍的表层纸、装饰纸和用酚醛树脂浸渍的底层纸&#xff0c;层积后在高压下热压而成的一种装饰材料&#xff0c;简称高压装饰板&#xff08;HPL&#xff09;&#xff0c;具有防火&#xff0c;防潮&#xff0c;抗菌等性能&#xff…

海德堡UV灯电源维修eta Plus Elc PE22-400-210

uv灯电源维修故障包括&#xff1a; 1、电压不稳&#xff1a;检查uv打印机的电压&#xff0c;设置一个稳压箱即可。 2、温度过高&#xff1a;uv打印机温度过高也会影响uv灯&#xff0c;可以更换为水冷式循环降温。 3、水箱里的信号线接触不好&#xff1a;将两边的信号线对调&…

Hive文件存储与压缩

压缩和存储 1、 Hadoop压缩配置 1) MR支持的压缩编码 压缩格式工具算法文件扩展名是否可切分DEFAULT无DEFAULT.deflate否GzipgzipDEFAULT.gz否bzip2bzip2bzip2.bz2是LZOlzopLZO.lzo否LZ4无LZ4.lz4否Snappy无Snappy.snappy否 为了支持多种压缩/解压缩算法&#xff0c;Hadoop…

理解MoCo

目录 1、简介 2、MoCo 2.1、引言 2.2、主要贡献 2.3、相关工作 2.4、方法 2.4.1、损失函数 2.4.2、队列 2.4.3、动量编码器 2.4.4、前向过程 2.5、实验 2.5.1、网络结构比较 2.5.2、动量参数m 2.5.3、imageNet数据集结果 2.5.4、迁移特性 2.6、MoCo代码 1、简介…

cfa一级考生复习经验分享系列(十一)

理工科已经毕业&#xff0c;正在工作&#xff0c;毫无金融背景。一共准备了四个月&#xff0c;每天下班和周末抽时间看看。前三个月节奏比较松散&#xff0c;毕竟时不时有人叫我出去high&#xff0c;最后一个月认真看了看。 用到的资料 JC网课&#xff0c;官方Mock&#xff0c;…

Go和Java实现工厂方法模式

Go和Java实现工厂方法模式 本文通过计算器案例来说明工厂方法模式的使用&#xff0c;使用Go语言和Java语言实现。 1、工厂方法模式 工厂方法模式是最常用的设计模式之一&#xff0c;这种类型的设计模式属于创建型模式&#xff0c;它提供了一种创建对象的最佳方 式。 在工厂…

数字音频编辑软件audition 2021 mac功能介绍

Audition 2021 mac是一款专业数字音频编辑软件&#xff0c;提供先进的音频混音、编辑和效果处理功能&#xff0c;专为音频和视频专业人员设计。无论是要录制音乐、无线电广播&#xff0c;还是为录像配音&#xff0c;Audition都能帮到您。它可提供先进的音频混合、编辑、控制和效…

深度学习中的收敛是什么意思?

在深度学习中&#xff0c;“收敛” 指的是训练过程中模型逐渐学习并改善其性能&#xff0c;直到达到一种稳定状态。具体来说&#xff0c;这通常涉及以下几个方面&#xff1a; 1. 损失函数的减少 深度学习模型的训练通常涉及最小化一个称为损失函数&#xff08;或代价函数&#…

Zookeeper的使用场景

统一命名服务 利用ZooKeeper节点的树形分层结构和子节点的顺序维护能力&#xff0c;来为分布式系统中的资源命名。 例&#xff1a;分布式节点命名 分布式消息队列 1.在Zookeeper中创建一个持久节点&#xff0c;用作队列的根节点。队列元素的节点放在这个根节点下。 2.入队:…

【Unity学习笔记】1.创建场景

创建场景 注1&#xff1a;samplescene&#xff08;示例场景&#xff09;、standard assets&#xff08;标准资产&#xff09;、favorites&#xff08;收藏夹&#xff09;、terrain&#xff08;地形&#xff09;。 注2&#xff1a;favorites用于存放各种资源&#xff1b;sample…

Spring-2-配置和Springboot

bean的生命周期 挂钩到bean的创建 通过了解初始化的时间&#xff0c;bean可以检查是否满足其所需的所有依赖项。 尽管Spring可以帮助我们检查依赖项&#xff0c;但它几乎是一种全有或全无的方法&#xff0c;并且不会提供任何机会来将其他逻辑应用于依赖项的解析过程中。 假设…

打破常规:瑞幸咖啡如何实施RGM策略来逆袭市场趋势

瑞幸咖啡仅用短短18个月时间从品牌创立到纳斯达克上市&#xff0c;刷新全球最快上市记录。2020年因交易造假事件被勒令退市股价暴跌80%&#xff0c;有人说这个创造了赴美IPO奇迹的“巨婴”将是下一个倒下的ofo。2022年瑞幸咖啡以逆势超速增长领跑咖啡赛道有力回应了市场的质疑&…

TCP协议及工作原理(三)客户端的搭建

ui界面的搭建 &#xff1a; QTcpServer是基于TCP的服务器类提供一种方便的方式管理和创建TCP服务器&#xff0c;QTcpSocket处理TCP套接字编程用于建立TCP连接&#xff0c;发送接收数据等功能。 参考前两篇可深入理解&#xff01;&#xff01;&#xff01;&#xff01;&#xff…

用 Unity 实现的安检模拟小游戏源码,通过安检设备 (扫描仪) 检查乘客的随身物品 根据禁止名单对乘客做出判断是否允许通行

介绍 用 Unity 实现的安检模拟小游戏 软件版本 Unity 2019.4.9f1 (64-bit) Visual Studio 2019 游戏玩法 在游戏中你将扮演一名安全检查员 通过安检设备 (扫描仪) 检查每位乘客的随身物品 根据禁止名单对乘客做出判断&#xff1a;允许通行或者下令逮捕 游戏效果 游戏截图…

带你学C语言~指针(3)

目录 ✍0.前言 &#x1f680;1.字符指针变量 &#x1f685;2.数组指针变量 &#x1f431;‍&#x1f3cd;2.1.数组指针变量是什么 &#x1f431;‍&#x1f3cd;2.2数组指针变量怎么初始化 &#x1f6a2;3.二维数组传参的本质 &#x1f680;4.函数指针变量 ✈4.1函数指…

使用JSON.parse字符串转换json报错解决办法

终于用正则表达式一步完美处理了Json数据里的存在单引号/双引号导致解析失败的报错&#xff0c;这样无论什么开发语言&#xff0c;都可以搞定这个问题了。 Uncaught SyntaxError: JSON.parse: expected , or } after property value in object at line 1 column 41 of the JSO…