回溯算法精讲

原理

回溯,就和深度优先遍历(DFS)类似,属于先一层到底直至到终点,如果这条路径不对,则回退一下,再继续往下搜索。

抽象地说,解决一个回溯问题,实际上就是遍历一棵决策树的过程,树的每个叶子节点存放着一个合法答案。你把整棵树遍历一遍,把叶子节点上的答案都收集起来,就能得到所有的合法答案。

基本策略属于:”深度优先遍历“ + ”状态回退“

核心框架

路径:已经做出的选择。
选择列表:当前可以做的选择。
结束条件:达到决策树底层,无法再做选择的条件。

for 属于横向遍历
递归,属于纵向遍历
这样就将整个集合全部搜索了一遍

在这里插入图片描述

void backtrack(路径, 选择列表):
    if 满足结束条件:
        result.add(路径)
        return
    
    for 选择 in 选择列表:
        做选择
        backtrack(路径, 选择列表)
        撤销选择

做选择:将当前的选择加入到路径中,并更新选择列表和路径。
递归调用:继续向下逐层递归,如果到达决策树的底层,满足结束条件,则将当前路径加入到结果集中。
撤销选择:递归完成返回后,撤销前一步的选择,恢复状态,尝试其他选项。

核心就是 for 循环里面的递归,在递归调用之前「做选择」,在递归调用之后「撤销选择」

回溯法主要解决什么问题?

当回溯法求解问题,我们可以尽量将问题抽象为树形结构, 上文我们都说过属于 深度遍历搜索 + 回退状态,所以尽可能的抽象为树结构。
回溯法就可以理解为,再集合中递归查询子集,集合大小就是抽象树的宽度,递归深度就是抽象树的深度。

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

组合问题

力扣77题:组合

在这里插入图片描述
思路:
第一,先抽象为一个树。
在这里插入图片描述
第二:找到终止条件
根据题意,当路径达到我们需要的,我们就直接终止

        if(path.size() == k){
            res.push_back(path);
            return;
        }

第三、怎么横向遍历?
直接往后一步一步挪动。

        for(int i = start; i<=n;i++){
            path.push_back(i);
            back(n,k,i+1);
            path.pop_back();
        }

该题解完整代码:

class Solution {
public:

    vector<vector<int>> res; // 最终结果
    vector<int> path;		//	每次查询的路径

    vector<vector<int>> combine(int n, int k) {
        back(n,k,1);
        // 测试输出
        for (vector<vector<int>>::iterator it = res.begin(); it != res.end(); it++) {
                for (vector<int>::iterator jt = (*it).begin(); jt != (*it).end(); jt++) {
                    cout << *jt << " ";
                }
                cout << endl;
        }
        return res;
    }
    // 回溯代码
    void back(int n,int k ,int start){
        if(path.size() == k){
            res.push_back(path);
            return;
        }
        for(int i = start; i<=n;i++){
            path.push_back(i);
            back(n,k,i+1);
            path.pop_back();
        }
    }
};

如何优化呢?就是剪枝问题了。比如就这道题,如果 N= 4, k = 4,那是不是从 2 开始后面都不行,因为不够4个数。但是我们上文代码,属于全部搜索一下。如何优化?
在这里插入图片描述

for (int i = start; i <= n - (k - path.size()) + 1; i++) // i为本次搜索的起始位置

力扣 216 组合总和III

在这里插入图片描述

这题和上题,思路基本一致,唯一修改,就是我们再遍历路径的时候,对路径上的值进行加减。 直接看代码:

class Solution {
public:
    vector<vector<int>> res;
    vector<int> path;

    int all = 0; // 用来记录路径上的值
    void back(int k ,int n, int start){
        if(all > n){  // 剪枝操作,此处代表后续路径都不可能会存在了
            return;
        }
        if(path.size()==k&& all == n){
            // if(all == n){
            //     res.push_back(path);
            // }
            res.push_back(path);
            return;
        }
        for(int i = start ; i<=9 - (k-path.size()) + 1 ;i++){
            all += i;   
            path.push_back(i);
            back(k,n,i+1);
           // int c = path.back();
           // path.pop_back();
           // all -= c;  // 回退也需要将他减去
           path.pop_back();
           all -= i;
        }
    }
    vector<vector<int>> combinationSum3(int k, int n) {
        back(k,n,1);
        return res;
    }
};

力扣17.电话号码的字母组合

在这里插入图片描述
思路:

这道题终究还是归结于组合问题,无非事每一层树节点需要根据数字进行判断,看下图。

在这里插入图片描述

class Solution {
public:

    const string letterMap[10] = {
        "", // 0
        "", // 1
        "abc", // 2
        "def", // 3
        "ghi", // 4
        "jkl", // 5
        "mno", // 6
        "pqrs", // 7
        "tuv", // 8
        "wxyz", // 9
    };

    vector<string> res;
    string path;

    void back(string digits ,int index){
        if(index == digits.size()){
            res.push_back(path);
            return;
        }
        int digit = digits[index] - '0';
        string letters = letterMap[digit];
        for(int i = 0 ; i< letters.size();i++){
            path.push_back(letters[i]); 
            back(digits,index+1);  // 递归处理下一层数字
            path.pop_back();
        }
    }

    vector<string> letterCombinations(string digits) {
        if(digits.size() == 0){
            return res;
        }
        
        back(digits,0);
        return res;
    }
};

力扣39. 组合总和

在这里插入图片描述
思路:会故意想上文216题,是不是也是存在路径值得问题?但这题与之区别是什么呢?因为这题得节点可以重复选取,我们需要怎么改变?

看代码,我们在 back得时候,是不是又从 i 节点开始得?代表我每次都可以重复选取
在这里插入图片描述

        for(int i = index;i<candidates.size();i++){
            path.push_back(candidates[i]);
            num += candidates[i];
            back(candidates,target,i);
            num -= candidates[i];
            path.pop_back();
        }

完整代码:

class Solution {
public:

    vector<vector<int>> res;
    vector<int>path;

    int num = 0;
    void back(vector<int> candidates,int target,int index){
        if(num > target){
            return;
        }
        if(num == target){
            res.push_back(path);
            return;
        }
        for(int i = index;i<candidates.size();i++){
            path.push_back(candidates[i]);
            num += candidates[i];
            back(candidates,target,i);
            num -= candidates[i];
            path.pop_back();
        }
    }
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        for(int i = 0;i<candidates.size();i++){
            cout<<setw(4)<<candidates[i];
        }
        back(candidates,target,0);
        return res;
    }
};

力扣40.组合总和II

在这里插入图片描述
思路:

对于去重问题,我们得考虑,什么是树枝

根据题目,我们得看一点,集合中数据存在重复,然后每个数字只能用一次。这样我们就需要去加个判断了。
经典判断:
用来看这个数据我们处理过没有。

  // used[i - 1] == true,说明同一树枝candidates[i - 1]使用过
 // used[i - 1] == false,说明同一树层candidates[i - 1]使用过
        bool visited[candidates.size()] ;
        for(int i = 0 ;i<candidates.size();i++){
            visited[i] = false;
        }

看完整代码:

class Solution {
public:

    vector<vector<int>> res;
    vector<int>path;
    int num = 0;
    void back(vector<int> candidates,int target,int index,bool visited[]){
        if(num > target){
            return;
        }
        if(num == target){
            // // 此处比较超出时间限制
            // vector<vector<int>>::iterator it = find(res.begin(), res.end(), path);
            // if(it == res.end()){
            //     res.push_back(path);
            // }
            res.push_back(path);
            return;
        }
        for(int i = index;i<candidates.size() && (num+candidates[i])<=target;i++){
        	// 通过 visited进行去重
            // if(i>index && candidates[i] == candidates[i-1] && visited[i-1] ==){
            //     continue;
            // }
            
            // 判断同层,是否有遇到相同数字,如果遇到,则跳过,因为会重复
            if(i>index && candidates[i] == candidates[i-1] ){
                continue;
            }
            path.push_back(candidates[i]);
            visited[i] = true;
            num += candidates[i];
            back(candidates,target,i+1,visited);
            num -= candidates[i];
            visited[i] =false;
            path.pop_back();
        }
    }
    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        
        bool visited[candidates.size()] ;
        for(int i = 0 ;i<candidates.size();i++){
            visited[i] = false;
        }

        sort(candidates.begin(),candidates.end()); // 排序
        for(int i = 0;i<candidates.size();i++){
            cout<<setw(4)<<candidates[i];
        }

        back(candidates,target,0,visited);
        return res;
    }
};

组合总结

其实组合问题,最经典就是77题的组合,其他题型无非就是在此基础上加入了路径的值,或者不能重复。

重点:

怎么去重?
去什么的重? 树枝上?还是同一树层?
怎么使用visited,来判断是否经历过这个节点?

  // used[i - 1] == true,说明同一树枝candidates[i - 1]使用过
 // used[i - 1] == false,说明同一树层candidates[i - 1]使用过

组合问题总结

组合问题最基础还是77题,基本都是在此基础上的衍生。

核心回溯模板如下:

    void backtracking(int n, int k, int index)
    {
        if(path.size() == k)
        {
            res.push_back(path);
            return;
        }

        for(int i = index; i<=n;i++)
        {
            path.push_back(i);
            backtracking(n,k,i+1);  // 不能重复选择
            path.pop_back();
        }
    }

当我们可以重复选择时,在递归时,就得注意一下了

backtracking(n,k,i);   // 可以重复选择

接下来就是关于去重问题了

去重分为两种:
树枝去重:used[i - 1] == true,说明同一树枝candidates[i - 1]使用过
树层去重:used[i - 1] == false,说明同一树层candidates[i - 1]使用过

分割问题

我们先搞清楚,分割和组合得区别
比如字符串 abcdef
组合问题:选取一个a之后,在bcdef中再去选取第二个,选取b之后在cdef中再选取第三个…。
切割问题:切割一个a之后,在bcdef中再去切割第二段,切割b之后在cdef中再切割第三段…。

131.分割回文串

在这里插入图片描述
怎么将切割问题转化为抽象树呢?
在这里插入图片描述
注意:每切一个,我们得想想还剩下什么,剩下的又怎么切?什么时候去切?
核心代码:

       for(int i = index;i<s.size();i++){
            if(ishuiwen(s,index,i)){
                string str = s.substr(index, i - index + 1);
                path.push_back(str);
                back(s,i+1);
                path.pop_back();
            }
        }

完整代码:

class Solution {
public:

    //` string substr(int pos = 0, int n = npos) const; //返回由pos开始的n个字符组成的字符串`
    vector<vector<string>> res;
    vector<string> path;

    void back(string s,int index){
        if(index >= s.size()){
            res.push_back(path);
            return;
        }

        for(int i = index;i<s.size();i++){
            if(ishuiwen(s,index,i)){
                string str = s.substr(index, i - index + 1);
                path.push_back(str);
                back(s,i+1);
                path.pop_back();
            }
        }
    }

    bool ishuiwen(string s,int index ,int c){
        for(int i = index,j = c; i<j;i++,j--){
            if(s[i] == s[j]){
                continue;
            }else{
                return false;
            }
        }
        return true;
    }
    vector<vector<string>> partition(string s) {
        back(s,0);
        return res;
    }
};

力扣93.复原IP地址

在这里插入图片描述
思路:
我们此处怎么去切割?是不是就等于在字符串上加个 ’.‘ 是不是就属于切割?
在这里插入图片描述
思路:

这个也属于分割问题
我们按照其中的 ‘.’ 数来确实是否达标了
选择代码如下

if (count > 4) return; // 超过四部分,返回
        if (index == s.size() && count == 4) { // 完全匹配四部分
            res.push_back(path.substr(0, path.length() - 1)); // 移除最后的一个点
            return;
        }

那么如何在数层排列呢?
我们可以按照点数进行循环

        for (int i = 1; i <= 3; i++) {
            if (index + i > s.size()) break; // 超出字符串长度
            string str = s.substr(index, i);
            if (isValid(str)) {
                path += str + ".";
                back(s, index + i, count + 1);
                path.erase(path.length() - i - 1, i + 1); // 回溯,移除刚添加的部分及点
            }
        }

完整代码如下:

class Solution {
public:
    vector<string> res;
    string path;
    
    void back(string s, int index, int count) {
        if (count > 4) return; // 超过四部分,返回
        if (index == s.size() && count == 4) { // 完全匹配四部分
            res.push_back(path.substr(0, path.length() - 1)); // 移除最后的一个点
            return;
        }

        for (int i = 1; i <= 3; i++) {
            if (index + i > s.size()) break; // 超出字符串长度
            string str = s.substr(index, i);
            if (isValid(str)) {
                path += str + ".";
                back(s, index + i, count + 1);
                path.erase(path.length() - i - 1, i + 1); // 回溯,移除刚添加的部分及点
            }
        }
    }

    bool isValid(const string& str) {
        if (str.size() > 1 && str[0] == '0') return false; // 防止前导零
        int num = stoi(str);
        return num <= 255;
    }
    
    vector<string> restoreIpAddresses(string s) {
        back(s, 0, 0);
        return res;
    }
};

分割问题总结

其实分割问题,还是在 77 题组合问题上的修改。
我们在详细分化一下什么是组合,什么又是分割。

组合问题
组合问题主要是在不考虑元素顺序的情况下,从一组元素中选择若干元素的所有可能方式。

元素顺序不重要:选择的元素集合 [1,2] 和 [2,1] 被认为是相同的。
不切割原数据:直接在原数据集上进行选择。

分割问题
分割问题通常是指将一个字符串或数组分割成符合特定条件的多个子部分。

考虑元素顺序:分割的段必须维持原有元素的顺序。
切割原数据:需要在原数据中按顺序选择切点。

我们给出核心模板代码:

    void backtrack(const string& s, int start) {
        if (start == s.size()) { // 如果起始位置已经到达字符串末尾
            result.push_back(path);
            return;
        }

        for (int i = start; i < s.size(); i++) {
            if (isValid(s, start, i)) { // 判断当前分割是否有效
                path.push_back(s.substr(start, i - start + 1)); // 取子串并加入路径
                backtrack(s, i + 1); // 递归调用,从下一个字符开始新的分割
                path.pop_back(); // 回溯,撤销上一步的分割
            }
        }
    }

    bool isValid(const string& s, int start, int end) {
        // 判断 s[start...end] 是否符合条件,具体实现依据问题而定
        while (start < end) {
            if (s[start] != s[end])
                return false;
            start++;
            end--;
        }
        return true;
    }

子集问题

子集问题,就是组合问题的特殊版本。
子集问题,属于集合是无序的:那么既然是无序,取过的元素不会重复取,写回溯算法的时候,for就要从startIndex开始,而不是从0开始!

以示例中nums = [1,2,3]为例把求子集抽象为树型结构,如下:
在这里插入图片描述

78.子集

在这里插入图片描述
思路:

我们在77题,基础上进行修改,就是无序的组合

完整代码:

class Solution {
public:
    vector<vector<int>> res;
    vector<int>path;

    void back(vector<int> nums,int index){
        res.push_back(path);
        if(index>=nums.size()){
            return;
        }


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

    vector<vector<int>> subsets(vector<int>& nums) {
        // res.push_back(path);
        back(nums,0);
        return res;
    }
};

90.子集II

在这里插入图片描述
思路

我们先明白一点,这个子集不能是同重复的子集
是不是又回到当初的一个知识点?
判断是数层重复?还是树枝重复?
在这里插入图片描述

核心去重代码
我们用之前used方法进行判断:

            // used[i - 1] == true,说明同一树枝candidates[i - 1]使用过
            // used[i - 1] == false,说明同一树层candidates[i - 1]使用过
            // 而我们要对同一树层使用过的元素进行跳过
            if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) {
                continue;
            }
		 // 跳过这个递归层次,防止重复
            if(i > index && nums[i] == nums[i-1] && i >= 1){
                continue;
            }

完整代码:

class Solution {
public:

    vector<vector<int>> res;
    vector<int> path;

    void back(vector<int> &nums,int index){
        res.push_back(path);
        if(index >= nums.size()){
            return;
        }

        for(int i = index;i<nums.size();i++){
            // 跳过这个递归层次,防止重复
            if(i > index && nums[i] == nums[i-1] && i >= 1){
                continue;
            }
            path.push_back(nums[i]);
            back(nums,i+1);
            path.pop_back();
        }
    }

    vector<vector<int>> subsetsWithDup(vector<int>& nums) {
        sort(nums.begin(),nums.end());
        back(nums,0);
        return res;
    }
};

排列问题

排列,是属于有序的。和前文的组合不一样。比如: [1,2] 和 [2,1] 是两个集合。
可以看出元素1在[1,2]中已经使用过了,但是在[2,1]中还要在使用一次1,所以处理排列问题就不用使用startIndex了。
所以此时for循环如下,直接从0开始,不在需要startIndex索引了。

for (int i = 0; i < nums.size(); i++) 

那如何判断,是否数组里面有的已经使用过呢?此时我们就要继续用到上述的 used 函数。

  if (used[i] == true) continue; // path里已经收录的元素,直接跳过

在这里插入图片描述

46.全排列

在这里插入图片描述
思路:

我们还是可以按照,77题基础代码进行修改。
我们加入判断,看看是不是使用过的。

if (used[i] == true) continue; // path里已经收录的元素,直接跳过

完整代码如下:

class Solution {
public:

    vector<vector<int>> res;
    vector<int> path;
    void back(vector<int> &nums, vector<bool>& visited){
        if(path.size() == nums.size()){
            res.push_back(path);
            return;
        }
        for(int i = 0 ; i < nums.size();i++){
            if(visited[i]== true){
                continue;
            }
            path.push_back(nums[i]);
            visited[i] = true;
            back(nums,visited);
            visited[i] = false;
            path.pop_back();
        }
    }
    vector<vector<int>> permute(vector<int>& nums) {
        vector<bool> visited(nums.size(),false);
        back(nums,visited);
        return res;
    }
};

47.全排列 II

在这里插入图片描述
思路:

对比于上一个题,我们区别是什么?就是不重复的全排列。
不重复,第一时间想到的就是,树枝去重?还是数层去重呢?
此题,就是树层的去重。

核心代码如下:

if (used[i]) continue; // 如果当前元素已使用,则跳过
if (i > 0 && nums[i] == nums[i - 1] && !used[i - 1]) continue; // 跳过重复元素

完整代码如下:

#include <vector>
#include <algorithm>

using namespace std;

class Solution {
public:
    vector<vector<int>> permuteUnique(vector<int>& nums) {
        vector<vector<int>> results;
        vector<int> path;
        vector<bool> used(nums.size(), false); // 标记是否使用过该元素
        sort(nums.begin(), nums.end()); // 排序,以便跳过重复元素
        backtrack(nums, path, used, results);
        return results;
    }

    void backtrack(const vector<int>& nums, vector<int>& path, vector<bool>& used, vector<vector<int>>& results) {
        if (path.size() == nums.size()) {
            results.push_back(path);
            return;
        }

        for (int i = 0; i < nums.size(); ++i) {
            if (used[i]) continue; // 如果当前元素已使用,则跳过
            if (i > 0 && nums[i] == nums[i - 1] && !used[i - 1]) continue; // 跳过重复元素
            
            path.push_back(nums[i]);
            used[i] = true;
            backtrack(nums, path, used, results);
            path.pop_back();
            used[i] = false;
        }
    }
};

排列问题总结

回溯法解决排列问题的原理:

选择:从一组数中选出一个元素作为排列的一部分,并标记该元素以避免重复使用。
约束:在选择下一个元素时,必须确保它尚未在当前排列中使用过(对于不包含重复元素的数组)。
目标:当排列达到数组的长度时,说明找到了一个可能的解决方案。

核心代码模板

#include <vector>

using namespace std;

class Solution {
public:
    vector<vector<int>> permute(vector<int>& nums) {
        vector<vector<int>> res;
        vector<int> path;
        vector<bool> used(nums.size(), false);
        backtrack(nums, path, used, res);
        return res;
    }

    void backtrack(vector<int>& nums, vector<int>& path, vector<bool>& used, vector<vector<int>>& res) {
        if (path.size() == nums.size()) { // 所有数都选完了
            res.push_back(path);
            return;
        }

        for (int i = 0; i < nums.size(); i++) {
            if (used[i]) continue; // 这个数已经用过了,跳过
            path.push_back(nums[i]);
            used[i] = true;
            backtrack(nums, path, used, res); // 继续递归填下一个数
            path.pop_back(); // 撤销选择
            used[i] = false; // 撤销选择
        }
    }
};

棋盘问题

51. N皇后

在这里插入图片描述
思路:

第一个想法是,如何抽象为一个树?
这种类型就是属于网格树

在这里插入图片描述
我们要写出判断是否符合要求的位置:

    bool isCanreach(int row,int col ,int n){
        // 同列
        for(int i = 0; i < row; i++){
            if(path[i][col] == 'Q'){
                return false;
            }
        }

        // 左上
        for(int i = row-1,j = col-1; i>=0&&j>=0; i--,j--){
            if(path[i][j] == 'Q'){
                return false;
            }
        }

        // 右上
        for(int i = row - 1, j = col+1; i>=0 && j<n; i--,j++){
            if(path[i][j] == 'Q'){
                return false;
            }
        }
        return true;
    }

再写出回溯部分代码:

    void back(int n,int row){
        if(row == n){
            res.push_back(path);
            return ;
        }

        for(int col = 0 ; col < n; col++){
            if(isCanreach(row,col,n)){
                path[row][col] = 'Q';
                back(n,row+1);
                path[row][col] = '.';
            }
        }
    }

完整代码如下:

class Solution {
public:
    vector<vector<string>> res;
    vector<string> path;

    void back(int n,int row){
        if(row == n){
            res.push_back(path);
            return ;
        }

        for(int col = 0 ; col < n; col++){
            if(isCanreach(row,col,n)){
                path[row][col] = 'Q';
                back(n,row+1);
                path[row][col] = '.';
            }
        }
    }

    bool isCanreach(int row,int col ,int n){
        // 同列
        for(int i = 0; i < row; i++){
            if(path[i][col] == 'Q'){
                return false;
            }
        }

        // 左上
        for(int i = row-1,j = col-1; i>=0&&j>=0; i--,j--){
            if(path[i][j] == 'Q'){
                return false;
            }
        }

        // 右上
        for(int i = row - 1, j = col+1; i>=0 && j<n; i--,j++){
            if(path[i][j] == 'Q'){
                return false;
            }
        }
        return true;
    }

    vector<vector<string>> solveNQueens(int n) {
        path.resize(n, string(n, '.'));
        back(n,0);
        return res;
    }
};

37. 解数独在这里插入图片描述

思路:
我们还是得先想想这种问题,如何抽象为一个抽象树呢?
在这里插入图片描述
判断棋盘是否合法有如下三个维度:

同行是否重复
同列是否重复
9宫格里是否重复

    bool isCanreach(vector<vector<char>>& board,int row,int col,char c){
        for(int i = 0; i<9;i++){
            if(board[row][i] == c){
                return false;
            }
            if(board[i][col] == c){
                return false;
            }
            // 3*3方格
            if(board[3*(row/3)+i/3][3*(col/3)+i%3] == c){
                return false;
            }
        }
        return true;
    }

一个for循环遍历棋盘的行,一个for循环遍历棋盘的列,一行一列确定下来之后,递归遍历这个位置放9个数字的可能性!

    bool back(vector<vector<char>>& board){
        for(int i = 0;i<9;i++){
            for(int j = 0;j<9;j++){
                if(board[i][j] == '.'){
                    for(char c = '1' ; c<='9';c++){
                        if(isCanreach(board,i,j,c)){
                            board[i][j] = c;
                            if(back(board)){
                                return true;
                            }
                            // 回溯
                            board[i][j] = '.';
                        }
                    }
                    // 代表所有数字都不行
                    return false;
                }
            }
        }
        return true;
    }

完整代码:

class Solution {
public:

    //vector<vector<char>> res;

    bool back(vector<vector<char>>& board){
        for(int i = 0;i<9;i++){
            for(int j = 0;j<9;j++){
                if(board[i][j] == '.'){
                    for(char c = '1' ; c<='9';c++){
                        if(isCanreach(board,i,j,c)){
                            board[i][j] = c;
                            if(back(board)){
                                return true;
                            }
                            // 回溯
                            board[i][j] = '.';
                        }
                    }
                    // 代表所有数字都不行
                    return false;
                }
            }
        }
        return true;
    }

    bool isCanreach(vector<vector<char>>& board,int row,int col,char c){
        for(int i = 0; i<9;i++){
            if(board[row][i] == c){
                return false;
            }
            if(board[i][col] == c){
                return false;
            }
            // 3*3方格
            if(board[3*(row/3)+i/3][3*(col/3)+i%3] == c){
                return false;
            }
        }
        return true;
    }

    void solveSudoku(vector<vector<char>>& board) {
        back(board);
    }
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/617196.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【神经网络】输出层的设计

文章目录 前言一、恒等函数和softmax函数恒等函数softmax 函数python实现softmax函数 二、实现softmax函数时的注意事项函数优化python实现 三、softmax函数的特征计算神经网络的输出输出层的softmax函数可以省略“学习”和“推理”阶段 四、输出层的神经元数量 前言 神经网络…

Disk Map for Mac,让您的Mac更“轻”松

还在为Mac磁盘空间不足而烦恼吗&#xff1f;Disk Map for Mac来帮您轻松解决&#xff01;通过独特的TreeMap视觉显示技术&#xff0c;让您一眼就能看出哪些文件和文件夹占用了大量空间。只需简单几步操作&#xff0c;即可快速释放磁盘空间&#xff0c;让您的Mac更“轻”松。快来…

el-checkbox选中后的值为id,组件显示为label中文

直接上代码 方法一 <el-checkbox v-for"item in list" :key"item.id" :label"item.id">{{中文}} </el-checkbox> 方法二 <el-checkbox-group class"flex_check" v-model"rkStatusList" v-for"item…

prometheus、mysqld_exporter、node_export、Grafana安装配置

工具简介 Prometheus&#xff08;普罗米修斯&#xff09;&#xff1a;是一个开源的服务监控系统和时间序列数据库 mysqld_exporter&#xff1a; 用于监控 mysql 服务器的开源工具&#xff0c;它是由 Prometheus 社区维护的一个官方 Exporter。该工具通过连接到mysql 服务器并执…

EasyNmon服务器性能监控工具环境搭建

一、安装jdk环境 1、看我这篇博客 https://blog.csdn.net/weixin_54542209/article/details/138704468 二、下载最新easyNmon包 1、下载地址 https://github.com/mzky/easyNmon/releases wget https://github.com/mzky/easyNmon/releases/download/v1.9/easyNmon_AMD64.tar.…

openssl 生成证书步骤

本地测试RSA非对称加密功能时&#xff0c;需要用到签名证书。本文记录作者使用openssl本地生成证书的步骤&#xff0c;并没有深入研究openssl&#xff0c;难免会有错误&#xff0c;欢迎指出&#xff01;&#xff01;&#xff01; 生成证书标准流程&#xff1a; 1、生成私钥&am…

单位学校FM调频电台直放站系统

随着教育技术的不断发展&#xff0c;校园广播系统的建设已成为现代学校必不可少的一部分。作为传统有线广播的有效补充&#xff0c;基于无线电信号传输的 FM 调频电台在学校的使用日益广泛&#xff0c;尤其是在紧急通知、日常信息传播及教学辅助等方面发挥着重要作用。为了增强…

msvcp140dll怎么修复,分享5种有效的解决方法

MSVCP140.dll文件丢失这一现象究竟是何缘由&#xff0c;又会引发哪些令人头疼的问题呢&#xff1f;在探索这个问题的答案之前&#xff0c;我们先来深入了解这个神秘的DLL文件。MSVCP140.dll是Microsoft Visual C Redistributable Package的一部分&#xff0c;它扮演着至关重要的…

IP地址定位技术在网络安全中的作用

在当今数字化时代&#xff0c;网络安全已经成为企业、政府和个人面临的重要挑战之一。随着互联网的普及和网络攻击的增加&#xff0c;保护个人隐私和防止网络犯罪变得尤为重要。在这一背景下&#xff0c;IP地址定位技术作为网络安全的重要组成部分之一&#xff0c;发挥着关键作…

【Shell】shell编程之循环语句

目录 1.for循环 例题 2.while循环 例题 3.until循环 1.for循环 读取不同的变量值&#xff0c;用来逐个执行同一组命令 for 变量 in 取值列表 do 命令序列 done [rootlocalhost ~]# for i in 1 2 3 > do > echo "第 $i 次跳舞" > done 第 1 次跳舞 第 …

Redis经典问题:数据不一致

大家好,我是小米,今天我想和大家聊一聊Redis的一个经典问题——数据不一致。在使用Redis的过程中,你是否曾遇到过这样的问题?缓存和数据库中的数据不一致,可能导致应用程序的功能异常。下面,我将详细介绍数据不一致的原因,以及一些有效的解决方案。 什么是数据不一致 …

WordPress插件Plus WebP,可将jpg、png、bmp、gif图片转为WebP

现在很多浏览器和CDN都支持WebP格式的图片了&#xff0c;不过我们以前的WordPress网站使用的图片都是jpg、png、bmp、gif&#xff0c;那么应该如何将它们转换为WebP格式的图片呢&#xff1f;推荐安装这款Plus WebP插件&#xff0c;可以将上传到媒体库的图片转为WebP格式图片&am…

picoCTF-Web Exploitation-Trickster

Description I found a web app that can help process images: PNG images only! 这应该是个上传漏洞了&#xff0c;十几年没用过了&#xff0c;不知道思路是不是一样的&#xff0c;以前的思路是通过上传漏洞想办法上传一个木马&#xff0c;拿到webshell&#xff0c;今天试试看…

多线程-线程安全

目录 线程安全问题 加锁(synchronized) synchronized 使用方法 synchronized的其他使用方法 synchronized 重要特性(可重入的) 死锁的问题 对 2> 提出问题 对 3> 提出问题 解决死锁 对 2> 进行解答 对4> 进行解答 volatile 关键字 wait 和 notify (重要…

如何在沉浸式翻译浏览器插件中使用免费的DEEPLX和配置API接口

如何在浏览器插件沉浸式翻译中使用DEEPLX 如何配置免费的DEEPLX翻译功能如何打开PDF翻译功能如何解除翻译额度限制 如何配置免费的DEEPLX翻译功能 假设你已经在浏览器上安装了沉浸式翻译插件&#xff0c;但是不知道如何使用免费的DEEPLX功能 这里以EDGE浏览器为例&#xff0c;…

JVM从1%到99%【精选】-类加载子系统

目录 1.类的生命周期 1.加载 2.连接 3.初始化 2.类的加载器 1.类加载器的分类 2.双亲委派机制 3.面试题&#xff1a;类的双亲委派机制是什么&#xff1f; 4.打破双亲委派机制 1.类的生命周期 类加载过程&#xff1a;加载、链接&#xff08;验证、准备、解析&a…

# 从浅入深 学习 SpringCloud 微服务架构(十七)--Spring Cloud config(1)

从浅入深 学习 SpringCloud 微服务架构&#xff08;十七&#xff09;–Spring Cloud config&#xff08;1&#xff09; 一、配置中心的 概述 1、配置中心概述 对于传统的单体应用而言&#xff0c;常使用配置文件来管理所有配置&#xff0c;比如 SpringBoot 的 application.y…

解决 Content type ‘application/json;charset=UTF-8‘ not supported

文章目录 问题描述原因分析解决方案参考资料 问题描述 我项目前端采用vue-elementUi-admin框架进行开发&#xff0c;后端使用SpringBoot&#xff0c;但在前后端登录接口交互时&#xff0c;前端报了如下错误 完整报错信息如下 前端登录接口JS代码如下 export function login(…

商业数据分析--时间序列图及趋势分析

绘制时间序列图,并指出存在什么样的状态如上两图: 可见状态:从时间序列图可以看出,这些数据存在明显的季节性波动,每年的第4季度值都最高,而第2季度值最低。同时也存在一些下降的趋势。 通过引进虚拟变量,建立多元线性回归模型。答: 通过引入虚拟变量,我们可以建立如下的…

7-Zip:解锁数字世界的压缩艺术

探索数字世界&#xff0c;你需要的不仅是勇气&#xff0c;还有正确的工具。《7-Zip&#xff1a;解锁数字世界的压缩艺术》将带你深入了解7-Zip——这个开源免费的压缩工具&#xff0c;将帮助你在数字世界中更加游刃有余&#xff01; 文章目录 7-Zip 使用介绍1. 引言2. 背景介绍…