代码随想录算法训练营第二十三天| 39. 组合总和 40.组合总和II 131.分割回文串

本文主要是介绍代码随想录算法训练营第二十三天| 39. 组合总和 40.组合总和II 131.分割回文串,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

  • 一、LeetCode 39. 组合总和
    • 思路:
    • C++代码
  • 二、LeetCode 40.组合总和II
    • 思路
    • C++代码
  • 三、LeetCode 131.分割回文串
    • 思路
    • C++代码
  • 总结


一、LeetCode 39. 组合总和

题目链接:LeetCode 39. 组合总和

文章讲解:代码随想录
视频讲解:带你学透回溯算法-组合总和(对应「leetcode」力扣题目:39.组合总和)| 回溯法精讲!

思路:

 题目要求从给出的数组里选取几个数,使得总和等于给出的target,并且数组中的数可以无限制重复选取,那么相对于前面做过的216. 组合总和 III来说,只需要在每层循环的时候考虑到上次选过的数字可以重复选取即可,即让循环条件中的初始值int i = pre:

for(int i = pre; i < candidates.size(); i++) //pre为上一层选取的数字的下标

 其余的代码基本不变,要找题目要求编程即可。

C++代码

class Solution {
private:vector<vector<int>> comb;vector<int> set;void backtrack(vector<int> candidates, int target, int sum, int pre){//pre记录前一个数字的下标if(sum > target) return; //剪枝操作if(sum == target){if(comb.size() < 150){comb.push_back(set);}return;}for(int i = pre; i < candidates.size(); i++){ //同一个数字可以重复选取,所以可以从下标pre开始取set.push_back(candidates[i]);sum += candidates[i];backtrack(candidates, target, sum, i);sum -= candidates[i];set.pop_back();}}
public:vector<vector<int>> combinationSum(vector<int>& candidates, int target) {backtrack(candidates, target, 0, 0);return comb;}
};

二、LeetCode 40.组合总和II

题目链接:LeetCode 40.组合总和II

文章讲解:代码随想录
视频讲解:回溯算法中的去重,树层去重树枝去重,你弄清楚了没?| LeetCode:40.组合总和II

思路

 本题的重点在于:每个数字只能选一次,且给出的数组中会出现重复的数字,因此对于解集中的数组需要进行去重操作

 笔者在本题中采用的是在子集生成过程中,对子集树同一层的数进行的去重操作。
在这里插入图片描述
 如图,在子集树的遍历过程中,当数字选取在同一条树支上时,即选取数字都会出现在子集中时,重复数字是可以选取的;而当选取数字在同一树层上时,即在同一个位置上选取数字时,不能重复选取。

 体现在代码中则是当前数字与前一个数字相同,且前一个数字没有被选取时,那么当前的数字就不能选取(因为如果选取,那么后续生成的所有子集都会和选取前一个数生成的子集相同,出现重复),所以我们在递归中加上一个判断条件即可去重。

C++代码

class Solution {
private:vector<vector<int>> comb;vector<int> set;vector<bool> used;int sum;void backtrack(vector<int> candidates, int target, int pre){if(sum == target){comb.push_back(set);return;}for(int i = pre + 1; i < candidates.size() && sum + candidates[i] <= target; i++){if(i > 0 && candidates[i] == candidates[i-1] && !used[i-1]) continue;//遇到同一层相同数字,则跳过此次循环set.push_back(candidates[i]);sum += candidates[i];used[i] = true;backtrack(candidates, target, i);used[i] = false;sum -= candidates[i];set.pop_back();}}
public:vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {sum = 0;sort(candidates.begin(), candidates.end());for(auto x: candidates){used.push_back(false);}backtrack(candidates, target, -1);return comb;}
};

三、LeetCode 131.分割回文串

题目链接:LeetCode 131.分割回文串

文章讲解:代码随想录
视频讲解:带你学透回溯算法-分割回文串(对应力扣题目:131.分割回文串)| 回溯法精讲!

思路

 题目要求将给出的字符串划分为几个回文串的组合,笔者采用回溯算法。对于回文串的判断和处理,笔者采用延伸生成的方法,如图:
在这里插入图片描述
 对于一个已经确定的回文串,如果他的左右两边的字符相等的话,那么加上左右两边的字符会变成一个更长的字符串(这一点适用于奇数长度与偶数长度);因此笔者的算法思想为:在当前未确定的字符串部分找到一个最短的回文串,在这个最短回文串的基础上向两边延伸生成新的回文串;于是对于回溯算法的设计即是在每一层对于最短的回文串进行操作。
在这里插入图片描述
 如图,对于每层回溯的递归逻辑,将当前遍历到的字符作为回文串生成的中心位置字符,在此基础上进行延伸;具体延伸方法为:在原回文串基础上,判断原回文串两侧的字符是否相等,如果相等就可以加入到原串两侧,完成延伸,进入下一层for循环继续延伸;否则跳出for循环。

 回溯递归函数的设计依旧是分为三部曲来编写:

 递归函数的传参以及返回值,笔者设计回溯算法无返回值,设置全局变量储存回文串的分割方案;传参方面,由于笔者在递归函数中处理的是回文串最中间的字符,因此需要一个下标记录当前的字符位置,同时需要另一个下标记录回文串可以到达的最左端的位置(用来控制for循环),因此递归的传参和返回为:

void backtrack(int cur, int pre)

 终止条件设计为,递归函数传入的当前字符的位置大于原串的长度,将当前方案存入全局变量中,递归函数返回。

if (cur >= size) {palindrome.push_back(set);return;
}

 递归函数逻辑如下:

 由于该算法中,奇数长度与偶数长度的回文串生成方式有区别,所以需要分开判断延伸的条件:

//延伸条件判断
if (palindrome[0][cur - i] == palindrome[0][cur + i]) //奇数长度回文串if (palindrome[0][cur - i] == palindrome[0][cur + i + 1]) //偶数长度回文串

 由于奇数长度可以直接将当前的字符作为第一个回文串,偶数长度需要进行一次判断才能确定第一个回文串,因此在循环逻辑和循环条件上也有区别,因此奇数长度和偶数长度笔者分成两个for循环来写。

for (int i = 0; i < size - cur && i <= cur - pre; i++) //奇数长度回文串for (int i = 0; i < size - cur - 1 && i <= cur - pre; i++) //偶数长度回文串

for循环中,当满足两端字符相等的延伸条件时,扩展回文串,并且删除前面一个字符(因为前面一个字符被包含进当前的回文串了),进入下一层递归。

 由于本题和笔者算法的特殊性,在递归返回时不直接进行回溯,而是当回文串两端字符不相等时,即不能再延伸出更长的回文串时,将回文串中包含过的左侧的元素依次返还,便于返回上一层递归继续操作:

int b = odd.size() / 2; //遍历完毕,归还前面的元素
for (int j = 0; j < b; j++) {string tmp(1, odd[j]);set.push_back(tmp);
}

C++代码

class Solution {
private:vector<vector<string>> palindrome;vector<string> set;int size;void backtrack(int cur, int pre) {if (cur >= size) {palindrome.push_back(set);return;}string odd = "";string even = "";for (int i = 0; i < size - cur && i <= cur - pre; i++) {//奇数长度回文串if (odd.empty()) {  //奇数个存在一个的情况,除此以外的情况与偶数个类似odd = palindrome[0][cur];set.push_back(odd);backtrack(cur + 1, pre);set.pop_back();}else {if (palindrome[0][cur - i] == palindrome[0][cur + i]) {odd = palindrome[0][cur - i] + odd + palindrome[0][cur - i];set.pop_back();set.push_back(odd);backtrack(cur + i + 1, cur + i + 1);set.pop_back();}else {break;}}}int b = odd.size() / 2; //遍历完毕,归还前面的元素for (int j = 0; j < b; j++) {string tmp(1, odd[j]);set.push_back(tmp);}for (int i = 0; i < size - cur - 1 && i <= cur - pre; i++) {if (palindrome[0][cur - i] == palindrome[0][cur + i + 1]) { //偶数长度回文串even = palindrome[0][cur - i] + even + palindrome[0][cur - i];if (i > 0) set.pop_back();set.push_back(even);backtrack(cur + i + 2, cur + i + 2);set.pop_back();}else {break;}}b = (even.size() / 2) - 1; //遍历完毕,归还元素for (int j = 0; j < b; j++) {string tmp(1, even[j]);set.push_back(tmp);}}
public:vector<vector<string>> partition(string s) {size = s.size();for (auto x : s) { //构建一个初始集合,便于生成string pal;pal.push_back(x);set.push_back(pal);}palindrome.push_back(set);set.clear();backtrack(0, 0);palindrome.erase(palindrome.begin()); //构建的初始集合和生成的第一个集合重复,因此需要删除一个(可优化)return palindrome;}
};


总结

 回溯法的进阶应用。在编写回溯算法时仍要注意递归的逻辑结构、终止条件和剪枝条件、循环的边界条件,以及递归返回时数值的恢复(回溯),都是回溯算法的重点问题。


文章图片来源:代码随想录 (https://programmercarl.com/)

这篇关于代码随想录算法训练营第二十三天| 39. 组合总和 40.组合总和II 131.分割回文串的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1115725

相关文章

不懂推荐算法也能设计推荐系统

本文以商业化应用推荐为例,告诉我们不懂推荐算法的产品,也能从产品侧出发, 设计出一款不错的推荐系统。 相信很多新手产品,看到算法二字,多是懵圈的。 什么排序算法、最短路径等都是相对传统的算法(注:传统是指科班出身的产品都会接触过)。但对于推荐算法,多数产品对着网上搜到的资源,都会无从下手。特别当某些推荐算法 和 “AI”扯上关系后,更是加大了理解的难度。 但,不了解推荐算法,就无法做推荐系

康拓展开(hash算法中会用到)

康拓展开是一个全排列到一个自然数的双射(也就是某个全排列与某个自然数一一对应) 公式: X=a[n]*(n-1)!+a[n-1]*(n-2)!+...+a[i]*(i-1)!+...+a[1]*0! 其中,a[i]为整数,并且0<=a[i]<i,1<=i<=n。(a[i]在不同应用中的含义不同); 典型应用: 计算当前排列在所有由小到大全排列中的顺序,也就是说求当前排列是第

csu 1446 Problem J Modified LCS (扩展欧几里得算法的简单应用)

这是一道扩展欧几里得算法的简单应用题,这题是在湖南多校训练赛中队友ac的一道题,在比赛之后请教了队友,然后自己把它a掉 这也是自己独自做扩展欧几里得算法的题目 题意:把题意转变下就变成了:求d1*x - d2*y = f2 - f1的解,很明显用exgcd来解 下面介绍一下exgcd的一些知识点:求ax + by = c的解 一、首先求ax + by = gcd(a,b)的解 这个

综合安防管理平台LntonAIServer视频监控汇聚抖动检测算法优势

LntonAIServer视频质量诊断功能中的抖动检测是一个专门针对视频稳定性进行分析的功能。抖动通常是指视频帧之间的不必要运动,这种运动可能是由于摄像机的移动、传输中的错误或编解码问题导致的。抖动检测对于确保视频内容的平滑性和观看体验至关重要。 优势 1. 提高图像质量 - 清晰度提升:减少抖动,提高图像的清晰度和细节表现力,使得监控画面更加真实可信。 - 细节增强:在低光条件下,抖

hdu4869(逆元+求组合数)

//输入n,m,n表示翻牌的次数,m表示牌的数目,求经过n次操作后共有几种状态#include<iostream>#include<algorithm>#include<cstring>#include<stack>#include<queue>#include<set>#include<map>#include<stdio.h>#include<stdlib.h>#includ

【数据结构】——原来排序算法搞懂这些就行,轻松拿捏

前言:快速排序的实现最重要的是找基准值,下面让我们来了解如何实现找基准值 基准值的注释:在快排的过程中,每一次我们要取一个元素作为枢纽值,以这个数字来将序列划分为两部分。 在此我们采用三数取中法,也就是取左端、中间、右端三个数,然后进行排序,将中间数作为枢纽值。 快速排序实现主框架: //快速排序 void QuickSort(int* arr, int left, int rig

csu1328(近似回文串)

题意:求近似回文串的最大长度,串长度为1000。 解题思路:以某点为中心,向左右两边扩展,注意奇偶分开讨论,暴力解即可。时间复杂度O(n^2); 代码如下: #include<iostream>#include<algorithm>#include<stdio.h>#include<math.h>#include<cstring>#include<string>#inclu

活用c4d官方开发文档查询代码

当你问AI助手比如豆包,如何用python禁止掉xpresso标签时候,它会提示到 这时候要用到两个东西。https://developers.maxon.net/论坛搜索和开发文档 比如这里我就在官方找到正确的id描述 然后我就把参数标签换过来

poj 3974 and hdu 3068 最长回文串的O(n)解法(Manacher算法)

求一段字符串中的最长回文串。 因为数据量比较大,用原来的O(n^2)会爆。 小白上的O(n^2)解法代码:TLE啦~ #include<stdio.h>#include<string.h>const int Maxn = 1000000;char s[Maxn];int main(){char e[] = {"END"};while(scanf("%s", s) != EO

poj 1258 Agri-Net(最小生成树模板代码)

感觉用这题来当模板更适合。 题意就是给你邻接矩阵求最小生成树啦。~ prim代码:效率很高。172k...0ms。 #include<stdio.h>#include<algorithm>using namespace std;const int MaxN = 101;const int INF = 0x3f3f3f3f;int g[MaxN][MaxN];int n