Bootstrap

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

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

学习反思

  1. 将输入的字符串转换为字符数组,以便于通过索引访问和取子串。
  2. 定义一个辅助函数isPalindrome,使用双指针法判断子串是否为回文字符串。
  3. 初始化结果数组result和切割方案数组path。
  4. 定义回溯函数backtracking,传入参数startIndex表示当前要切割的子串的起始索引。
  5. 终止条件为startIndex超过字符串长度,此时收集切割方案path并添加到结果数组result中。
  6. 遍历从startIndex到字符串末尾的所有子串,判断子串是否为回文字符串。如果是,将子串添加到切割方案path中,然后递归调用backtracking函数来寻找下一个起始位置的子串。
  7. 在递归调用之后,如果切割方案path非空,将最后一个元素弹出,进行回溯操作。
  8. 最后调用backtracking函数,startIndex初始为0,完成递归回溯的过程。
  9. 返回结果数组result。

在Swift中,使用数组来存储结果集和切割方案,通过append和removeLast方法来添加和移除元素。通过回溯算法,可以找到所有满足条件的切割方案。时间复杂度为O(N * 2^N)

总结

对回溯算法有了更深刻的认识,加油!!!

;