回溯算法
1、理论
定义
回溯法也可以叫做回溯搜索法,它是一种搜索的方式。回溯是递归的副产品,只要有递归就会有回溯。
效率
因为回溯的本质是穷举,穷举所有可能,然后选出我们想要的答案,如果想让回溯法高效一些,可以加一些剪枝的操作,但也改不了回溯法就是穷举的本质。
解决的问题
回溯法,一般可以解决如下几种问题:
- 组合问题:N个数里面按一定规则找出k个数的集合
- 切割问题:一个字符串按一定规则有几种切割方式
- 子集问题:一个N个数的集合里有多少符合条件的子集
- 排列问题:N个数按一定规则全排列,有几种排列方式
- 棋盘问题:N皇后,解数独等等
组合与排列
组合是不强调元素顺序的,排列是强调元素顺序。
例如:{1, 2} 和 {2, 1} 在组合上,就是一个集合,因为不强调顺序,而要是排列的话,{1, 2} 和 {2, 1} 就是两个集合了。
如何理解回溯法
回溯法解决的问题都可以抽象为树形结构,是的,我指的是所有回溯法的问题都可以抽象为树形结构!
因为回溯法解决的都是在集合中递归查找子集,集合的大小就构成了树的宽度,递归的深度,都构成的树的深度。
回溯法模板
搜索的遍历过程
模板
void backtracking(参数) {
if (终止条件) {
存放结果;
return;
}
for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
处理节点;
backtracking(路径,选择列表); // 递归
回溯,撤销处理结果
}
}
2、组合问题
leetcode 77. 组合(相同集合)
leetcode 77. 组合
给定两个整数 n 和 k,返回 1 … n 中所有可能的 k 个数的组合。
示例:
输入: n = 4, k = 2
输出:
[
[2,4],
[3,4],
[2,3],
[1,2],
[1,3],
[1,4],
]
使用模板进行回溯:
参考 代码随想录 的讲解
/**
* 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().
*/
#define MAX_SIZE 10001
void backtracking(int n, int k, int start, int* path, int pathSize, int** res, int* returnSize, int** returnColumnSizes) {
if (pathSize == k) {
res[*returnSize] = (int*)malloc(sizeof(int) * k);
memcpy(res[*returnSize], path, sizeof(int) * k);
(*returnColumnSizes)[(*returnSize)++] = k;
return;
}
/* 就是解法1的剪纸操作,若n = 4,k = 3时,当前路径长度为1的话,那么从3开始就不需要遍历了;
* 若n = 4,k = 4时,当前路径长度为0的话,那么从2开始就不需要遍历了;
* 即每一层的start最多可以遍历到 n - (k - pathSize) + 1的位置(加1保证start的起始位置)
*
* 还有这里i从start开始遍历防止1、2;2、1这种重复的集合出现
*/
for (int i = start; i <= n - (k - pathSize) + 1; i++) {
path[pathSize] = i;
backtracking(n, k, i + 1, path, pathSize + 1, res, returnSize, returnColumnSizes);
}
}
int** combine(int n, int k, int* returnSize, int** returnColumnSizes){
*returnSize = 0;
int** res = (int**)malloc(sizeof(int*) * MAX_SIZE);
if (n == 0) {
return res;
}
*returnColumnSizes = (int*)calloc(MAX_SIZE, sizeof(int));
int* path = (int*)calloc(k, sizeof(int));
backtracking(n, k, 1, path, 0, res, returnSize, returnColumnSizes);
return res;
}
leetcode 216. 组合总和 III(相同集合)
leetcode 216. 组合总和 III
找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。
说明:
- 所有数字都是正整数。
- 解集不能包含重复的组合。
示例 1:
- 输入: k = 3, n = 7
- 输出: [[1,2,4]]
此题比较77题就是多了一个路径中元素总和的限制,代码如下:
/**
* 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().
*/
#define MAX_SZIE 10001
void backtracking(int k, int target, int start, int sum, int* path, int pathSize, int** res, int* returnSize, int** returnColumnSizes) {
if (sum > target) {
return;
}
if (pathSize == k) {
if (sum == target) {
res[*returnSize] = (int*)malloc(sizeof(int) * k);
memcpy(res[*returnSize], path, sizeof(int) * k);
(*returnColumnSizes)[(*returnSize)++] = k;
}
return;
}
for (int i = start; i <= 9 - (k - pathSize) + 1; i++) {
path[pathSize] = i;
/* i + 1表示起始节点回溯
* pathSize + 1表示当前路径回溯
*/
backtracking(k, target, i + 1, sum + i, path, pathSize + 1, res, returnSize, returnColumnSizes);
}
}
int** combinationSum3(int k, int n, int* returnSize, int** returnColumnSizes) {
*returnSize = 0;
*returnColumnSizes = (int*)malloc(sizeof(int) * MAX_SZIE);
int** res = (int**)malloc(sizeof(int*) * MAX_SZIE);
if (n == 0) {
return res;
}
int* path = (int*)malloc(sizeof(int) * k);
backtracking(k, n, 1, 0, path, 0, res, returnSize, returnColumnSizes);
return res;
}
leetcode 17. 电话号码的字母组合(不同集合)
leetcode 17. 电话号码的字母组合
此题解决以下几个问题:
- 数字和字符进行映射
- 在不同集合中进行组合,单层遍历的逻辑
- 输入1 * # 等异常情况
解决:
- 使用二维数组进行数字和字符的映射
- 在不同集合中,单层逻辑是 i 每次从0开始
- 输入异常时,直接返回(但是leetcode的测试集中没有异常输入,就没有处理)
代码如下:
/**
* Note: The returned array must be malloced, assume caller calls free().
*/
char phoneMap[9][5] = {"", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"}; // 1 不对应任何字母
void backtracking(char* digits, int digitsSize, int index, int pathSize, char* path, char** res, int* returnSize) {
if (index == digitsSize) { /* 终止条件,当前路径字符个数达到要求 */
/* 返回数组分配空间 */
res[*returnSize] = (char*)malloc(sizeof(char) * (pathSize + 1));
/* 将当前这一条路径,copy到返回数组中 */
strcpy(res[*returnSize], path);
/* 返回数组的行数加一 */
(*returnSize)++;
} else { /* 未达到终止条件 */
/* 将输入数组的当前字符所代表的字符串取出、并记录其长度lettersSize */
char* letters = phoneMap[digits[index] - '1'];
int lettersSize = strlen(letters);
/* 在当前层进行选择,挨个进行回溯递归遍历 */
for (int i = 0; i < lettersSize; i++) {
path[pathSize++] = letters[i];
path[pathSize] = '\0';
/* index + 1直接在参数中回溯了
* pathSize在backtracking函数前后进行回溯
*/
backtracking(digits, digitsSize, index + 1, pathSize, path, res, returnSize);
path[--pathSize] = '\0';
}
}
}
char ** letterCombinations(char * digits, int* returnSize) {
/* 特判、输入字符串为空,返回NULL */
int digitsSize = strlen(digits);
*returnSize = 0;
if (digitsSize == 0) {
return NULL;
}
/* 每一位按键最多4个字符,所以返回数组最多4^digitsSize行、每行4个字符 */
int maxResSize = (int)pow(4, digitsSize); /* 4 ^ digitsSize */
char** res = (char**)malloc(sizeof(char*) * maxResSize);
/* 记录当前路径、每条路径digitsSize个字符加一个'\0' */
char* path = (char*)malloc(sizeof(char) * (digitsSize + 1));
backtracking(digits, digitsSize, 0, 0, path, res, returnSize);
return res;
}
leetcode 39. 组合总和(同一集合,元素不重复,元素任意取多次)
leetcode 39. 组合总和
给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的数字可以无限制重复被选取。
说明:
- 所有数字(包括 target)都是正整数。
解集不能包含重复的组合。
示例 1:
输入:candidates = [2,3,6,7], target = 7,
所求解集为:
[
[7],
[2,2,3]
]
此题是在一个集合中组合、集合中的元素可以取任意次。
那么递归终止条件为:sum == target
单层逻辑,i 从 index开始,而且进入下一层递归index不需要 i + 1,传入 i 即可。
代码如下:
/**
* 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 cmp(const void* a, const void* b) {
return *(int*)a - *(int*)b;
}
void backtracking(int* candidates, int candidatesSize, int sum, int target, int index, int* path, int pathSize,
int** res, int* returnSize, int** returnColumnSizes) {
if (sum == target) {
res[*returnSize] = (int*)malloc(sizeof(int) * pathSize);
memcpy(res[*returnSize], path, sizeof(int) * pathSize);
(*returnColumnSizes)[*returnSize] = pathSize;
(*returnSize)++;
} else {
for (int i = index; i < candidatesSize && sum + candidates[i] <= target; i++) {
path[pathSize] = candidates[i];
backtracking(candidates, candidatesSize, sum + candidates[i], target, i,
path, pathSize + 1, res, returnSize, returnColumnSizes);
}
}
}
int** combinationSum(int* candidates, int candidatesSize, int target, int* returnSize, int** returnColumnSizes) {
int** res = (int**)malloc(sizeof(int*) * 10001);
int* path = (int*)malloc(sizeof(int) * 10001);
*returnColumnSizes = (int*)malloc(sizeof(int) * 10001);
*returnSize = 0;
qsort(candidates, candidatesSize, sizeof(int), cmp);
backtracking(candidates, candidatesSize, 0, target, 0, path, 0, res, returnSize, returnColumnSizes);
return res;
}
leetcode 40. 组合总和 II(同一集合,元素重复,每个组合中每个元素只使用一次,组合不重复)
要去重的是“同一树层上的使用过”,如果判断同一树层上元素(相同的元素)是否使用过了呢。
如果candidates[i] == candidates[i - 1] 并且 used[i - 1] == false,就说明:前一个树枝,使用了candidates[i - 1],也就是说同一树层使用过candidates[i - 1]。
此时for循环里就应该做continue的操作。
如图:
我在图中将used的变化用橘黄色标注上,可以看出在candidates[i] == candidates[i - 1]相同的情况下:
- used[i - 1] == true,说明同一树支candidates[i - 1]使用过
- used[i - 1] == false,说明同一树层candidates[i - 1]使用过
在同一树枝下,若candidates[i] == candidates[i - 1]时;used[i - 1] == true 说明 candidates[i] 之前使用过,如 1、2(1)、2(2)、3中组合为:1、2(1)、2(2)、3
在同一数层下,若candidates[i] == candidates[i - 1]时;used[i - 1] == false 说明 candidates[i] 之前使用过,如 1、2(1)、2(2)、3中组合为:1、2(1);1、2(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 cmp(const void* a, const void* b) {
return *(int*)a - *(int*)b;
}
void backtracking(int* candidates, int candidatesSize, int sum, int target, int startIndex, int* path, int pathSize,
int** res, int* returnSize, int** returnColumnSizes, bool* used) {
if (sum == target) {
res[*returnSize] = (int*)malloc(sizeof(int) * pathSize);
memcpy(res[*returnSize], path, sizeof(int) * pathSize);
(*returnColumnSizes)[*returnSize] = pathSize;
(*returnSize)++;
} else {
for (int i = startIndex; i < candidatesSize && sum + candidates[i] <= target; i++) {
if (i > 0 && candidates[i] == candidates[ i - 1] && used[i - 1] == false) {
continue;
}
used[i] = true;
path[pathSize] = candidates[i];
backtracking(candidates, candidatesSize, sum + candidates[i], target, i + 1,
path, pathSize + 1, res, returnSize, returnColumnSizes, used);
used[i] = false;
}
}
}
int** combinationSum2(int* candidates, int candidatesSize, int target, int* returnSize, int** returnColumnSizes) {
int** res = (int**)malloc(sizeof(int*) * 10001);
int* path = (int*)malloc(sizeof(int) * 10001);
bool* used = (bool*)calloc(10001, sizeof(bool));
*returnColumnSizes = (int*)malloc(sizeof(int) * 10001);
*returnSize = 0;
qsort(candidates, candidatesSize, sizeof(int), cmp);
backtracking(candidates, candidatesSize, 0, target, 0, path, 0, res, returnSize, returnColumnSizes,used);
return res;
}
3、切割问题
leetcode 131. 分割回文串(三级指针表示返回值,判断回文串)
- 切割完之后要存储之前个字串,使用二维path数组存储
- 判断回文串,使用双指针左右向中间遍历的方法
- 当startIndex走到sLen时表示切割完成,记录一个结果
- 还有C语言中,返回的是一个三级指针,需要正确对其内存分配
代码如下:
/**
* 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().
*/
bool isPalindrome(char* s, int left, int right) {
for (int i = left, j = right; i < j; i++, j--) {
if (s[i] != s[j]) {
return false;
}
}
return true;
}
void backtracking(char* s, int sLen, char*** res, int* returnSize, int** returnColumnSizes,
char path[][100], int pathSize, int startIndex) {
if (startIndex == sLen) {
res[*returnSize] = (char**)malloc(sizeof(char*) * pathSize);
(*returnColumnSizes)[*returnSize] = pathSize;
for (int i = 0; i < pathSize; i++) {
res[*returnSize][i] = (char*)malloc(sizeof(char) * (strlen(path[i]) + 1));
//strncpy(res[*returnSize][i] , path[i], sizeof(char) * (strlen(path[i]) + 1));
memcpy(res[*returnSize][i], path[i], sizeof(char) * (strlen(path[i]) + 1));
}
(*returnSize)++;
} else {
for (int i = startIndex; i < sLen; i++) {
if (!isPalindrome(s, startIndex, i)) {
continue;
} else {
memcpy(path[pathSize++], s + startIndex, sizeof(char) * (i - startIndex + 1));
//strncpy(path[pathSize++], s + startIndex, i - startIndex + 1);
backtracking(s, sLen, res, returnSize, returnColumnSizes, path, pathSize, i + 1);
memset(path[--pathSize], 0, 100);
}
}
}
}
char*** partition(char* s, int* returnSize, int** returnColumnSizes) {
*returnSize = 0;
*returnColumnSizes = (int*)malloc(sizeof(int) * 100001);
char*** res = (char***)malloc(sizeof(char**) * 100001);
char path[1000][100];
int sLen = strlen(s);
backtracking(s, sLen, res, returnSize, returnColumnSizes, path, 0, 0);
return res;
}
leetcode 93. 复原 IP 地址(131的加强版)
class Solution {
private:
/* 返回数组 */
vector<string> res;
void backtracking(string& s, int startIndex, int pointNum) {
/* 当前点数位3,需要进一步判断第四部分的地址值 */
if (pointNum == 3) {
/* 判断第四部分是否合法 */
if (isValid(s, startIndex, s.size() - 1)) {
/* 将当前路径加入返回数组 */
res.push_back(s);
}
return;
}
/* 遍历当前层 */
for (int i = startIndex; i < s.size(); i++) {
/* 判断当前部分是否合法 */
if (isValid(s, startIndex, i)) {
/* 将分割点插入s中 */
s.insert(s.begin() + i + 1, '.');
/* 开始下一层递归 */
backtracking(s, i + 2, pointNum + 1);
/* 回溯,将当前加入的点删除 */
s.erase(s.begin() + i + 1);
} else {
break;
}
}
}
bool isValid(const string& s, int start, int end) {
/* 越界特判 */
if (start > end) {
return false;
}
if (s[start] == '0' && start != end) { // 0开头的数字不合法
return false;
}
int num = 0;
for (int i = start; i <= end; i++) {
if (s[i] > '9' || s[i] < '0') { // 遇到非数字字符不合法
return false;
}
num = num * 10 + (s[i] - '0');
if (num > 255) { // 如果大于255了不合法
return false;
}
}
return true;
}
public:
vector<string> restoreIpAddresses(string s) {
res.clear();
if (s.size() > 12) {
return res;
}
backtracking(s, 0, 0);
return res;
}
};
4、子集问题
子集问题是取所有节点,其他问题是取叶子节点
如果把 子集问题、组合问题、分割问题都抽象为一棵树的话,那么组合问题和分割问题都是收集树的叶子节点,而子集问题是找树的所有节点,既遍历整棵树!
其实子集也是一种组合问题,因为它的集合是无序的,子集{1,2} 和 子集{2,1}是一样的。
那么既然是无序,取过的元素不会重复取,写回溯算法的时候,for就要从startIndex开始,而不是从0开始!
leetcode 78. 子集(无重复元素)
leetcode 78. 子集
元素不重复,每一个集合中每个元素只能使用一次,和排列很像;
代码如下:
/**
* 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().
*/
void backtracking(int* nums, int numsSize, int** res, int* returnSize, int** returnColumnSizes,
int* path, int pathSize, int startIndex) {
res[*returnSize] = (int*)malloc(sizeof(int) * pathSize);
memcpy(res[*returnSize], path, sizeof(int) * pathSize);
(*returnColumnSizes)[*returnSize] = pathSize;
(*returnSize)++;
for (int i = startIndex; i < numsSize; i++) {
path[pathSize] = nums[i];
backtracking(nums, numsSize, res, returnSize, returnColumnSizes, path, pathSize + 1, i + 1);
}
}
int** subsets(int* nums, int numsSize, int* returnSize, int** returnColumnSizes) {
*returnSize = 0;
*returnColumnSizes = (int*)malloc(sizeof(int) * 10001);
int** res = (int**)malloc(sizeof(int*) * 10001);
int* path = (int*)malloc(sizeof(int) * numsSize);
backtracking(nums, numsSize, res, returnSize, returnColumnSizes, path, 0, 0);
return res;
}
leetcode 90. 子集 II(有重复元素)
leetcode 90. 子集 II
代码如下:
/**
* 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 cmp(const void* a, const void* b) {
return *(int*)a - *(int*)b;
}
void backtracking(int* nums, int numsSize, int** res, int* returnSize, int** returnColumnSizes,
int* path, int pathSize, int startIndex, bool* used) {
res[*returnSize] = (int*)malloc(sizeof(int) * pathSize);
memcpy(res[*returnSize], path, sizeof(int) * pathSize);
(*returnColumnSizes)[*returnSize] = pathSize;
(*returnSize)++;
for (int i = startIndex; i < numsSize; i++) {
if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) {
continue;
}
path[pathSize] = nums[i];
used[i] = true;
backtracking(nums, numsSize, res, returnSize, returnColumnSizes, path, pathSize + 1, i + 1, used);
used[i] = false;
}
}
int** subsetsWithDup(int* nums, int numsSize, int* returnSize, int** returnColumnSizes) {
*returnSize = 0;
*returnColumnSizes = (int*)malloc(sizeof(int) * 10001);
int** res = (int**)malloc(sizeof(int*) * 10001);
int* path = (int*)malloc(sizeof(int) * numsSize);
bool* used = (bool*)malloc(sizeof(bool) * numsSize);
qsort(nums, numsSize, sizeof(int), cmp);
backtracking(nums, numsSize, res, returnSize, returnColumnSizes, path, 0, 0, used);
return res;
}
leetcode 491. 递增子序列(元素重复、无序)
leetcode 491. 递增子序列
C语言代码如下:
因为此题输入元素在-100到100之间,使用加100这个操作,使得used中的下表落在0到200之间,这样就可以记录当前层元素的使用情况。
/**
* 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().
*/
void backtracking(int* nums, int numsSize, int** res, int* returnSize, int** returnColumnSizes,
int* path, int pathSize, int startIndex) {
/* 终止条件,元素个数大于等于2个 */
if (pathSize >= 2) {
/* 为当前返回值分配空间 */
res[*returnSize] = (int*)malloc(sizeof(int) * pathSize);
/* 赋值 */
memcpy(res[*returnSize], path, sizeof(int) * pathSize);
/* 当前返回值的列数 */
(*returnColumnSizes)[*returnSize] = pathSize;
/* 返回行数加一 */
(*returnSize)++;
}
/* 当前层使用过的元素,每一层都有会有一个这样的used数组 */
int* used = (int*)calloc(201, sizeof(int));
for (int i = startIndex; i < numsSize; i++) {
/* 当前路径中的当前元素小于上一个元素、或者当前中当前元素之前以及使用过 */
if ((pathSize > 0 && nums[i] < path[pathSize - 1]) || used[nums[i] + 100] == 1) {
continue;
}
/* 记录当路径数组中 */
path[pathSize] = nums[i];
/* 因为此题输入元素在-100到100之间,使用加100这个操作,
* 使得used中的下表落在0到200之间,这样就可以记录当前层元素的使用情况
*/
used[nums[i] + 100] = 1;
backtracking(nums, numsSize, res, returnSize, returnColumnSizes, path, pathSize + 1, i + 1);
}
free(used);
used = NULL;
}
int** findSubsequences(int* nums, int numsSize, int* returnSize, int** returnColumnSizes) {
/* 返回数组行数 */
*returnSize = 0;
/* 特判,递增子序列最少需要两个元素 */
if (numsSize < 2) {
return NULL;
}
/* 返回数组中每一行的列数 */
*returnColumnSizes = (int*)malloc(sizeof(int) * 100001);
/* 返回数组 */
int** res = (int**)malloc(sizeof(int*) * 100001);
/* 当前路径数组 */
int* path = (int*)malloc(sizeof(int) * numsSize);
backtracking(nums, numsSize, res, returnSize, returnColumnSizes, path, 0, 0);
free(path);
path = NULL;
return res;
}
C++代码如下:参考 代码随想录 的讲解
使用set定义used数组能够处理更多数据。
// 版本一
class Solution {
private:
vector<vector<int>> result;
vector<int> path;
void backtracking(vector<int>& nums, int startIndex) {
if (path.size() > 1) {
result.push_back(path);
// 注意这里不要加return,要取树上的节点
}
unordered_set<int> uset; // 使用set对本层元素进行去重
for (int i = startIndex; i < nums.size(); i++) {
if ((!path.empty() && nums[i] < path.back())
|| uset.find(nums[i]) != uset.end()) {
continue;
}
uset.insert(nums[i]); // 记录这个元素在本层用过了,本层后面不能再用了
path.push_back(nums[i]);
backtracking(nums, i + 1);
path.pop_back();
}
}
public:
vector<vector<int>> findSubsequences(vector<int>& nums) {
result.clear();
path.clear();
backtracking(nums, 0);
return result;
}
};
5、排列问题
leetcode 46. 全排列(无重复元素)
- 全排列单层搜素逻辑是 i 从0开始,但是也需要使用used数组进行去重;
- 排列中1、2和2、1是两个集合,但是每一层搜索时前面的元素使用过了,就不能在使用了;
- 比如1,2:排列为:1、2和2、1,但是不去重的话,就会是
1、1;
1、2;
2、1;
2、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().
*/
void backtracking(int* nums, int numsSize, int** res, int* returnSize,
int** returnColumnSizes, int* path, int pathSize, int* used) {
if (pathSize == numsSize) {
res[*returnSize] = (int*)malloc(sizeof(int) * pathSize);
memcpy(res[*returnSize], path, sizeof(int) * pathSize);
(*returnColumnSizes)[*returnSize] = numsSize;
(*returnSize)++;
return;
}
for (int i = 0; i < numsSize; i++) {
if (used[i] == 1) {
continue;
}
path[pathSize] = nums[i];
used[i] = 1;
backtracking(nums, numsSize, res, returnSize, returnColumnSizes, path, pathSize + 1, used);
used[i] = 0;
}
}
int** permute(int* nums, int numsSize, int* returnSize, int** returnColumnSizes) {
*returnSize = 0;
*returnColumnSizes = (int*)malloc(sizeof(int) * 100001);
int**res = (int**)malloc(sizeof(int*) * 100001);
int* path = (int*)malloc(sizeof(int) * numsSize);
int* used = (int*)malloc(sizeof(int) * numsSize);
backtracking(nums, numsSize, res, returnSize, returnColumnSizes, path, 0, used);
return res;
}
leetcode 47. 全排列 II(有重复元素)
去重代码:
if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) {
continue;
}
如果改成 used[i - 1] == true, 也是正确的!,去重代码如下:
if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == true) {
continue;
}
如果要对树层中前一位去重,就用used[i - 1] == false,如果要对树枝前一位去重用used[i - 1] == true。
对于排列问题,树层上去重和树枝上去重,都是可以的,但是树层上去重效率更高!
输入: [1,1,1] 来举一个例子。
树层上去重(used[i - 1] == false),的树形结构如下:
树枝上去重(used[i - 1] == true)的树型结构如下:
树层上对前一位去重非常彻底,效率很高,树枝上对前一位去重最后可以得到答案,但做了很多无用搜索
/**
* 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 cmp(const void* a, const void* b) {
return *(int*)a - *(int*)b;
}
void backtracking(int* nums, int numsSize, int** res, int* returnSize,
int** returnColumnSizes, int* path, int pathSize, int* used) {
if (pathSize == numsSize) {
res[*returnSize] = (int*)malloc(sizeof(int) * pathSize);
memcpy(res[*returnSize], path, sizeof(int) * pathSize);
(*returnColumnSizes)[*returnSize] = numsSize;
(*returnSize)++;
return;
}
for (int i = 0; i < numsSize; i++) {
if (i > 0 && nums[i - 1] == nums[i] && used[i - 1] == 0) {
continue;
}
if (used[i] == 0) {
path[pathSize] = nums[i];
used[i] = 1;
backtracking(nums, numsSize, res, returnSize, returnColumnSizes, path, pathSize + 1, used);
used[i] = 0;
}
}
}
int** permuteUnique(int* nums, int numsSize, int* returnSize, int** returnColumnSizes) {
*returnSize = 0;
*returnColumnSizes = (int*)malloc(sizeof(int) * 100001);
int**res = (int**)malloc(sizeof(int*) * 100001);
int* path = (int*)malloc(sizeof(int) * numsSize);
int* used = (int*)calloc(numsSize, sizeof(int));
qsort(nums, numsSize, sizeof(int), cmp);
backtracking(nums, numsSize, res, returnSize, returnColumnSizes, path, 0, used);
return res;
}
leetcode 332. 重新安排行程
leetcode 332. 重新安排行程
参考 代码随想录 的讲解
这道题目有几个难点:
- 一个行程中,如果航班处理不好容易变成一个圈,成为死循环
- 有多种解法,字母序靠前排在前面,让很多同学望而退步,如何该记录映射关系呢 ?
- 使用回溯法(也可以说深搜) 的话,那么终止条件是什么呢?
- 搜索的过程中,如何遍历一个机场所对应的所有机场。
代码如下:
/**
* Note: The returned array must be malloced, assume caller calls free().
*/
char** res;
bool* used;
int g_isFound;
int cmp(const void* str1, const void* str2) {
const char** tmp1 = *(char**)str1;
const char** tmp2 = *(char**)str2;
int res = strcmp(tmp1[0], tmp2[0]);
if (res == 0) {
return strcmp(tmp1[1], tmp2[1]);
}
return res;
}
void backtracking(char*** tickets, int ticketsSize, char* start, char** res, int* returnSize, bool* used) {
if (*returnSize == ticketsSize + 1) {
g_isFound = 1;
return;
}
for (int i = 0; i < ticketsSize; i++) {
/* 这里的used还是在树枝上去重 */
if (used[i] == false && (strcmp(start, tickets[i][0]) == 0)) {
res[*returnSize] = (char*)malloc(sizeof(char) * 4);
memcpy(res[*returnSize], tickets[i][1], sizeof(char) * 4);
(*returnSize)++;
used[i] = true;
backtracking(tickets, ticketsSize, tickets[i][1], res, returnSize, used);
if (g_isFound) {
return;
}
used[i] = false;
//(*returnSize)--; //返回数组长度不需要回溯,因为肯定可以找到一个结果,并且找到后就返回了
}
}
}
char** findItinerary(char*** tickets, int ticketsSize, int* ticketsColSize, int* returnSize) {
*returnSize = 0;
if (!tickets || ticketsSize == 0) {
return NULL;
}
used = (bool*)malloc(sizeof(bool) * ticketsSize);
memset(used, false, sizeof(bool) * ticketsSize);
res = (char**)malloc(sizeof(char*) * (ticketsSize + 1)); /* 返回数组中机场的数量就是机票数量加一 */
res[(*returnSize)++] = (char*)malloc(sizeof(char) * 4);
memcpy(res[0], "JFK", sizeof(char) * 4);
g_isFound = 0;
qsort(tickets, ticketsSize, sizeof(tickets[0]), cmp); /* 有相同的出发机场,按照字符大小升序排序 */
backtracking(tickets, ticketsSize, "JFK", res, returnSize, used);
return res;
}
6、棋盘问题
leetcode 第51题. N皇后
leetcode 第51题. N皇后
具体看代码:
/**
* 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().
*/
bool isValid(int n, int* path, int row, int col) {
for (int i = 0; i < row; i++) { /* 同一列 */
if (path[i] == col) {
return false;
}
}
for (int i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) { /* 左上角 */
if (path[i] == j) {
return false;
}
}
for (int i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++) { /* 右上角 */
if (path[i] == j) {
return false;
}
}
return true;
}
void backtracking(int n, int row, char*** res, int* returnSize, int** returnColumnSizes, int* path) {
if (row == n) {
res[*returnSize] = (char**)malloc(sizeof(char*) * n);
for (int i = 0; i < n; i++) {
res[*returnSize][i] = (char*)calloc(n + 1, sizeof(char));
memset(res[*returnSize][i], '.', n);
res[*returnSize][i][path[i]] = 'Q';
}
(*returnColumnSizes)[*returnSize] = n;
(*returnSize)++;
return;
}
for (int j = 0; j < n; j++) {
if (!isValid(n, path, row, j)) {
continue;
}
path[row] = j;
backtracking(n, row + 1, res, returnSize, returnColumnSizes, path);
}
}
char*** solveNQueens(int n, int* returnSize, int** returnColumnSizes) {
*returnSize = 0;
*returnColumnSizes = (int*)malloc(sizeof(int) * n * n * n);
char*** res = (char***)malloc(sizeof(char**) * n * n * n);
int* path = (int*)calloc(n, sizeof(int));
backtracking(n, 0, res, returnSize, returnColumnSizes, path);
return res;
}
leetcode 37. 解数独
leetcode 37. 解数独
此题单层逻辑更像排列,每一次都是从头开始遍历,具体看代码:
bool isValid(char** board, int row, int col, int k) {
/* 判断当前行是否有重复元素 */
for (int i = 0; i < 9; i++) {
if (board[i][col] == k) {
return false;
}
}
/* 判断当前列是否有重复元素 */
for (int j = 0; j < 9; j++) {
if (board[row][j] == k) {
return false;
}
}
/* 计算当前9宫格左上角的位置 */
int startRow = (row / 3) * 3;
int startCol = (col / 3) * 3;
/* 判断当前元素所在九宫格是否有重复元素 */
for (int i = startRow; i < startRow + 3; i++) {
for (int j = startCol; j < startCol + 3; j++) {
if (board[i][j] == k) {
return false;
}
}
}
/* 满足条件,返回true */
return true;
}
bool backtracking(char** board, int boardSize, int* boardColSize) {
/* 从上到下、从左到右依次遍历输入数组 */
for (int i = 0; i < boardSize; i++) {
for (int j = 0; j < *boardColSize; j++) {
/* 遇到数字跳过 */
if (board[i][j] != '.') {
continue;
}
/* 依次将数组1到9填入当前位置 */
for (int k = '1'; k <= '9'; k++) {
/* 判断当前位置是否与满足条件,是则进入下一层 */
if (isValid(board, i, j, k)) {
board[i][j] = k;
/* 判断下一层递归之后是否找到一种解法,是则返回true */
if (backtracking(board, boardSize, boardColSize)) {
return true;
}
/* 回溯,将当前位置清零 */
board[i][j] = '.';
}
}
/* 若填入的9个数均不满足条件,返回false,说明此解法无效 */
return false;
}
}
/* 遍历完所有的棋盘,没有返回false,说明找到了解法,返回true */
return true;
}
void solveSudoku(char** board, int boardSize, int* boardColSize) {
bool res = backtracking(board, boardSize, boardColSize);
}