代码随想录算法训练营第二十六天
- LeetCode 39. 组合总和
- 题目描述
- 思路
- 参考代码
- 总结
- LeetCode 40.组合总和II
- 题目描述
- 思路
- 参考代码
- LeetCode 131.分割回文串
- 题目描述
- 思路
- 切割问题
- 回文判断
- 参考代码
- 总结
LeetCode 39. 组合总和
题目链接:39. 组合总和
文章讲解:代码随想录#39. 组合总和
视频讲解:带你学透回溯算法-组合总和(对应「leetcode」力扣题目:39.组合总和)| 回溯法精讲!
题目描述
给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。
candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。
对于给定的输入,保证和为 target 的不同组合数少于 150 个。
示例1
输入:candidates = [2,3,6,7], target = 7
输出:[[2,2,3],[7]]
解释:
2 和 3 可以形成一组候选,2 + 2 + 3 = 7 。注意 2 可以使用多次。
7 也是一个候选, 7 = 7 。
仅有这两种组合。
示例2
candidates = [2,3,5], target = 8
输出: [[2,2,2,2],[2,3,3],[3,5]]
示例3
输入: candidates = [2], target = 1
输出: []
提示
- 1 <= candidates.length <= 30
- 2 <= candidates[i] <= 40
- candidates 的所有元素 互不相同
- 1 <= target <= 40
思路
同一个数字可以无限制重复使用,这和之前 77.组合 不太一样,所以每次递归遍历时,索引不需要+1。
target作为递归函数的终止条件。
也可以做一些剪枝的操作,比如前sum之和大于target时直接返回。
参考代码
/*** 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 **res;
int cnt;
int sum;
typedef struct {int index;int nums[30];
}Data;
Data data = {0};void backtracking(int* candidates, int candidatesSize, int target, int** returnColumnSizes, int idx)
{if (sum == target) {res[cnt] = (int *)malloc(data.index * sizeof(int));for (int i = 0; i < data.index; i++) {res[cnt][i] = data.nums[i]; }(*returnColumnSizes)[cnt] = data.index;cnt++;return;}for (int i = idx; i < candidatesSize; i++) {if (sum + candidates[i] > target) continue; // 剪枝,跳过当前循环data.nums[data.index++] = candidates[i];sum += candidates[i];backtracking(candidates, candidatesSize, target, returnColumnSizes, i);sum -= candidates[i]; // 回溯data.index--;data.nums[data.index] = 0;}
}int** combinationSum(int* candidates, int candidatesSize, int target, int* returnSize, int** returnColumnSizes) {res = (int**)malloc(1000 * sizeof(int));*returnColumnSizes = (int*)malloc(sizeof(int) * 200);cnt = 0;sum = 0;backtracking(candidates, candidatesSize, target, returnColumnSizes, 0);*returnSize = cnt;return res;
}
总结
- 对于返回值以及returnColumnSizes的初始化时,空间大小不好选择,我每次都是试出来了,这个不太好。
LeetCode 40.组合总和II
题目链接:40.组合总和II
文章讲解:代码随想录#40.组合总和II
视频讲解:回溯算法中的去重,树层去重树枝去重,你弄清楚了没?| LeetCode:40.组合总和II
题目描述
给定一个候选人编号的集合 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用 一次 。
注意:解集不能包含重复的组合。
示例1
输入: candidates = [10,1,2,7,6,1,5], target = 8,
输出:
[
[1,1,6],
[1,2,5],
[1,7],
[2,6]
]
示例2
输入: candidates = [2,5,2,1,2], target = 5,
输出:
[
[1,2,2],
[5]
]
提示
- 1 <= candidates.length <= 100
- 1 <= candidates[i] <= 50
- 1 <= target <= 30
思路
需要注意几个点如下:
- candidates 数组中的每个数字在每个组合中只能使用一次。
- candidates 数组中的元素会有重复的。
这道题我们需要考虑去重的情况了。
所谓去重,其实就是使用过的元素不能重复使用。
那应该如何去重呢?
①首先对candidates 数组进行从小到大的排序,相同的数值的元素就连在一起了。
②构造一个与candidates 数组大小相同的used数组,用来表示元素是否使用过。
具体思路可以查看代码随想录。
我盗用两张图说明一下重复的情况。
在candidates[i] == candidates[i - 1]相同的情况下:
- used[i - 1] == true,说明同一树枝candidates[i - 1]使用过
- used[i - 1] == false,说明同一树层candidates[i - 1]使用过
参考代码
int cnt;
int sum;
int* used;
int** ret;
typedef struct {int index;int nums[100];
} Data;
Data data = {0};int cmp(const void* p1, const void* p2) { return *(int*)p1 - *(int*)p2; }void backtracking(int* candidates, int candidatesSize, int target, int** returnColumnSizes, int idx) {if (sum == target) {ret[cnt] = malloc(sizeof(int) * data.index);for (int i = 0; i < data.index; i++)ret[cnt][i] = data.nums[i];(*returnColumnSizes)[cnt] = data.index;cnt++;return;}for (int i = idx; i < candidatesSize; i++) {if (sum + candidates[i] > target) // 剪枝操作break;// 当前执行到i,如果used[i - 1] == 0,说明candidates[i - 1]已经使用过了,直接跳过// 如果看不懂,可以多看看随想录的思路,然后自已在本地推导几遍if (i > 0 && candidates[i] == candidates[i - 1] && used[i - 1] == 0) continue;sum += candidates[i];data.nums[data.index++] = candidates[i];used[i] = 1;// 必须是i+1,下一层的循环跳过当前层的元素,指向下一个元素backtracking(candidates, candidatesSize, target, returnColumnSizes, i + 1);sum -= candidates[i]; // 回溯data.index--;data.nums[data.index] = 0;used[i] = 0;}
}int** combinationSum2(int* candidates, int candidatesSize, int target,int* returnSize, int** returnColumnSizes) {ret = (int**)malloc(10000 * sizeof(int*));used = (int*)malloc(100 * sizeof(int));*returnColumnSizes = (int*)malloc(sizeof(int) * 100);for (int i = 0; i < 100; i++) // 清空used数组used[i] = 0;cnt = 0;sum = 0;// 排序,将相同值的元素挨在一起qsort(candidates, candidatesSize, sizeof(int), cmp);backtracking(candidates, candidatesSize, target, returnColumnSizes, 0);*returnSize = cnt;return ret;
}
LeetCode 131.分割回文串
题目链接:[131.分割回文串 (https://leetcode.cn/problems/palindrome-partitioning/)
文章讲解:代码随想录#131.分割回文串
视频讲解:带你学透回溯算法-分割回文串(对应力扣题目:131.分割回文串)| 回溯法精讲!
题目描述
给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是 回文串 。返回 s 所有可能的分割方案。
回文串 是正着读和反着读都一样的字符串。
示例1
输入:s = “aab”
输出:[[“a”,“a”,“b”],[“aa”,“b”]]
示例2
输入:s = “a”
输出:[[“a”]]
提示
- 1 <= s.length <= 16
- s 仅由小写英文字母组成
思路
看完题目描述后没有一点儿思路,还是得看一遍视频。
本题有两个关键点:
①字符串切割,可以使用回溯法进行不同方式的切割。
②对切割好的子串进行回文判断,如果是回文,则添加到返回的变量中。
切割问题
其实切割问题,可以采用回溯法进行切割,终止条件就是切割到了字符串的最后一个字符。
递归函数的返回值为void,参数有两个,一个是原字符串,另一个当前层遍历的起始位置idx。
在单层搜索的逻辑中会有一个for循环,相当于对树进行横向遍历,i的起始值为idx,i的终止值为strlen(s)。
首先对以idx/i为起始的字符串进行处理(第一层),
如果[idx, i]的子串为回文,则将子串添加到res变量中,
调用递归函数,对以i+1为起始的字符串进行处理(第二层)。。。
递归函数处理完后,进行回溯操作,然后对i+1继续下一次遍历。
如果不是回文,在同一层i+1继续遍历。
借用一张图说明一下树状关系:
回文判断
可以使用双指针法,一个从头向尾,一个从尾向前开始遍历,如果前后指针指向的元素相等,则说明是回文字符串。
参考代码
char*** partition(char* s, int* returnSize, int** returnColumnSizes) {// 待实现
}
总结
- 这道题的复杂之处就是要对收集的结果赋值给一个三维指针,时间有限,我暂时还没有想清楚,后面补上。