39. 组合总和
题目链接/文章讲解:https://programmercarl.com/0039.%E7%BB%84%E5%90%88%E6%80%BB%E5%92%8C.html
视频讲解:https://www.bilibili.com/video/BV1KT4y1M7HJ
思路
int* path;
int pathTop;
int** ans;
int ansTop;
int* length;
void backTracking(int target, int index, int* candidates, int candidatesSize, int sum) {
if(sum >= target) {
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++) {
sum+=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;
}
学习反思
代码定义了几个全局变量:
- "path"是一个数组,用于存储当前正在探索的数字组合。
- "pathTop"是"path"数组中下一个可用位置的索引。
- "ans"是一个二维数组,用于存储所有有效的数字组合。
- "ansTop"是"ans"数组中下一个可用位置的索引。
- "length"是一个数组,用于存储"ans"数组中每个组合的长度。
"backTracking"函数是主要的递归函数,它探索所有可能的组合。它接收目标值、当前候选数数组的索引、候选数数组、候选数数组的大小和当前数字组合的和作为参数。
该函数首先检查当前和是否大于或等于目标值。如果是,它再检查和是否等于目标值。如果是,它创建一个名为"tempPath"的新数组,并将当前组合从"path"数组复制到它。然后,它将"tempPath"数组添加到"ans"数组中,并将组合的长度存储在"length"数组中。最后,它递增"ansTop"变量。然后,该函数继续探索候选数数组中的下一个数字。它通过从当前索引迭代到候选数数组的末尾来实现。对于每个数字,它将其添加到和中,将其添加到"path"数组中,并使用更新后的参数递归调用"backTracking"函数。在递归调用之后,它更新和路径变量以移除添加的数字,实际上回溯到先前的状态。最后,"combinationSum"函数初始化全局变量,调用"backTracking"函数,并设置返回数组的大小和列大小。
40.组合总和II
题目链接/文章讲解:https://programmercarl.com/0040.%E7%BB%84%E5%90%88%E6%80%BB%E5%92%8CII.html
视频讲解:https://www.bilibili.com/video/BV12V4y1V73A
思路
int* path;
int pathTop;
int** ans;
int ansTop;
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) {
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;
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;
}
学习反思
在回溯算法之前,对候选数数组进行了快速排序。通过将相同的元素放在一起,可以避免在搜索过程中出现重复的组合。这样做可以减少递归调用的次数,从而提高算法的效率。其次,在递归调用的过程中,添加了一个判断条件。当遍历到同一层级的相同元素时,跳过对其的处理。这样可以避免产生重复的组合,进一步提高算法的效率。此外,对动态分配的数组也进行了一些调整。在ans数组中,不再使用ansTop指针来记录数组元素的位置,而是直接将ans数组中的每个元素与length数组中的相应位置关联起来。这样可以更方便地管理组合的长度信息。
131.分割回文串
题目链接/文章讲解:
https://programmercarl.com/0131.%E5%88%86%E5%89%B2%E5%9B%9E%E6%96%87%E4%B8%B2.html
视频讲解:https://www.bilibili.com/video/BV1c54y1e7k6
思路
char** path;
int pathTop;
char*** ans;
int ansTop = 0;
int* ansSize;
void copy() {
char** tempPath = (char**)malloc(sizeof(char*) * pathTop);
int i;
for(i = 0; i < pathTop; i++) {
tempPath[i] = path[i];
}
ans[ansTop] = tempPath;
ansSize[ansTop++] = pathTop;
}
bool isPalindrome(char* str, int startIndex, int endIndex) {
while(endIndex >= startIndex) {
if(str[endIndex--] != str[startIndex++])
return 0;
}
return 1;
}
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];
tempString[index] = '\0';
return tempString;
}
void backTracking(char* str, int strLen, int startIndex) {
if(startIndex >= strLen) {
copy();
return ;
}
int i;
for(i = startIndex; i < strLen; i++) {
if(isPalindrome(str, startIndex, i)) {
path[pathTop++] = cutString(str, startIndex, i);
}
else {
continue;
}
backTracking(str, strLen, i + 1);
pathTop--;
}
}
char*** partition(char* s, int* returnSize, int** returnColumnSizes){
int strLen = strlen(s);
path = (char**)malloc(sizeof(char*) * strLen);
ans = (char***)malloc(sizeof(char**) * 40000);
ansSize = (int*)malloc(sizeof(int) * 40000);
ansTop = pathTop = 0;
backTracking(s, strLen, 0);
*returnSize = ansTop;
*returnColumnSizes = (int*)malloc(sizeof(int) * ansTop);
int i;
for(i = 0; i < ansTop; ++i) {
(*returnColumnSizes)[i] = ansSize[i];
}
return ans;
}
学习反思
- 将输入的字符串转换为字符数组,以便于通过索引访问和取子串。
- 定义一个辅助函数isPalindrome,使用双指针法判断子串是否为回文字符串。
- 初始化结果数组result和切割方案数组path。
- 定义回溯函数backtracking,传入参数startIndex表示当前要切割的子串的起始索引。
- 终止条件为startIndex超过字符串长度,此时收集切割方案path并添加到结果数组result中。
- 遍历从startIndex到字符串末尾的所有子串,判断子串是否为回文字符串。如果是,将子串添加到切割方案path中,然后递归调用backtracking函数来寻找下一个起始位置的子串。
- 在递归调用之后,如果切割方案path非空,将最后一个元素弹出,进行回溯操作。
- 最后调用backtracking函数,startIndex初始为0,完成递归回溯的过程。
- 返回结果数组result。
在Swift中,使用数组来存储结果集和切割方案,通过append和removeLast方法来添加和移除元素。通过回溯算法,可以找到所有满足条件的切割方案。时间复杂度为O(N * 2^N)
总结
对回溯算法有了更深刻的认识,加油!!!