代码随想录刷题笔记8——回溯算法

回溯算法理论基础

回溯法的定义

回溯法也可以叫做回溯搜索法,它是一种搜索的方式。

回溯是递归的副产品,只要有递归就会有回溯。

回溯法的效率

因为回溯的本质是穷举,穷举所有可能,然后选出我们想要的答案,如果想让回溯法高效一些,可以加一些剪枝的操作,但也改不了回溯法就是穷举的本质。所以说,回溯法不是高效的算法,实质就是暴力枚举所有情况。

所以说,回溯法主要用来解决只能暴力穷举的题目,比如题目要求列出所有符合要求的解,那就只能回溯来做(我个人的理解就是**,for循环都写不清楚几层了,肯定是回溯**)。

回溯法解决的问题

回溯法,一般可以解决如下几种问题:

  • 组合问题:N个数里面按一定规则找出k个数的集合
  • 切割问题:一个字符串按一定规则有几种切割方式
  • 子集问题:一个N个数的集合里有多少符合条件的子集
  • 排列问题:N个数按一定规则全排列,有几种排列方式
  • 棋盘问题:N皇后,解数独等等

这其中,组合和排列比较搞,先强调一下这其中的区别:

组合并不强调元素顺序,而排列是强调元素顺序的!也就是说,组合无序,排列有序!

理解回溯法

回溯法解决的问题都可以抽象为树形结构,是的,是所有回溯法的问题都可以抽象为树形结构!

因为回溯法解决的都是在集合中递归查找子集,集合的大小就构成了树的宽度,递归的深度,都构成的树的深度

递归就要有终止条件,所以必然是一棵高度有限的树(N叉树)。

回溯法模板

这里就是代码随想录所总结的模板,基本可以套用在所有的回溯法求解的问题当中!

  • 回溯函数模板返回值及参数

回溯算法中返回值一般为void。

参数的话,因为回溯算法需要的参数可不像二叉树递归的时候那么容易一次性确定下来,所以一般是先写逻辑,然后需要什么参数,就填什么参数

  • 回溯函数终止条件

一般来说搜到叶子节点了,也就找到了满足条件的一条答案,把这个答案存放起来,并结束本层递归

  • 回溯搜索的遍历过程

回溯法一般是在集合中递归搜索,集合的大小构成了树的宽度,递归的深度构成的树的深度。我白嫖了代码随想录的演示图,这个图在之后所有的回溯算法题目中,都可以借鉴:

回溯法遍历过程示意图

for循环就是遍历集合区间,可以理解一个节点有多少个孩子,这个for循环就执行多少次

backtracking这里自己调用自己,实现递归。

大家可以从图中看出for循环可以理解是横向遍历,backtracking(递归)就是纵向遍历,这样就把这棵树全遍历完了,一般来说,搜索叶子节点就是找的其中一个结果了。

回溯算法模板框架

void backtracking(参数) {
    if (终止条件) {
        存放结果;
        return;
    }

    for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
        处理节点;
        backtracking(路径,选择列表); // 递归
        回溯,撤销处理结果
    }
}

组合问题

例题77(中等)组合

注意要点:

  1. 递归来做层叠嵌套(可以理解是开k层for循环),每一次的递归中嵌套一个for循环,那么递归就可以用于解决多层嵌套循环的问题了
  2. 在组合问题中,需要int型变量startIndex,这个参数用来记录本层递归的中,集合从哪里开始遍历(集合就是[1,…,n] ),通过这个参数来防止出现重复的组合
  3. 终止条件就是path中已经满了,那么返回到结果中结束这一次的遍历就可以了;
  4. 最后还可以剪枝:只要后面剩的数不足够了就可以不用考虑,再注意代码的区间是左闭即可。

下面贴出代码:

CPP版本

class Solution {
private:
    vector<vector<int>> result;
    vector<int> path;
    void backtracking(int n, int k, int startIndex)
    {
        if (path.size() == k)
        {
            result.push_back(path);
            return;
        }

        for (int i = startIndex; i <= n - (k - path.size()) + 1; i++)
        {
            path.push_back(i);
            backtracking(n, k, i + 1);
            path.pop_back();
        }
    }
public:
    vector<vector<int>> combine(int n, int k) {
        result.clear();
        path.clear();
        backtracking(n, k, 1);
        return result;
    }
};

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().
 */

int* path;
int pathTop; //存储回溯结果
int** ans;
int ansTop;

void backtracking(int n, int k, int startIndex)
{
    //path达到了k维,就要把他放进ans
    if (pathTop == k)
    {
        //path数组是申请的,直接放进ans相当于把path首地址存进去,值并未保留
        int* tmp = (int* )malloc(sizeof(int) * k);
        for (int i = 0; i < k; i++) {tmp[i] = path[i];}
        ans[ansTop++] = tmp;
        return;
    }

    for (int j = startIndex; j <= n - (k - pathTop) + 1; j++)
    {
        //当前节点放入path
        path[pathTop++] = j;
        //递归在for里面,回溯的经典模板
        backtracking(n, k, j + 1);
        //回溯
        pathTop--;
    }
}

int** combine(int n, int k, int* returnSize, int** returnColumnSizes){
    //malloc那些已经申请的数组和int值
    path = (int* )malloc(sizeof(int) * k);
    pathTop = 0;
    ans = (int** )malloc(sizeof(int*) * 200001);//实际Cnk即可,也就是排列组合的个数
    ansTop = 0;

    //回溯
    backtracking(n, k, 1);
    //返回值的定义
    *returnSize = ansTop;
    *returnColumnSizes = (int* )malloc(sizeof(int) * (*returnSize));
    for (int i = 0; i < (*returnSize); i++) {(*returnColumnSizes)[i] = k;}
    return ans;
}

从回溯这边,更能深刻体会到,C++造完了轮子对我们写核心代码的难度降低了多少!

组合总和

例题216(中等)组合总和III

注意要点:

  1. 对于个数的剪枝与上一题相同;
  2. 需要一个sum参数记录当前和,只要和超过了目标值就可以直接return完成剪枝

下面贴出代码:

CPP版本

class Solution {
private:
    vector<vector<int>> result;
    vector<int> path;
    void backtracking(int k, int n, int startIndex, int sum)
    {
        if (sum > n) {return;}
        if (path.size() == k)
        {
            if (sum == n)
            {
                result.push_back(path);
                return;
            }
        }

        for (int i = startIndex; i <= 9 - (k - path.size()) + 1; i++)
        {
            sum += i;
            path.push_back(i);
            backtracking(k, n, i + 1, sum);
            sum -= i;
            path.pop_back();
        }
    }

public:
    vector<vector<int>> combinationSum3(int k, int n) {
        result.clear();
        path.clear();
        backtracking(k, n, 1, 0);
        return result;
    }
};

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().
 */

int* path;
int pathTop;
int** ans;
int ansTop;

void backtracking(int sum, int k, int n, int startIndex)
{
    if (sum > n) {return;}
    if (pathTop == k)
    {
        if (sum == n)
        {
            int* tmp = (int* )malloc(sizeof(int) * k);
            for (int i = 0; i < pathTop; i++) {tmp[i] = path[i];}
            ans[ansTop++] = tmp;
        }
        return;
    }
    for (int j = startIndex; j <= 9 - (k - pathTop) + 1; j++)
    {
        sum += j;
        path[pathTop++] = j;
        backtracking(sum, k, n, j + 1);
        sum -= j;
        pathTop--;
    }
}

int** combinationSum3(int k, int n, int* returnSize, int** returnColumnSizes){
    ans = (int** )malloc(sizeof(int* ) * 101);
    path = (int* )malloc(sizeof(int) * k);
    ansTop = pathTop = 0;

    backtracking(0, k, n, 1);

    *returnSize = ansTop;
    *returnColumnSizes = (int* )malloc(sizeof(int) * (*returnSize));
    for (int i = 0; i < (*returnSize); i++) {(*returnColumnSizes)[i] = k;}
    return ans;
}

电话号码的字母组合

例题17(中等)电话号码的字母组合

注意要点:

  1. 这一题,回溯的是每一次取出的字母表中的字母
  2. 通过index来实现数字的移动,即查取下一个字母表。

下面贴出代码:

CPP版本

class Solution {
private:
    const string letterMap[10] = {
        "", // 0
        "", // 1
        "abc", // 2
        "def", // 3
        "ghi", // 4
        "jkl", // 5
        "mno", // 6
        "pqrs", // 7
        "tuv", // 8
        "wxyz", // 9
    };
    vector<string> result;
    string path;
    void backtracking(const string& digits, int index)
    {
        if (path.size() == digits.size())
        {
            result.push_back(path);
            return;
        }

        int digit = digits[index] - '0';
        string letters = letterMap[digit];
        for (int i = 0; i < letters.size(); i++)
        {
            path += letters[i];
            backtracking(digits, index + 1);
            path.pop_back();
        }
    }

public:
    vector<string> letterCombinations(string digits) {
        result.clear();
        path.clear();
        if (!digits.size()) {return result;}
        backtracking(digits, 0);
        return result;
    }
};

C版本

/**
 * Note: The returned array must be malloced, assume caller calls free().
 */

char* path;
int pathTop;
char** ans;
int ansTop;

//首先需要自定义一个对应的哈希表
char phoneMap[10][5] = {
    "\0",
    "\0",
    "abc\0", 
    "def\0", 
    "ghi\0", 
    "jkl\0", 
    "mno\0", 
    "pqrs\0", 
    "tuv\0", 
    "wxyz\0"
};

void backtracking(char* digits, int index)
{
    int len = strlen(digits);
    if (index == len)
    {
        char* tmp = (char* )malloc(sizeof(char) * (len + 1));
        for (int i = 0; i < index; i++) {tmp[i] = path[i];}
        tmp[index] = '\0';
        ans[ansTop++] = tmp;
        return;
    }
    //对应的哈希表转换
    int digit = digits[index] - '0';
    char* letters = phoneMap[digit];
    for (int i = 0; i < strlen(letters); i++)
    {
        path[pathTop++] = letters[i];
        backtracking(digits, index + 1);
        pathTop--;
    }
}

char ** letterCombinations(char * digits, int* returnSize){
    path = (int* )malloc(sizeof(int) * strlen(digits));
    ans = (char** )malloc(sizeof(char* ) * 301);
    pathTop = ansTop = 0;

    *returnSize = 0;
    if (!strlen(digits)) {return ans;}
    backtracking(digits, 0);
    *returnSize = ansTop;
    return ans;
}

组合总和

例题39(中等)组合总和

注意要点:

  1. 组合问题,所以其中的元素可以重复,但是为了答案不重复,所以每一次的startIndex都要从当前循环的i开始
  2. 其他的都是回溯模板;
  3. 剪枝,先升序排列,就是如果当前的candidate+sum超过了target,那么就不需要去遍历了,直接跳过这个循环。

下面贴出代码:

CPP版本

class Solution {
private:
    vector<vector<int>> result;
    vector<int> path;
    void backtracking(vector<int>& candidates, int target, int sum, int startIndex)
    {
        if (sum > target) {return;}
        if (sum == target)
        {
            result.push_back(path);
            return;
        }

        for (int i = startIndex; i < candidates.size() && candidates[i] + sum <= target; i++)
        {
            sum += candidates[i];
            path.push_back(candidates[i]);
            backtracking(candidates, target, sum, i);
            sum -= candidates[i];
            path.pop_back();
        }
    }
public:
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        result.clear();
        path.clear();
        sort(candidates.begin(), candidates.end());
        backtracking(candidates, target, 0, 0);
        return result;
    }
};

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().
 */

int** ans;
int ansTop;
int* path;
int pathTop;
int* pathSize; //记录每一行数组的长度,几个数字构成

int cmp(const void* a, const void* b)
{
    return *(int* )a - *(int* )b;
}

void backtracking(int* candidates, int candidatesSize, int target, int sum, int startIndex)
{
    if (sum > target) {return;}
    if (sum == target)
    {
        int* tmp = (int* )malloc(sizeof(int) * pathTop);
        for (int j = 0; j < pathTop; j++) {tmp[j] = path[j];}
        ans[ansTop] = tmp;
        pathSize[ansTop++] = pathTop;
        return;
    }
    //这里剪枝可以参考一下,主要是for循环第二个地方不只是可以平常那样写
    for (int i = startIndex; i < candidatesSize && sum + candidates[i] <= target; i++)
    {
        sum += candidates[i];
        path[pathTop++] = candidates[i];
        backtracking(candidates, candidatesSize, target, sum, i);
        sum -= candidates[i];
        pathTop--;
    }
    return;
}

int** combinationSum(int* candidates, int candidatesSize, int target, int* returnSize, int** returnColumnSizes){
    ans = (int** )malloc(sizeof(int* ) * 200);
    path = (int* )malloc(sizeof(int) * 50);
    pathSize = (int* )malloc(sizeof(int) * 200);
    ansTop = pathTop = 0;
    qsort(candidates, candidatesSize, sizeof(int), cmp);
    
    backtracking(candidates, candidatesSize, target, 0, 0);
    *returnSize = ansTop;
    *returnColumnSizes = (int* )malloc(sizeof(int) * (*returnSize));
    for (int i = 0; i < (*returnSize); i++) {(*returnColumnSizes)[i] = pathSize[i];}
    return ans;
}

例题40(中等)组合总和II

注意要点:

  1. 与上一题类似,剪枝方法也相同(先按照升序排序)
  2. 为了没有重复的元素,需要先升序排序(把相等元素连在一起),然后进入循环判断,如果当前元素与上一个元素相等,直接跳过即可

下面贴出代码:

CPP版本

class Solution {
private:
    vector<vector<int>> result;
    vector<int> path;
    void backtracking(vector<int>& candidates, int target, int startIndex, int sum)
    {
        if (sum > target) {return;}
        if (sum == target)
        {
            result.push_back(path);
            return;
        }

        for (int i = startIndex; i < candidates.size() && candidates[i] + sum <= target; i++)
        {
            if (i > startIndex && candidates[i] == candidates[i - 1]) {continue;}
            sum += candidates[i];
            path.push_back(candidates[i]);
            backtracking(candidates, target, i + 1, sum);
            path.pop_back();
            sum -= candidates[i];
        }
    }

public:
    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        result.clear();
        path.clear();
        sort(candidates.begin(), candidates.end());
        backtracking(candidates, target, 0, 0);
        return result;
    }
};

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().
 */

int** ans;
int ansTop;
int* path;
int pathTop;
int* pathSize;

int cmp(const void* a, const void* b)  //从小到大qsort
{
    return *(int* )a - *(int* )b;
}

//这里需要先排序才可以除去重复
void backtracking(int* candidates, int candidatesSize, int target, int sum, int startIndex)
{
    if (sum == target)
    {
        int* tmp = (int* )malloc(sizeof(int) * pathTop);
        for (int i = 0; i < pathTop; i++) {tmp[i] = path[i];}
        pathSize[ansTop] = pathTop;
        ans[ansTop++] = tmp;
        return;
    }

    for (int i = startIndex; i < candidatesSize && candidates[i] + sum <= target; i++)
    {
        if (i > startIndex && candidates[i] == candidates[i - 1]) {continue;}
        sum += candidates[i];
        path[pathTop++] = 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){
    ans = (int** )malloc(sizeof(int* ) * 101);
    path = (int* )malloc(sizeof(int) * 101);
    ansTop = pathTop = 0;
    pathSize = (int* )malloc(sizeof(int) * 101);
    qsort(candidates, candidatesSize, sizeof(int), cmp);
    backtracking(candidates, candidatesSize, target, 0, 0);
    *returnSize = ansTop;
    *returnColumnSizes = (int* )malloc(sizeof(int* ) * ansTop);
    for (int i = 0; i < ansTop; i++) {(*returnColumnSizes)[i] = pathSize[i];}
    return ans;
}

分割回文串

例题131(中等)分割回文串

注意要点:

  1. C++中,可以通过s.substr(begin, length)来截取字符串,从begin下标截取长度为length的字符串;C只能自己操作来生成一个子字符串;
  2. 判断回文,经典双指针法
  3. 是回文,就压入path进入回溯,否则世界跳过。

为了易于理解,我白嫖了代码随想录的思维图:

分割回文的回溯示意图

下面贴出代码:

CPP版本

class Solution {
private:
    vector<vector<string>> result;
    vector<string> path;
    bool isPalindrome(const string& s, int left, int right)
    {
        while (left < right)
        {
            if (s[left] != s[right]) {return 0;}
            left++;
            right--;
        }
        return 1;
    }

    void backtracking(const string& s, int startIndex)
    {
        if (startIndex == s.size())
        {
            result.push_back(path);
            return;
        }

        for (int i = startIndex; i < s.size(); i++)
        {
            if (isPalindrome(s, startIndex, i))
            {
                string str = s.substr(startIndex, i - startIndex + 1);
                path.push_back(str);
            }
            else continue;
            backtracking(s, i + 1);
            path.pop_back();
        }
    }

public:
    vector<vector<string>> partition(string s) {
        result.clear();
        path.clear();
        backtracking(s, 0);
        return result;
    }
};

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().
 */

char*** ans;
int ansTop;
char** path;
int pathTop;
int* pathSize;

//把是回文串要写入的操作写成函数
void copy()
{
    char** tmp = (char** )malloc(sizeof(char* ) * pathTop);
    for (int i = 0; i < pathTop; i++) {tmp[i] = path[i];}
    pathSize[ansTop] = pathTop;
    ans[ansTop++] = tmp;
}
//双指针判断回文
bool isPalindrome(char* str, int left, int right)
{
    while (left < right)
    {
        if (str[left++] != str[right--]) {return false;}
    }
    return true;
}
//切割从startIndex到endIndex子字符串
char* cut(char* str, int left, int right)
{
    int index = 0;
    char* tmp = (char* )malloc(sizeof(char) * (right - left + 1 + 1)); //需要终止符的空间
    for (int i = left; i <= right; i++) {tmp[index++] = str[i];}
    tmp[index] = '\0';
    return tmp;
}

void backtracking(char* s, int startIndex)
{
    if (startIndex >= strlen(s)) {copy();return;}

    for (int i = startIndex; i < strlen(s); i++)
    {
        if (isPalindrome(s, startIndex, i)) {path[pathTop++] = cut(s, startIndex, i);}
        else continue;
        backtracking(s, i + 1);
        pathTop--;
    }
}

char *** partition(char * s, int* returnSize, int** returnColumnSizes){
    path = (char** )malloc(sizeof(char* ) * strlen(s));
    ans = (char*** )malloc(sizeof(char** ) * 40000);
    pathSize = (int* )malloc(sizeof(int) * 40000);
    ansTop = pathTop = 0;
    backtracking(s, 0);
    *returnSize = ansTop;
    *returnColumnSizes = (int* )malloc(sizeof(int) * ansTop);
    for (int i = 0; i < ansTop; i++){(*returnColumnSizes)[i] = pathSize[i];}
    return ans;
}

复原IP地址

例题93(中等)复原IP地址

注意要点:

  1. 在回溯函数中,需要一个参数来统计切分次数(也就是加了几个切分符号’.')
  2. 可以通过s.insert和s.erase来增加和清除字符,需要的参数是位置(insert还需要添加的字符);
  3. 判断是否满足IP,按照题设写出判断函数即可;
  4. 回溯达成的条件是:切分数量成立且最后一段符合要求

这一题我感觉是回溯里面算很难的题目了,为了理解,我白嫖了代码随想录的图:

字符串分割的回溯示意图

下面贴出代码:

CPP版本

class Solution {
private:
    vector<string> result;
    bool isValid(const string& s, int start, int end)
    {
        if (start > end) {return 0;}  //下标有问题
        if (end - start > 2) {return 0;}  //超过三位数
        if (s[start] == '0' && start != end) {return 0;}  //首位为0

        int num = 0;
        for (int i = start; i <= end; i++)
        {
            int now = s[i] - '0';
            num = num * 10 + now;
            if (num > 255) {return 0;}
        }
        return 1;
    }
    void backtracking(string& s, int startIndex, int pointNum)
    {
        //已经切分三次,有了3个.
        if (pointNum == 3)
        {
            if (isValid(s, startIndex, s.size() - 1)) {result.push_back(s);}
            return;
        }

        for (int i = startIndex; i < s.size(); i++)
        {
            if (isValid(s, startIndex, i))
            {
                s.insert(s.begin() + i + 1, '.');
                pointNum++;
                backtracking(s, i + 2, pointNum);
                s.erase(s.begin() + i + 1);
                pointNum--;
            }
            else break;
        }
    }

public:
    vector<string> restoreIpAddresses(string s) {
        result.clear();
        if (s.size() < 4 || s.size() > 12) {return result;}
        backtracking(s, 0, 0);
        return result;
    }
};

C版本

/**
 * Note: The returned array must be malloced, assume caller calls free().
 */

char** ans;
int anstop;
//需要切割并使用'.'分割
int segment[3];

bool isValid(char* s, int start, int end)
{
    if (start > end) {return false;}
    //首位即为0
    if (s[start] == '0' && start != end) {return false;}
    int num = 0;
    for (int i = start; i <= end; i++)
    {
        if (s[i] > '9' || s[i] < '0') {return false;}
        num = 10 * num + s[i] - '0'; //获取后下一次即移位*10
        if (num > 255) {return false;}
    }
    return true;
}
//与回文串最大的不同就是还有一个分隔符'.',使用pointNum来记录其位置
void backtracking(char* s, int startIndex, int pointNum)
{
    //有了3个'.'就结束
    if (pointNum == 3)
    {
        //只要最后一个字符符合,就可以存储
        if (isValid(s, startIndex, strlen(s) - 1))
        {
            char* tmp = (char* )malloc(sizeof(char) * (strlen(s) + 4));
            //记录tmp下标
            int count = 0;
            //记录'.'下标
            int count1 = 0;
            for (int j = 0; j < strlen(s); j++)
            {
                tmp[count++] = s[j];
                //如果遇到'.'下标,且下标还未使用3个,就添加进去
                if (count1 < 3 && j == segment[count1])
                {
                    tmp[count++] = '.';
                    count1++;
                }
            }
            tmp[count] = 0;
            //把tmp放入ans中
            ans = (char** )realloc(ans, sizeof(char* ) * (anstop + 1));
            ans[anstop++] = tmp;
        }
        return;
    }

    for (int i = startIndex; i < strlen(s); i++)
    {
        if (isValid(s, startIndex, i))
        {
            segment[pointNum] = i;
            backtracking(s, i + 1, pointNum + 1);
        }
        else {break;}
    }
}

char ** restoreIpAddresses(char * s, int* returnSize){
    ans = (char** )malloc(0);
    anstop = 0;
    backtracking(s, 0, 0);
    *returnSize = anstop;
    return ans;
}

C版本的思路略有不同,因为每次回溯对字符串都进行增删操作过于麻烦,就只是记录当前满足条件的分割位置,最后满足条件,一并进行增加’.'的操作

可以看出,C++对于字符串的轮子就十分好用,C的话自己写字符串数组的操作很痛苦。

子集问题

例题78(中等)子集

注意要点:

  1. 子集就是要统计所有的可能,所以回溯函数一上来就是压入结果
  2. 子集问题可以不写return,因为我们要的就是遍历整棵树(也就是遍历所有元素),大的for循环到数组的大小就不会递增,也就不会需要return。

下面贴出代码:

CPP版本

class Solution {
private:
    vector<vector<int>> result;
    vector<int> path;
    void backtracking(const vector<int>& nums, int startIndex)
    {
        result.push_back(path);
        if (startIndex == nums.size()) {return;}

        for (int i = startIndex; i < nums.size(); i++)
        {
            path.push_back(nums[i]);
            backtracking(nums, i + 1);
            path.pop_back();
        }
    }

public:
    vector<vector<int>> subsets(vector<int>& nums) {
        result.clear();
        path.clear();
        backtracking(nums, 0);
        return result;
    }
};

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().
 */

int* path;
int pathTop;
int** ans;
int ansTop;
int* pathSize;

void copy()
{
    int* tmp = (int* )malloc(sizeof(int) * pathTop);
    for (int i = 0; i < pathTop; i++) {tmp[i] = path[i];}
    ans = (int** )realloc(ans, sizeof(int* ) * (ansTop + 1));
    pathSize = (int* )realloc(pathSize, sizeof(int) * (ansTop + 1));
    pathSize[ansTop] = pathTop;
    ans[ansTop++] = tmp;
}

void backtracking(int* nums, int numsSize, int startIndex)
{
    //copy需要在停止递归之前,否则会漏掉自己本身
    copy();
    //if (startIndex >= numsSize) {return;}

    for (int i = startIndex; i < numsSize; i++)
    {
        path[pathTop++] = nums[i];
        backtracking(nums, numsSize, i + 1);
        pathTop--;
    }
}

int** subsets(int* nums, int numsSize, int* returnSize, int** returnColumnSizes){
    ans = (int** )malloc(0);
    pathSize = (int* )malloc(0);
    path = (int* )malloc(sizeof(int) * numsSize);
    ansTop = pathTop = 0;
    backtracking(nums, numsSize, 0);
    *returnSize = ansTop;
    *returnColumnSizes = (int* )malloc(sizeof(int) * ansTop);
    for (int i = 0; i < ansTop; i++) {(*returnColumnSizes)[i] = pathSize[i];}
    return ans;
}

例题90(中等)子集II

注意要点:

  1. 与上一题的区别是,想同的子集不能出现,也就是说同一层内需要去重
  2. 去重的逻辑之前有过展示,只要先排序就可以把相等的元素凑在一起,之后的遍历如果遇到一样的元素就跳过

子集去重

以上是我摘出来的代码随想录的演示图,图里面是新建了一个used数组来记录这个数有没有使用过;在本题中可以不用。

下面贴出代码:

CPP版本

class Solution {
private:
    vector<vector<int>> result;
    vector<int> path;
    void backtracking(const vector<int>& nums, int startIndex)
    {
        result.push_back(path);

        for (int i = startIndex; i < nums.size(); i++)
        {
            if (i > startIndex && nums[i] == nums[i - 1]) {continue;}
            path.push_back(nums[i]);
            backtracking(nums, i + 1);
            path.pop_back();
        }
    }

public:
    vector<vector<int>> subsetsWithDup(vector<int>& nums) {
        result.clear();
        path.clear();
        sort(nums.begin(), nums.end());
        backtracking(nums, 0);
        return result;
    }
};

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().
 */

int* path;
int pathTop;
int** ans;
int ansTop;
int* pathSize;

int cmp(const void* a, const void* b)
{
    return *(int* )a - *(int* )b;
}

void copy()
{
    int* tmp = (int* )malloc(sizeof(int) * pathTop);
    for (int i = 0; i < pathTop; i++) {tmp[i] = path[i];}
    ans = (int** )realloc(ans, sizeof(int* ) * (ansTop + 1));
    pathSize = (int* )realloc(pathSize, sizeof(int) * (ansTop + 1));
    pathSize[ansTop] = pathTop;
    ans[ansTop++] = tmp;
}

void backtracking(int* nums, int numsSize, int startIndex)
{
    copy();

    for (int i = startIndex; i < numsSize; i++)
    {
        if (i > startIndex && nums[i] == nums[i - 1]) {continue;}
        path[pathTop++] = nums[i];
        backtracking(nums, numsSize, i + 1);
        pathTop--;
    }
}

int** subsetsWithDup(int* nums, int numsSize, int* returnSize, int** returnColumnSizes){
    ans = (int** )malloc(0);
    pathSize = (int* )malloc(0);
    path = (int* )malloc(sizeof(int) * numsSize);
    ansTop = pathTop = 0;
    qsort(nums, numsSize, sizeof(int), cmp);
    backtracking(nums, numsSize, 0);
    *returnSize = ansTop;
    *returnColumnSizes = (int* )malloc(sizeof(int) * ansTop);
    for (int i = 0; i < ansTop; i++) {(*returnColumnSizes)[i] = pathSize[i];}
    return ans;
}

递增子序列

例题491(中等)递增子序列

注意要点:

  1. 题设中说明不能有重复元素,所以需要去重;
  2. 去重可以用哈希,来判断同一树层中与其相等的元素是否有使用(给定了可能出现的次数就可以用数组);
  3. 哈希数组只需要插入,不需要删除(因为哈希去重只负责本层,新的一层有一个新定义的,相当于清空了)。

下面贴出代码:

CPP版本

class Solution {
private:
    vector<vector<int>> result;
    vector<int> path;
    void backtracking(const vector<int>& nums, int startIndex)
    {
        if (path.size() >= 2) {result.push_back(path);}

        int used[201] = {0};  //题目的所有nums的值域最多201,没给定只能unordered_set
        for (int i = startIndex; i < nums.size(); i++)
        {
            if ((!path.empty() && nums[i] < path.back()) || used[nums[i] + 100]) {continue;}
            used[nums[i] + 100] = 1;
            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;
    }
};

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().
 */
int* path;
int pathTop;
int** ans;
int ansTop;
int* pathSize;

void copy()
{
    int* tmp = (int* )malloc(sizeof(int) * pathTop);
    for (int i = 0; i < pathTop; i++) {tmp[i] = path[i];}
    pathSize[ansTop] = pathTop;
    ans[ansTop++] = tmp;
}

void backtracking(int* nums, int numsSize, int startIndex)
{
    //大于一个才是结果,存储一下,但不要return,因为接下来可能还要用到这个path的元素
    if (pathTop > 1) {copy();}
    
    int* used = (int* )malloc(sizeof(int) * 201);
    memset(used, 0, sizeof(int) * 201);
    for (int i = startIndex; i < numsSize; i++)
    {
        if (pathTop && nums[i] < path[pathTop - 1]) {continue;}
        if (used[nums[i] + 100]) {continue;}
        used[nums[i] + 100] = 1;
        path[pathTop++] = nums[i];
        backtracking(nums, numsSize, i + 1);
        pathTop--;
    }
}

int** findSubsequences(int* nums, int numsSize, int* returnSize, int** returnColumnSizes){
    ans = (int** )malloc(sizeof(int* ) * 40001);
    pathSize = (int* )malloc(sizeof(int) * 40001);
    path = (int* )malloc(sizeof(int) * numsSize);
    pathTop = ansTop = 0;
    backtracking(nums, numsSize, 0);
    *returnSize = ansTop;
    *returnColumnSizes = (int* )malloc(sizeof(int) * ansTop);
    for (int i = 0; i < ansTop; i++) {(*returnColumnSizes)[i] = pathSize[i];}
    return ans;
}

全排列

例题46(中等)全排列

注意要点:

  1. 全排列,不同顺序就是不同解,所以就不需要通过startIndex来完成来控制元素只能遍历一次
  2. 通过哈希数组来记录该元素是否使用过。

流程的话如下图所示:(老样子白嫖代码随想录)

全排列去重

下面贴出代码:

CPP版本

class Solution {
private:
    vector<vector<int>> result;
    vector<int> path;
    void backtracking(const vector<int>& nums, vector<bool>& used)
    {
        if (path.size() == nums.size())
        {
            result.push_back(path);
            return;
        }

        for (int i = 0; i < nums.size(); i++)
        {
            if (used[i]) {continue;}
            used[i] = 1;
            path.push_back(nums[i]);
            backtracking(nums, used);
            used[i] = 0;
            path.pop_back();
        }
    }
    
public:
    vector<vector<int>> permute(vector<int>& nums) {
        result.clear();
        path.clear();
        vector<bool> used(nums.size(), 0);
        backtracking(nums, used);
        return result;
    }
};

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().
 */

int** ans;
int ansTop;
int* path;
int pathTop;

void copy()
{
    int* tmp = (int* )malloc(sizeof(int) * pathTop);
    for (int i = 0; i < pathTop; i++) {tmp[i] = path[i];}
    ans = (int** )realloc(ans, sizeof(int* ) * (ansTop + 1));
    ans[ansTop++] = tmp;
}

void backtracking(int* nums, int numsSize, int* used)
{
    if (pathTop == numsSize)
    {
        copy();
        return;
    }

    for (int i = 0; i < numsSize; i++)
    {
        if (used[i]) {continue;}
        used[i] = 1;
        path[pathTop++] = nums[i];
        backtracking(nums, numsSize, used);
        pathTop--;
        used[i] = 0;
    }
}

int** permute(int* nums, int numsSize, int* returnSize, int** returnColumnSizes){
    ans = (int** )malloc(0);
    path = (int* )malloc(sizeof(int) * numsSize);
    int* used = (int* )malloc(sizeof(int) * numsSize);
    memset(used, 0, sizeof(int) * numsSize);
    pathTop = ansTop = 0;
    backtracking(nums, numsSize, used);
    *returnSize = ansTop;
    *returnColumnSizes = (int* )malloc(sizeof(int) * ansTop);
    for (int i = 0; i < ansTop; i++) {(*returnColumnSizes)[i] = numsSize;}
    return ans;
}

例题47(中等)全排列II

注意要点:

  1. 与上一题的区别在于,数组本身有重复元素;
  2. 去重方法也与之前一样,通过排序把重复元素连在一起,然后判断,如果与前一个元素相等且前一个元素被使用,就跳过

文字比较难理解,所以我把图给拉下来了:

有重复元素的全排列去重

下面贴出代码:

CPP版本

class Solution {
private:
    vector<vector<int>> result;
    vector<int> path;
    void backtracking(const vector<int>& nums, vector<bool>& used)
    {
        if (path.size() == nums.size())
        {
            result.push_back(path);
            return;
        }
        
        for (int i = 0; i < nums.size(); i++)
        {
            if ((i && nums[i] == nums[i - 1] && !used[i - 1]) || used[i]) {continue;}
            used[i] = 1;
            path.push_back(nums[i]);
            backtracking(nums, used);
            used[i] = 0;
            path.pop_back();
        }
    }

public:
    vector<vector<int>> permuteUnique(vector<int>& nums) {
        result.clear();
        path.clear();
        vector<bool> used(nums.size(), 0);
        sort(nums.begin(), nums.end());
        backtracking(nums, used);
        return result;
    }
};

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().
 */

int** ans;
int ansTop;
int* path;
int pathTop;

int cmp(const int* a, const int* b)
{
    return *a - *b;
}

void copy()
{
    int* tmp = (int* )malloc(sizeof(int) * pathTop);
    for (int i = 0; i < pathTop; i++) {tmp[i] = path[i];}
    ans = (int** )realloc(ans, sizeof(int*) * (ansTop + 1));
    ans[ansTop++] = tmp;
}

void backtracking(int* nums, int numsSize, int* used)
{
    if (pathTop == numsSize)
    {
        copy();
        return;
    }

    for (int i = 0; i < numsSize; i++)
    {
        if (used[i]) {continue;}
        if (i > 0 && nums[i] == nums[i - 1] && !used[i - 1]) {continue;}
        used[i] = 1;
        path[pathTop++] = nums[i];
        backtracking(nums, numsSize, used);
        used[i] = 0;
        pathTop--;
    }
}

int** permuteUnique(int* nums, int numsSize, int* returnSize, int** returnColumnSizes){
    ans = (int** )malloc(0);
    path = (int* )malloc(sizeof(int) * numsSize);
    ansTop = pathTop = 0;
    int* used = (int* )malloc(sizeof(int) * numsSize);
    for (int i = 0; i < numsSize; i++) {used[i] = 0;}
    qsort(nums, numsSize, sizeof(int), cmp);
    backtracking(nums, numsSize, used);
    *returnSize = ansTop;
    *returnColumnSizes = (int* )malloc(sizeof(int) * ansTop);
    for (int i = 0; i < ansTop; i++) {(*returnColumnSizes)[i] = numsSize;}
    return ans;
}

去重的办法

主要是针对C++这一块,因为C的话,哈希得用UT_hash_handle hh;然后自己定义一个结构体再去操作,其实挺麻烦的。

C++可以直接unordered_set,然后通过哈希查找就可以了。

例题90(中等)子集II

下面贴出代码:

class Solution {
private:
private:
    vector<vector<int>> result;
    vector<int> path;
    void backtracking(vector<int>& nums, int startIndex, vector<bool>& used)
    {
        result.push_back(path);
        unordered_set<int> uset;
        for (int i = startIndex; i < nums.size(); i++)
        {
            if (uset.find(nums[i]) != uset.end()) {continue;}
            uset.insert(nums[i]);
            path.push_back(nums[i]);
            backtracking(nums, i + 1, used);
            path.pop_back();
        }
    }

public:
    vector<vector<int>> subsetsWithDup(vector<int>& nums) {
        result.clear();
        path.clear();
        sort(nums.begin(), nums.end());
        vector<bool> used(nums.size(), 0);
        backtracking(nums, 0, used);
        return result;
    }
};

重新安排形行程

例题322(困难)重新安排行程

注意要点:

  1. 因为需要字母序的排序,所以还需要一个可以有顺序的映射关系,在C++中就可以使用map一个机场映射多个机场就可以unordered_map,多个机场映射一个机场就可以map或者multimap(相当于是1个key对多个value,或者是多个key对1个value);
  2. 映射关系还需要能够增删,来对应回溯算法;所以选择unordered_map<string, map<string, int>> targets,对应unordered_map<出发机场, map<到达机场, 航班次数>> targets;
  3. 可通过一个参数ticketNum记录result中的机场个数,如果机场个数等于航线数+1,说明可以结束遍历。

这道题很难,在容器的选择以及使用上都很困难,多看看有思路也就可以了,真的面试笔试碰到,认命尽量写也就是了。

下面贴出代码:

CPP版本

class Solution {
private:
    // unordered_map<出发机场, map<到达机场, 航班次数>> targets
    unordered_map<string, map<string, int>> targets;
    bool backtracking(vector<string>& result, int ticketNum)
    {
        if (ticketNum + 1 == result.size()) {return 1;}

        for (pair<const string, int>& target : targets[result[result.size() - 1]])
        {
            if (target.second)
            {
                result.push_back(target.first);
                target.second--;
                if (backtracking(result, ticketNum)) {return 1;}
                result.pop_back();
                target.second++;
            }
        }
        return 0;
    }
public:
    vector<string> findItinerary(vector<vector<string>>& tickets) {
        targets.clear();
        vector<string> result;
        for (const vector<string>& vec : tickets) {targets[vec[0]][vec[1]]++;}
        result.push_back("JFK");
        backtracking(result, tickets.size());
        return result;
    }
};

C版本

C版本的还涉及字符串的比较,自己创建哈希数组记录,非常麻烦,有个思路就好了。

/**
 * Note: The returned array must be malloced, assume caller calls free().
 */

char** result;
bool* used;
bool found;

int cmp(const void* a, const void* b)  //用于比较两个字符串数组,void*万能指针
{
    const char** tmp1 = *(char** )a;
    const char** tmp2 = *(char** )b;
    int ret = strcmp(tmp1[0], tmp2[0]);
    if (!ret) {return strcmp(tmp1[1], tmp2[1]);}
    return ret;
}

void backtracking(char*** tickets, int ticketsSize, int* returnSize, char* start, char** result, bool* used)
{
    if (*returnSize == ticketsSize + 1)
    {
        found = 1;
        return;
    }

    for (int i = 0; i < ticketsSize; i++)
    {
        if (!used[i] && !strcmp(start, tickets[i][0]))
        {
            result[*returnSize] = (char* )malloc(sizeof(char) * 4);
            memcpy(result[*returnSize], tickets[i][1], sizeof(char) * 4);
            (*returnSize)++;
            used[i] = 1;

            backtracking(tickets, ticketsSize, returnSize, tickets[i][1], result, used);
            if (found) {return;}
            (*returnSize)--;
            used[i] = 0;
        }
    }
    return;
}

char ** findItinerary(char *** tickets, int ticketsSize, int* ticketsColSize, int* returnSize){
    if (!tickets || ticketsSize <= 0) {return NULL;}
    result = (char** )malloc(sizeof(char*) * (ticketsSize + 1));
    used = (bool* )malloc(sizeof(bool) * ticketsSize);
    memset(used, 0, sizeof(bool) * ticketsSize);
    result[0] = (char* )malloc(sizeof(char) * 4);
    memcpy(result[0], "JFK", sizeof(char) * 4);
    found = 0;
    *returnSize = 1;
    qsort(tickets, ticketsSize, sizeof(tickets[0]), cmp);
    backtracking(tickets, ticketsSize, returnSize, "JFK", result, used);
    *returnSize = ticketsSize + 1;
    return result;
}

N皇后

例题51(困难)N皇后

注意要点:

  1. 是否可以放棋子,就是判断同行同列和两条斜对角线有否棋子,其中,因为回溯相当于按行去遍历,同行的检查就可以跳过
  2. 递归深度就是row控制棋盘的行,每一层里for循环的col控制棋盘的列,一行一列,确定了放置皇后的位置;
  3. 这里的回溯,相当于是放棋子和不放棋子,给棋盘的字符串赋值的过程相当于做了回溯。

下面贴出代码:

CPP版本

class Solution {
private:
    vector<vector<string>> result;
    bool isValid(int row, int col, vector<string>& chessboard, int n)
    {
        //遍历列,行由回溯控制,每次每层只放一个,不用遍历
        for (int i = 0; i < row; i++)
        {
            if (chessboard[i][col] == 'Q') {return 0;}
        }
        //主对角线遍历
        for (int i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--)
        {
            if (chessboard[i][j] == 'Q') {return 0;}
        }
        //副对角线遍历
        for (int i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++)
        {
            if (chessboard[i][j] == 'Q') {return 0;}
        }
        return 1;
    }

    void backtracking(vector<string>& chessboard, int row, int n)
    {
        if (row == n)
        {
            result.push_back(chessboard);
            return;
        }

        for (int col = 0; col < n; col++)
        {
            if (isValid(row, col, chessboard, n))
            {
                chessboard[row][col] = 'Q';
                backtracking(chessboard, row + 1, n);
                chessboard[row][col] = '.';
            }
        }
    }
public:
    vector<vector<string>> solveNQueens(int n) {
        result.clear();
        vector<string> chessboard(n, string(n, '.'));
        backtracking(chessboard, 0, n);
        return result;
    }
};

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().
 */

char*** ans;
char** path;
int ansTop, pathTop;

void copy(int n)
{
    char** tmp = (char** )malloc(sizeof(char* ) * pathTop);
    for (int i = 0; i < pathTop; i++)
    {
        tmp[i] = (char* )malloc(sizeof(char) * (n + 1));
        for (int j = 0; j < n; j++) {tmp[i][j] = path[i][j];}
        tmp[i][n] = '\0';  //每一行末尾要加终止符
    }
    ans = (char*** )realloc(ans, sizeof(char** ) * (ansTop + 1));
    ans[ansTop++] = tmp;
}

//判断当前位置是否有效(是否不被其它皇后影响)
int isValid(int x, int y, int n) {
    int i, j;
    //检查同一行以及同一列是否有效
    for(i = 0; i < n; ++i)
    {
        if(path[y][i] == 'Q' || path[i][x] == 'Q') {return 0;}
    }
    //下面两个for循环检查斜角45度是否有效
    i = y - 1;
    j = x - 1;
    while(i >= 0 && j >= 0)
    {
        if(path[i][j] == 'Q') {return 0;}
        --i, --j;
    }

    //下面两个for循环检查135度是否有效
    i = y - 1;
    j = x + 1;
    while(i >= 0 && j < n)
    {
        if(path[i][j] == 'Q') {return 0;}
        --i, ++j;
    }
    return 1;
}

void backtracking(int n, int depth) {
    //若path中有四个元素,将其拷贝到ans中。从当前层返回
    if(pathTop == n)
    {
        copy(n);
        return;
    }

    //遍历横向棋盘
    int i;
    for(i = 0; i < n; ++i)
    {
        //若当前位置有效
        if(isValid(i, depth, n))
        {
            //在当前位置放置皇后
            path[depth][i] = 'Q';
            //path中元素数量+1
            ++pathTop;
            backtracking(n, depth + 1);
            //进行回溯
            path[depth][i] = '.';
            //path中元素数量-1
            --pathTop;
        }
    }
}

char *** solveNQueens(int n, int* returnSize, int** returnColumnSizes){
    ans = (char*** )malloc(0);
    path = (char** )malloc(sizeof(char* ) * n);
    ansTop = pathTop = 0;
    for (int i = 0; i < n; i++)
    {
        path[i] = (char* )malloc(sizeof(char) * (n + 1));
        for (int j = 0; j < n; j++) {path[i][j] = '.';}
        path[i][n] = '\0';
    }
    backtracking(n, 0);
    *returnSize = ansTop;
    *returnColumnSizes = (int* )malloc(sizeof(int) * ansTop);
    for (int i = 0; i < ansTop; i++) {(*returnColumnSizes)[i] = n;}
    return ans;
}

解数独

例题37(困难)解数独

注意要点:

  1. 需要检测棋盘放置数字后是否有效,检查行、列以及小方框
  2. 回溯过程中,因为9个数字都试过都不行,需要bool来控制回溯函数跳出循环,否则会进入四魂环;

下面贴出代码:

CPP版本

class Solution {
private:
    bool isValid(char num, int row, int col, vector<vector<char>>& board)
    {
        int n = board.size();
        //检查行
        for (int j = 0; j < n; j++)
        {
            if (board[row][j] == num) {return 0;}
        }
        //检查列
        for (int i = 0; i < n; i++)
        {
            if (board[i][col] == num) {return 0;}
        }
        //检查小块
        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] == num) {return 0;}
            }
        }
        return 1;
    }
    bool backtracking(vector<vector<char>>& board)
    {
        for (int i = 0; i < board.size(); i++)
        {
            for (int j = 0; j < board[0].size(); j++)
            {
                if (board[i][j] != '.') {continue;}
                for (char k = '1'; k <= '9'; k++)
                {
                    if (isValid(k, i, j, board))
                    {
                        board[i][j] = k;
                        if (backtracking(board)) {return 1;}
                        board[i][j] = '.';
                    }
                }
                return 0;
            }
        }
        return 1;
    }
public:
    void solveSudoku(vector<vector<char>>& board) {
        backtracking(board);
    }
};

C版本

bool isValid(int row, int col, char** board, char key)
{
    //检查行是否有重复
    for (int i = 0; i < 9; i++)
    {
        if (board[row][i] == key) {return false;}
    }
    //检查列
    for (int j = 0; j < 9; j++)
    {
        if (board[j][col] == key) {return false;}
    }
    //检查小方块
    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] == key) {return false;}
        }
    }
    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;}
            //填入数字进行尝试
            for (char k = '1'; k <= '9'; k++)
            {
                if (isValid(i, j, board, k))
                {
                    board[i][j] = k;
                    if (backtracking(board, boardSize, boardColSize)) {return true;}
                    board[i][j] = '.';
                }
            }
            //如果9个数都false,那么就无法完成数独,直接return false
            return false;
        }
    }
    return true;
}

void solveSudoku(char** board, int boardSize, int* boardColSize){
    backtracking(board, boardSize, boardColSize);
}

总结

题目类型总结

  • 组合问题

for循环横向遍历,递归纵向遍历,回溯不断调整结果集,剪枝精髓是:for循环在寻找起点的时候要有一个范围,如果这个起点到集合终止之间的元素已经不够题目要求的k个元素了,就没有必要搜索了

组合问题的经典剪枝如下所示:

i <= 9 - (k - path.size()) + 1
for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; i++)
排序后判断 candidate[i] == candidate[i - 1] continue;
  • 切割问题

如何模拟切割线,如何终止,如何截取子串,完成以上后的逻辑判断和回溯模板反而简单。前者设计string的一些操作,多看看题熟悉这些API。

  • 子集问题

子集问题有一些都不需要写return的if语句,因为在回溯的for循环中已经控制循环变量不超过数组大小;

原数组包含重复元素,子集不能重复,可在排序后如下操作进行去重:

 if (i > startIndex && nums[i] == nums[i - 1]) {continue;}

如果没给定大小,或者说题目要求导致原数组不能排序,那就只能用unordered_set哈希数组来做去重。

  • 排列问题

排列问题由于不需要顺序,所以回溯不需要startIndex!!!

排列问题可以在原数组排序后如下去重:

if ((i && nums[i] == nums[i - 1] && !used[i - 1]) || used[i]) {continue;}
  • 图论题

代码随想录已经开始更新图论了,之后我刷到在更新。

  • 棋盘问题

棋盘问题大多都需要bool的回溯函数来控制退出,找到合适的就true进入下一次,否则就退出来;如果退出到所有可能均尝试就直接false退出来了。

回溯算法的总结

一些函数API的调用

77题中:vector的插入和删除操作

path.push_back(元素);
path.pop_back();

17题中:新建一个string类型的哈希表

const string letterMap[10] {};

针对类型为string的vector,push_back可以换成“+=”,但是还是只能pop_back()。

39题中:可以看出for循环的写法并不死板

for (int i = startIndex; i < candidates.size() && candidates[i] + sum <= target; i++)

131中:字符串的切分操作

string str = s.substr(startIndex, i - startIndex + 1);

其中,第一个参数是起始位置,第二个则是切出来的长度。

93题中:字符串的插入和删除

s.insert(s.begin() + i + 1, '.');
s.erase(s.begin() + i + 1);

46题中:以下完成对vector的初始化

vector<bool> used(nums.size(), 0);

332题中:完成一个哈希容器的定义,几个语法熟悉一下

// unordered_map<出发机场, map<到达机场, 航班次数>> targets
    unordered_map<string, map<string, int>> targets;
	for (pair<const string, int>& target : targets[result[result.size() - 1]])
    for (const vector<string>& vec : tickets) {targets[vec[0]][vec[1]]++;}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值