DAY23|回溯算法Part02|LeetCode: 39. 组合总和 、40.组合总和II 、131.分割回文串

目录

LeetCode: 39. 组合总和

基本思路

C++代码

LeetCode: 40.组合总和II 

基本思路

C++代码

LeetCode: 131.分割回文串

基本思路

C++代码


LeetCode: 39. 组合总和

力扣代码链接

文字讲解:LeetCode: 39. 组合总和

视频讲解:带你学透回溯算法-组合总和

基本思路

        本题没有数量要求,可以无限重复,但是有总和的限制,所以间接的也是有个数的限制。将搜索过程抽象为以下树形结构:

  • 递归函数参数

        这里依然是定义两个全局变量,二维数组result存放结果集,数组path存放符合条件的结果。

        参数:包括给定的集合candidates, 和目标值target,还定义了int型的sum变量来统计单一结果path里的总和以及设置for循环起始位置的startIndex。

vector<vector<int>> result;
vector<int> path;
void backtracking(vector<int>& candidates, int target, int sum, int startIndex)
  • 递归终止条件

        终止只有两种情况,sum大于target和sum等于target。sum等于target的时候,需要收集结果。

if (sum > target) {return;
}
if (sum == target) {result.push_back(path);return;
}
  • 单层搜索的逻辑

        单层for循环依然是从startIndex开始,搜索candidates集合。

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();        // 回溯
}
  • 剪枝优化

        对于sum已经大于target的情况,其实是依然进入了下一层递归,只是下一层递归结束判断的时候,会判断sum > target的话就返回。其实如果已经知道下一层的sum会大于target,就没有必要进入下一层递归了。那么可以在for循环的搜索范围上做做文章了。

        对总集合排序之后,如果下一层的sum(就是本层的 sum + candidates[i])已经大于target,就可以结束本轮for循环的遍历

所以我们对for循环的剪枝如下:

for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; i++)

C++代码

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;}
};

LeetCode: 40.组合总和II 

力扣代码链接

文字讲解:LeetCode: 40.组合总和II 

视频讲解:回溯算法中的去重,树层去重树枝去重,你弄清楚了没?

基本思路

        这个题和上个题主要存在两个不同点:本题中candidates 中的每个数字在每个组合中只能使用一次以及本题数组candidates的元素是有重复的,而上一题是无重复元素的数组candidates

        如果我们还按照上一题的方法,就极有可能会获得重复的结果,那么我们应该怎么对结果进行去重呢?都知道组合问题可以抽象为树形结构,那么“使用过”在这个树形结构上是有两个维度的,一个维度是同一树枝上使用过,一个维度是同一树层上使用过所以我们要去重的是同一树层上的“使用过”,同一树枝上的都是一个组合里的元素,不用去重

        另外,我们在树层去重的时候要对数组进行排序。

  • 递归函数参数

此题还需要加一个bool型数组used,用来记录同一树枝上的元素是否使用过。

vector<vector<int>> result; // 存放组合集合
vector<int> path;           // 符合条件的组合
void backtracking(vector<int>& candidates, int target, int sum, int startIndex, vector<bool>& used) 
  • 递归终止条件

        终止条件为 sum > target 和 sum == target

if (sum > target) { // 这个条件其实可以省略return;
}
if (sum == target) {result.push_back(path);return;
}
  • 单层搜索的逻辑

        如果candidates[i] == candidates[i - 1] 并且 used[i - 1] == false,就说明:前一个树枝,使用了candidates[i - 1],也就是说同一树层使用过candidates[i - 1]。此时for循环里就应该做continue的操作。

        我在图中将used的变化用橘黄色标注上,可以看出在candidates[i] == candidates[i - 1]相同的情况下:

  • used[i - 1] == true,说明同一树枝candidates[i - 1]使用过
  • used[i - 1] == false,说明同一树层candidates[i - 1]使用过

        这一块的去重逻辑很抽象,一定要好好思考或者去听视频讲解去理解其中的含义。

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();
}

C++代码

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;}
};

LeetCode: 131.分割回文串

力扣代码链接

文字讲解:LeetCode: 131.分割回文串

视频讲解:带你学透回溯算法-分割回文串

基本思路

        我们来分析一下切割,其实切割问题类似组合问题

        例如对于字符串abcdef:

  • 组合问题:选取一个a之后,在bcdef中再去选取第二个,选取b之后在cdef中再选取第三个.....。
  • 切割问题:切割一个a之后,在bcdef中再去切割第二段,切割b之后在cdef中再切割第三段.....。

        而分割问题同样可以抽象为树形结构,递归用来纵向遍历,for循环用来横向遍历:

  • 递归函数参数

        全局变量数组path存放切割后回文的子串,二维数组result存放结果集。

        参数:本题递归函数参数还需要startIndex,因为切割过的地方,不能重复切割,和组合问题也是保持一致的。

vector<vector<string>> result;
vector<string> path; // 放已经回文的子串
void backtracking (const string& s, int startIndex)
  • 递归函数终止条件

        切割线切到了字符串最后面,说明找到了一种切割方法,此时就是本层递归的终止条件。在处理组合问题的时候,递归参数需要传入startIndex,表示下一轮递归遍历的起始位置,这个startIndex就是切割线。

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++)循环中,我们定义了起始位置startIndex,那么 [startIndex, i] 就是要截取的子串。首先判断这个子串是不是回文,如果是回文,就加入在vector<string> path中,path用来记录切割过的回文子串。

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();        // 回溯过程,弹出本次已经添加的子串
}

        注意切割过的位置,不能重复切割,所以,backtracking(s, i + 1); 传入下一层的起始位置为i + 1

        当然,我们需要一个判断是否为回文子串的函数,很容易想到前面提到过的双指针法。

 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;}

C++代码

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;}
};

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

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

相关文章

【linux】再谈网络基础(二)

8. 再谈端口号 &#xff08;一&#xff09;与协议之间的关系 端口号(Port)标识了一个主机上进行通信的不同的应用程序 在TCP/IP协议中, 用 "源IP", "源端口号", "目的IP", "目的端口号", "协议号" 这样一个五元组来标识…

OpenCV视觉分析之目标跟踪(12)找到局部的最大值函数meanShift()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 在反向投影图像上找到一个对象。 meanShift 是一种用于图像处理和计算机视觉领域的算法&#xff0c;特别适用于目标跟踪、图像分割等任务。该算…

VS2022配置OpenGL

下载地址&#xff1a; https://download.csdn.net/download/hgaohr1021/89974202 1、下载后&#xff0c;直接把OpenGL文件&#xff0c;全部放在 D:\Program Files这里&#xff0c;OpenGL这个名字也不要改&#xff01;&#xff01;&#xff01; 2、把文件PropertySheetOpenGL.p…

革命性AI搜索引擎!ChatGPT最新功能发布,无广告更智能!

文章目录 零、前言一、ChatGPT最新AI搜索引擎功能操作指导实战1:搜索新闻实战2:搜索天气实战3:搜索体育消息 二、感受 零、前言 大人&#xff0c;时代变了。 最强 AI 助力下的无广告搜索引擎终于问世。我们期待已久的这一刻终于到来了&#xff0c;从今天起&#xff0c;ChatGPT…

微积分复习笔记 Calculus Volume 1 - 4.10 Antiderivatives

4.10 Antiderivatives - Calculus Volume 1 | OpenStax

O-RAN Fronthual CU/Sync/Mgmt 平面和协议栈

O-RAN Fronthual CU/Sync/Mgmt 平面和协议栈 O-RAN Fronthual CU/Sync/Mgmt 平面和协议栈O-RAN前端O-RAN 前传平面C-Plane&#xff08;控制平面&#xff09;&#xff1a;控制平面消息定义数据传输、波束形成等所需的调度、协调。U-Plane&#xff08;用户平面&#xff09;&#…

PADS操作技巧

CTRL左键单击&#xff1a;结束布线 过孔&#xff1a;信号换层、散热 F2布线&#xff0c;然后右键添加过孔。 切换过孔大小

给初学者的 Jupyter Notebook 教程

目录 一、什么是Jupyter Notebook&#xff1f; 1. 简介 2. 组成部分 ① 网页应用 ② 文档 3. Jupyter Notebook的主要特点 二、安装Jupyter Notebook 0. 先试用&#xff0c;再决定 1. 安装 ① 安装前提 ② 使用Anaconda安装 ③ 使用pip命令安装 三、运行Jupyter No…

window下安装rust 及 vscode配置

安装 安装mingw64 &#xff08;c语言环境 选择posix-ucrt&#xff09; ucrt:通用c运行时库配置mingw64/bin的路径到环境变量中在cmd窗口中输入命令 "gcc -v" 4. 下载Rust安装程序 安装 Rust - Rust 程序设计语言 5. 配置rustup和cargo目录 &#xff08;cargo是包管…

RHCE的学习(13)

第十章openEuler简介 概述 openEuler的前身是运行在华为公司通用服务器上的操作系统EulerOS。 EulerOS是一款基于Linux内核的开源操作系统&#xff0c;支持X86和ARM等多种处理器架构&#xff0c;伴随着华为公司鲲鹏芯片的研发&#xff0c;EulerOS 理所当然地成为与鲲鹏芯片配…

ElasticSearch 添加IK分词器

ElasticSearch 添加IK分词器 前言一、IK分词器的算法二、Ik分词器的下载安装&#xff08;Winows 版本&#xff09;三、Ik分词器的下载安装&#xff08;Linux 版本&#xff09;四、验证测试&#xff08;postman工具&#xff09;测试 ik_smart 分词算法测试 ik_max_word 分词算法…

被复线远传节点机JR-IPAM-1600

产品描述 JR-IPAM-1600J是一款被复线远传节点机&#xff0c;通过传统双绞线电缆&#xff08;被复线\网线\对数电缆\矿用电缆等&#xff09;&#xff0c;用户就可以快速组成一个高速的传输网、局域网。它具有传输速率高、运行稳定、快速安装部署的特点&#xff0c;设备特有的AU…

window中借助nginx配置vite+vue项目的反向代理步骤

在官网下载好nginx的安装包后&#xff0c;解压后 CMD打开 start nginx 是启动命令 nginx -s stop 停止服务 nginx -s reload 如果重写了nginx.conf文件&#xff0c;要执行这条命令 正常情况下 成功启动和成功停止服务长这样 错误情况&解决 如果nginx -s stop失败 ngi…

移动取证和 Android 安全

当今的数字时代已经产生了许多技术进步&#xff0c;无论是智能手机还是虚拟现实、人工智能和物联网 (IoT) 等下一代基础技术。 智能手机已不再只是奢侈品&#xff0c;而是我们生存所必需的东西。根据各种统计数据&#xff0c;如今全球有超过 50% 的人使用手机。 由于数据存储…

图神经网络(GNN)入门笔记(1)——图信号处理与图傅里叶变换

一、信号处理&#xff1a;时域与频域 时域&#xff08;Time Domain&#xff09;是我们生活中常见的信号表示方式&#xff0c;以横轴为时间&#xff0c;纵轴为信号该时刻的强度&#xff08;幅度&#xff09;&#xff0c;就可以得到一张时域图。打个比方&#xff0c;通过每时每刻…

Cent OS-7的Apache服务配置

WWW是什么&#xff1f; WWW&#xff08;World Wide Web&#xff0c;万维网&#xff09;是一个全球性的信息空间&#xff0c;其中的文档和其他资源通过URL标识&#xff0c;并通过HTTP或其他协议访问。万维网是互联网的一个重要组成部分&#xff0c;但它并不是互联网的全部。互联…

开放式耳机如何选择?五款千万不能错过的开放式耳机机型推荐

在这里我先做一个行业的知识科普&#xff0c;目前市场上有超过80%的品牌&#xff0c;都是非专业的开放式耳机品牌&#xff0c;也就是跨界大牌或者网红品牌&#xff0c;这些品牌由于没有开放式声学的技术沉淀&#xff0c;在制作开放式耳机的时候&#xff0c;通常都是直接套用传统…

补一下 二维 平面直角坐标系 到三维

上一篇帖子写到 二维的平面直角坐标系&#xff0c;是那样的&#xff0c;这次补充一下三维的。首先需要&#xff0c;安装一个包&#xff0c;如下&#xff1a; 然后&#xff0c;把参数输入&#xff0c;输入这个坐标系的参数&#xff0c;如下&#xff1a; 这样就可以输出如下的三…

bat调用Perl脚本接收不到参数

perl -d checkLog.pl --logfileE:\temp4\test.log --batch-name"Test-log"这样写在BAT文件里肯定能接收到参数 checkLog.pl --logfileE:\temp4\test.log --batch-name"Test-log" 但当这样写时,就不一定能接受到参数了! 现象:在Win11系统上还好,只需要在*.…

【机器学习】决定系数(R²:Coefficient of Determination)

决定系数&#xff0c;也称为 R 平方&#xff0c;是一种用于衡量回归模型预测效果的统计指标。它表示了模型解释目标变量总变异的程度&#xff0c;数值介于 0 和 1 之间&#xff0c;数值越接近 1 表明模型的解释力越强。 1. 的定义和公式 的公式如下&#xff1a; 其中&#xf…