Day23.一刷数据结构算法(C语言版) 39组合总和;40组合总和II;131分割回文串

一、39组合总和

        本题是集合里元素可以用无数次,那么和组合问题的差别,其实仅在于对startIndex上的控制

        题目链接:组合总和

        文章讲解:代码随想录

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

1.思路分析

        本题和77.组合,216.组合总和III的区别是:本题没有数量要求,可以无限重复,但是有总和的限制,所以间接的也是有个数的限制。

        本题搜索的过程抽象成树形结构如下:

        注意图中叶子节点的返回条件,因为本题没有组合数量要求,仅仅是总和的限制,所以递归没有层数的限制,只要选取的元素总和超过target,就返回! 

        回溯三部曲: 

        1)递归函数参数

        这里依然是定义两个全局变量,二维数组result存放结果集,数组path存放符合条件的结果。(这两个变量可以作为函数参数传入),另外还有各自的栈顶指针ansTop、pathTop。

        首先是题目中给出的参数,集合candidates, 和目标值target。

        此外我还定义了int型的sum变量来统计单一结果path里的总和,其实这个sum也可以不用,用target做相应的减法就可以了,最后如何target==0就说明找到符合的结果了,但为了代码逻辑清晰,我依然用了sum。

        同时,本题还需要startIndex来控制for循环起始位置length记录每个path数组长度。

int* path;
int pathTop;
int** ans;
int ansTop;
//记录每一个和等于target的path数组长度
int* length;void backTracking(int target, int index, int* candidates, int candidatesSize, int sum)

        2)递归终止条件

        从叶子节点可以清晰看到,终止只有两种情况,sum大于target和sum等于target。

sum等于target的时候,需要收集结果,代码如下:

if(sum >= target) {//若sum等于target,将当前的组合放入ans数组中if(sum == target) {int* tempPath = (int*)malloc(sizeof(int) * pathTop);int j;for(j = 0; j < pathTop; j++) {tempPath[j] = path[j];}ans[ansTop] = tempPath;length[ansTop++] = pathTop;}return ;}

        3)单层搜索逻辑

        单层for循环依然是从startIndex开始,搜索candidates集合,且可重复选取

int i;for(i = index; i < candidatesSize; i++) {//将当前数字大小加入sumsum+=candidates[i];path[pathTop++] = candidates[i];backTracking(target, i, candidates, candidatesSize, sum);sum-=candidates[i];pathTop--;}

2.代码详解

/*** Return an array of arrays of size *returnSize.* The sizes of the arrays are returned as *returnColumnSizes array.* Note: Both returned array and *columnSizes array must be malloced, assume caller calls free().*/
int* path;
int pathTop;
int** ans;
int ansTop;
//记录每一个和等于target的path数组长度
int* length;void backTracking(int target, int index, int* candidates, int candidatesSize, int sum) {//若sum>=target就应该终止遍历if(sum >= target) {//若sum等于target,将当前的组合放入ans数组中if(sum == target) {int* tempPath = (int*)malloc(sizeof(int) * pathTop);int j;for(j = 0; j < pathTop; j++) {tempPath[j] = path[j];}ans[ansTop] = tempPath;length[ansTop++] = pathTop;}return ;}int i;for(i = index; i < candidatesSize; i++) {//将当前数字大小加入sumsum+=candidates[i];path[pathTop++] = candidates[i];backTracking(target, i, candidates, candidatesSize, sum);sum-=candidates[i];pathTop--;}
}int** combinationSum(int* candidates, int candidatesSize, int target, int* returnSize, int** returnColumnSizes){//初始化变量path = (int*)malloc(sizeof(int) * 50);ans = (int**)malloc(sizeof(int*) * 200);length = (int*)malloc(sizeof(int) * 200);ansTop = pathTop = 0;backTracking(target, 0, candidates, candidatesSize, 0);//设置返回的数组大小*returnSize = ansTop;*returnColumnSizes = (int*)malloc(sizeof(int) * ansTop);int i;for(i = 0; i < ansTop; i++) {(*returnColumnSizes)[i] = length[i];}return ans;
}

 二、40组合总和II

        本题开始涉及到一个问题了:去重。

        注意题目中给我们 集合是有重复元素的,那么求出来的 组合有可能重复,但题目要求不能有重复组合。 

        题目链接:组合总和II

        文章讲解:代码随想录

        视频讲解:回溯算法中的去重,树层去重树枝去重,你弄清楚了没?40.组合总和II

1.思路分析

        这道题目和 39组合总和 有如下区别:

        本题candidates 中的每个数字在每个组合中只能使用一次。

        本题数组candidates的元素是有重复的,而 39组合总和 是无重复元素的数组candidates        最后本题和 39组合总和 要求一样,解集不能包含重复的组合。本题的难点在于:集合(数组candidates)有重复元素,但还不能有重复的组合

        因此这道题我们要在搜索过程中去掉重复组合。 

        我们都知道组合问题可以抽象为树形结构,那么“使用过”在这个树形结构上是有两个维度的,一个维度是同一树枝上使用过,一个维度是同一树层上使用过。没有理解这两个层面上的“使用过” 是造成大家没有彻底理解去重的根本原因。

        那么问题来了,我们是要同一树层上使用过,还是同一树枝上使用过呢?

        回看一下题目,元素在同一个组合内是可以重复的,怎么重复都没事,但两个组合不能相同。

        所以我们要去重的是同一树层上的“使用过”,同一树枝上的都是一个组合里的元素,不用去重

        强调一下,树层去重的话,需要对数组排序! 

        为了理解去重我们来举一个例子,candidates = [1, 1, 2], target = 3,(方便起见candidates已经排序了) 

        回溯三部曲:

        1)递归函数参数

int* path;
int pathTop;
int** ans;
int ansTop;
//记录ans中每一个一维数组的大小
int* length;void backTracking(int* candidates, int candidatesSize,  int target, int sum, int startIndex)

        2)递归终止条件

if(sum >= target) {//若sum等于target,复制当前path进入if(sum == target) {int* tempPath = (int*)malloc(sizeof(int) * pathTop);int j;for(j = 0; j < pathTop; j++) {tempPath[j] = path[j];}length[ansTop] = pathTop;ans[ansTop++] = tempPath;}return ;}

        3)单层搜索逻辑

        循环过程中,如果遇到与前一个相同的元素就应该跳过,因此运用到了continue。

int i;for(i = startIndex; i < candidatesSize; i++) {//对同一层树中使用过的元素跳过if(i > startIndex && candidates[i] == candidates[i-1])continue;path[pathTop++] = candidates[i];sum += candidates[i];backTracking(candidates, candidatesSize, target, sum, i + 1);//回溯sum -= candidates[i];pathTop--;}

        补充:

        在代码中,为了将数组排序,用到了cmp函数以及qsort函数,具体用法如下:

int cmp(const void* a1, const void* a2) {return *((int*)a1) - *((int*)a2);
}
  • 它接受两个const void*参数a1和a2,它们分别表示要比较的数组元素的指针。
  • 在函数内部,这些指针被强制转换为int*类型,以将它们视为整数指针。
  • 使用*对a1和a2指向的实际整数进行解引用,然后进行相减操作。
  • 如果减法的结果为负数,则意味着在排序后a1应该位于a2之前。如果是正数,则a1应该位于a2之后。如果结果为零,则它们被视为相等。
  • 返回比较的结果。

        然后,可以将这个函数与要排序的数组一起传递给qsort函数。qsort使用这个比较函数对数组元素进行升序排序。

//快速排序candidates,让相同元素挨到一起qsort(candidates, candidatesSize, sizeof(int), cmp);
// qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *));
  • base:指向要排序的数组的首元素的指针。
  • nmemb:数组中元素的个数。
  • size:每个元素的大小(以字节为单位)。
  • compar:用于比较两个元素的函数指针。

2.代码详解

/*** Return an array of arrays of size *returnSize.* The sizes of the arrays are returned as *returnColumnSizes array.* Note: Both returned array and *columnSizes array must be malloced, assume caller calls free().*/
int* path;
int pathTop;
int** ans;
int ansTop;
//记录ans中每一个一维数组的大小
int* length;
int cmp(const void* a1, const void* a2) {return *((int*)a1) - *((int*)a2);
}void backTracking(int* candidates, int candidatesSize,  int target, int sum, int startIndex) {if(sum >= target) {//若sum等于target,复制当前path进入if(sum == target) {int* tempPath = (int*)malloc(sizeof(int) * pathTop);int j;for(j = 0; j < pathTop; j++) {tempPath[j] = path[j];}length[ansTop] = pathTop;ans[ansTop++] = tempPath;}return ;}int i;for(i = startIndex; i < candidatesSize; i++) {//对同一层树中使用过的元素跳过if(i > startIndex && candidates[i] == candidates[i-1])continue;path[pathTop++] = candidates[i];sum += candidates[i];backTracking(candidates, candidatesSize, target, sum, i + 1);//回溯sum -= candidates[i];pathTop--;}
}int** combinationSum2(int* candidates, int candidatesSize, int target, int* returnSize, int** returnColumnSizes){path = (int*)malloc(sizeof(int) * 50);ans = (int**)malloc(sizeof(int*) * 100);length = (int*)malloc(sizeof(int) * 100);pathTop = ansTop = 0;//快速排序candidates,让相同元素挨到一起qsort(candidates, candidatesSize, sizeof(int), cmp);backTracking(candidates, candidatesSize, target, 0, 0);*returnSize = ansTop;*returnColumnSizes = (int*)malloc(sizeof(int) * ansTop);int i;for(i = 0; i < ansTop; i++) {(*returnColumnSizes)[i] = length[i];}return ans;
}

三、131分割回文串

        本题较难,大家先看视频来理解 分割问题,明天还会有一道分割问题,先打打基础。

        题目链接:分割回文串

        文章讲解:代码随想录 

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

1.思路分析

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

        例如对于字符串abcdef:

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

        感受出来了不?

        所以切割问题,也可以抽象为一棵树形结构,如图:

        递归用来纵向遍历,for循环用来横向遍历,切割线(就是图中的红线)切割到字符串的结尾位置,说明找到了一个切割方法。

        此时可以发现,切割问题的回溯搜索的过程和组合问题的回溯搜索的过程是差不多的。

        回溯三部曲:

        1)递归函数参数

char** path;
int pathTop;
char*** ans;
int ansTop = 0;
int* ansSize;void backTracking(char* str, int strLen,  int startIndex)

        2)递归函数终止条件

        从树形结构的图中可以看出:切割线切到了字符串最后面,说明找到了一种切割方法,此时就是本层递归的终止条件。 

        那么在代码里什么是切割线呢?

在处理组合问题的时候,递归参数需要传入startIndex,表示下一轮递归遍历的起始位置,这个startIndex就是切割线。

        所以终止条件代码如下:

if(startIndex >= strLen) {//将path拷贝到ans中copy();return ;}

        3)单层搜索逻辑

        在循环中,[startIndex, i] 就是要截取的子串。 

        首先判断这个子串是不是回文,如果是回文,就将cutString(str, startIndex, i)加入在path[pathTop++]中,path用来记录切割过的回文子串。 

int i;for(i = startIndex; i < strLen; i++) {//若从subString到i的子串是回文字符串,将其放入path中if(isPalindrome(str, startIndex, i)) {path[pathTop++] = cutString(str, startIndex, i);}//若从startIndex到i的子串不为回文字符串,跳过这一层 else {continue;}//递归判断下一层backTracking(str, strLen, i + 1);//回溯,将path中最后一位元素弹出pathTop--;}

        补充:

        1)如何判断回文子串?

        最后我们看一下回文子串要如何判断了,判断一个字符串是否是回文。

        可以使用双指针法,一个指针从前向后,一个指针从后向前,如果前后指针所指向的元素是相等的,就是回文字符串了。

//判断字符串是否为回文字符串
bool isPalindrome(char* str, int startIndex, int endIndex) {//双指针法:当endIndex(右指针)的值比startIndex(左指针)大时进行遍历while(endIndex >= startIndex) {//若左指针和右指针指向元素不一样,返回Falseif(str[endIndex--] != str[startIndex++])return 0;}return 1;
}

        2)如何切割回文子串?

//切割从startIndex到endIndex子字符串
char* cutString(char* str, int startIndex, int endIndex) {//开辟字符串的空间char* tempString = (char*)malloc(sizeof(char) * (endIndex - startIndex + 2));int i;int index = 0;//复制子字符串for(i = startIndex; i <= endIndex; i++)tempString[index++] = str[i];//用'\0'作为字符串结尾tempString[index] = '\0';return tempString;
}

2.代码详解

/*** Return an array of arrays of size *returnSize.* The sizes of the arrays are returned as *returnColumnSizes array.* Note: Both returned array and *columnSizes array must be malloced, assume caller calls free().*/
char** path;
int pathTop;
char*** ans;
int ansTop = 0;
int* ansSize;//将path中的字符串全部复制到ans中
void copy() {//创建一个临时tempPath保存path中的字符串char** tempPath = (char**)malloc(sizeof(char*) * pathTop);int i;for(i = 0; i < pathTop; i++) {tempPath[i] = path[i];}//保存tempPathans[ansTop] = tempPath;//将当前path的长度(pathTop)保存在ansSize中ansSize[ansTop++] = pathTop;
}//判断字符串是否为回文字符串
bool isPalindrome(char* str, int startIndex, int endIndex) {//双指针法:当endIndex(右指针)的值比startIndex(左指针)大时进行遍历while(endIndex >= startIndex) {//若左指针和右指针指向元素不一样,返回Falseif(str[endIndex--] != str[startIndex++])return 0;}return 1;
}//切割从startIndex到endIndex子字符串
char* cutString(char* str, int startIndex, int endIndex) {//开辟字符串的空间char* tempString = (char*)malloc(sizeof(char) * (endIndex - startIndex + 2));int i;int index = 0;//复制子字符串for(i = startIndex; i <= endIndex; i++)tempString[index++] = str[i];//用'\0'作为字符串结尾tempString[index] = '\0';return tempString;
}void backTracking(char* str, int strLen,  int startIndex) {if(startIndex >= strLen) {//将path拷贝到ans中copy();return ;}int i;for(i = startIndex; i < strLen; i++) {//若从subString到i的子串是回文字符串,将其放入path中if(isPalindrome(str, startIndex, i)) {path[pathTop++] = cutString(str, startIndex, i);}//若从startIndex到i的子串不为回文字符串,跳过这一层 else {continue;}//递归判断下一层backTracking(str, strLen, i + 1);//回溯,将path中最后一位元素弹出pathTop--;}
}char*** partition(char* s, int* returnSize, int** returnColumnSizes){int strLen = strlen(s);//因为path中的字符串最多为strLen个(即单个字符的回文字符串),所以开辟strLen个char*空间path = (char**)malloc(sizeof(char*) * strLen);//存放path中的数组结果ans = (char***)malloc(sizeof(char**) * 40000);//存放ans数组中每一个char**数组的长度ansSize = (int*)malloc(sizeof(int) * 40000);ansTop = pathTop = 0;//回溯函数backTracking(s, strLen, 0);//将ansTop设置为ans数组的长度*returnSize = ansTop;//设置ans数组中每一个数组的长度*returnColumnSizes = (int*)malloc(sizeof(int) * ansTop);int i;for(i = 0; i < ansTop; ++i) {(*returnColumnSizes)[i] = ansSize[i];}return ans;
}

        如果你有问题或者有其他想法,欢迎评论区留言,大家可以一起探讨。

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

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

相关文章

从零开始构建大语言模型(MEAP)

原文&#xff1a;annas-archive.org/md5/c19a4ef8ab1664a3c5a59d52651430e2 译者&#xff1a;飞龙 协议&#xff1a;CC BY-NC-SA 4.0 一、理解大型语言模型 本章包括 大型语言模型&#xff08;LLM&#xff09;背后的基本概念的高层次解释 探索 ChatGPT 类 LLM 源自的 Transfo…

workminer之dht通信部分

workminer是通过SSH爆破传播的挖矿木马&#xff0c;感染后会释放xmrig挖矿程序利用主机的CPU挖取北方门罗币。该样本能够执行特定的指令&#xff0c;指令保存在一个配置文件config中&#xff0c;config文件类似于xml文件&#xff0c;里面有要执行的指令和参数&#xff0c;样本中…

spring cloud eureka 初始化报错(A bean with that name has already been defined)

报错内容 The bean ‘eurekaRegistration’, defined in class path resource [org/springframework/cloud/netflix/eureka/EurekaClientAutoConfiguration E u r e k a C l i e n t C o n f i g u r a t i o n . c l a s s ] , c o u l d n o t b e r e g i s t e r e d . A …

回归预测 | Matlab实现NGO-ESN北方苍鹰算法优化回声状态网络多输入单输出回归预测

回归预测 | Matlab实现NGO-ESN北方苍鹰算法优化回声状态网络多输入单输出回归预测 目录 回归预测 | Matlab实现NGO-ESN北方苍鹰算法优化回声状态网络多输入单输出回归预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 1.Matlab实现NGO-ESN北方苍鹰算法优化回声状态网络…

《QT实用小工具·四十五》可以在界面上游泳的小鱼

1、概述 源码放在文章末尾 该项目实现了灵动的小鱼&#xff0c;可以在界面上跟随鼠标点击自由的游泳&#xff0c;项目demo演示如下所示&#xff1a; 项目部分代码如下所示&#xff1a; #include "magicfish.h" #include <QtMath> #include <QPainter>…

C++:map和set的封装

关于红黑树的模拟实现&#xff0c;大家不清楚的先去看看博主的博客再来看这篇文章&#xff0c;因为set和map的封装底层都是利用用的红黑树。所以这里不会过多介绍红黑树的相关内容&#xff0c;而更多的是去为了契合STL中的红黑树去进行改造&#xff0c;让封装的set和map能够去复…

CMake使用

一、CMake 是什么 CMake 是一个跨平台的自动化构建系统&#xff0c;它使用配置文件 CMakeLists.txt 来管理软件构建过程。CMake 基于 Makefile 做了二次开发。 二、单个文件目录 # CMake 最低版本号要求 cmake_minimum_required(VERSION 3.16.3)# 工程名 project(CMakeSingle)…

uniapp自定义返回事件(封装)

uniapp自定义返回事件 在我们使用uniapp时&#xff0c;我们导航栏一般都是自定义的&#xff0c;比如用uview框架的导航栏&#xff0c;那么返回事件通常会遇到以下几个问题 返回事件前需要做一些额外的处理 h5项目刷新页面后返回失效 返回按钮点击后到指定页面 如果只是监听返…

PhotosCollage for Mac:优雅且实用的照片拼贴软件

PhotosCollage for Mac是一款优雅且实用的照片拼贴软件&#xff0c;为Mac用户提供了一个便捷、高效的平台&#xff0c;以创建精美、个性化的照片拼贴作品。 PhotosCollage for Mac v1.4.1激活版下载 该软件界面简洁直观&#xff0c;操作便捷。用户只需将想要拼贴的照片拖入“照…

CSS基础:position定位的5个类型详解!

你好&#xff0c;我是云桃桃。 一个希望帮助更多朋友快速入门 WEB 前端的程序媛。 云桃桃-大专生&#xff0c;一枚程序媛&#xff0c;感谢关注。回复 “前端基础题”&#xff0c;可免费获得前端基础 100 题汇总&#xff0c;回复 “前端工具”&#xff0c;可获取 Web 开发工具合…

Andorid复习

组件 TextView 阴影 android:shadowColor"color/red" 阴影颜色android:shadowRadius"3.0" 阴影模糊度&#xff08;大小&#xff09;android:shadowDx"10.0" 横向偏移android:shadowDy"10.0" 跑马灯 这里用自定义控件 public cla…

日本极致产品力 | 源自内蒙古,日本99.7%的人都喝过都百年畅销饮料

​《极致产品力》日本深度研学是一个顾问式课程,可以帮助企业找产品、找方向、找方法,在日本终端市场考察中洞悉热销产品背后的成功逻辑,了解最新最前沿的产品趋势和机会。结合日本消费趋势中国转化的众多经验,从品牌、包装、卖点、技术和生产工艺等多方面寻找中国市场的解决方…

WIFI/BT中蓝牙的硬件资源是如何调度的 UART和PCM接口传输的是什么信号

安卓或IOS手机中&#xff0c;wifi/bt中的蓝牙是如何调度硬件资源的&#xff0c;尤其是UART和PCM是如何分配的。M.2 wifi/bt模块或其他形式的模块中&#xff0c;蓝牙是如何调度硬件资源的&#xff0c;尤其是UART和PCM是如何分配的。今天我们就图文并茂的解决这个问题。 蓝牙文件…

新买的设备自带的仪器校准证书,是否可以作为校准报告使用?

设备在刚刚购买时&#xff0c;有些商家会承诺&#xff0c;在交付设备的同时&#xff0c;还会交付设备的校准证书&#xff0c;这证书是附赠的&#xff0c;属于商家给客户的一种福利&#xff0c;面对附赠的仪器校准证书&#xff0c;很多客户也会有疑惑&#xff0c;这附赠的证书有…

UTONMOS:用区块链技术拓展商业边界在哪里?

引言 大约从 2021 年Web 3 这个新概念开始受到风险基金和科技圈的普遍关注。但如果你对过去几年区块链的发展历史足够了解&#xff0c;就应该已经意识到现在的 Web 3 并不是什么新技术&#xff0c;甚至不是旧技术的进步&#xff0c;它只是一个基于区块链技术的宏大构想。 我是…

Unity 如何制作和发布你的 Package

一、制作你的第一个 Package Unity Package 不做过多赘述&#xff0c;像 URP 本质上也是一个 Package&#xff0c;在 Unity 中可以通过菜单栏 → Window → Package manager 来管理你当前的所有 Package 本篇文章主要介绍&#xff1a;如何制作并发布属于你的 Package 1.1 Pac…

自制贪吃蛇小游戏

此片文章涉及到到控制台设置的相关操作&#xff0c;虚拟键码&#xff0c;宽字符输出等&#xff0c;有些地方大家可能会看不懂&#xff0c;可以阅读以下文章来进一步了解&#xff1a; 控制台程序设置-CSDN博客 效果展示&#xff1a; QQ2024428-181932 源码已放在文章结尾 目录 …

Graph Neural Networks(GNN)学习笔记

本学习笔记的组织结构是&#xff0c;先跟李沐老师学一下&#xff0c;再去kaggle上寻摸一下有没有类似的练习&#xff0c;浅做一下&#xff0c;作为一个了解。 ———————————0428更新—————————————— 课程和博客看到后面准备主要看两个&#xff1a;GCN和…

ubuntu安装Anaconda安装及conda使用

一. 安装anaconda3详细教程 1、下载镜像 清华大学开源软件镜像站下载地址&#xff1a; https://mirrors.tuna.tsinghua.edu.cn/anaconda/archive/ 下拉到最低端选择Linux&#xff0c;选择最新版&#xff08;32/64位&#xff09;下载。这里我下载的是版本Anaconda3-4.3.30-Linux…

Java中的File类

File类概述和构造方法 File&#xff1a;它是文件和目录路径名的抽象表示 文件和目录是可以通过File封装成对象的 对于File而言&#xff0c;其封装的并不是一个真正存在的文件&#xff0c;仅仅是一个路径名而已&#xff0c;它可以存在&#xff0c;也可以不存在 我们对Fie的操…