【递归、回溯及搜索】No.4---综合练习

文章目录

  • 前言
  • 一、综合练习示例:
    • [1.1 找出所有⼦集的异或总和再求和](#1.1 找出所有⼦集的异或总和再求和)
    • [1.2 全排列 II](#1.2 全排列 II)
    • [1.3 电话号码的字⺟组合](#1.3 电话号码的字⺟组合)
    • [1.4 括号⽣成](#1.4 括号⽣成)
    • [1.5 组合](#1.5 组合)
    • [1.6 ⽬标和](#1.6 ⽬标和)
    • [1.7 组合总和](#1.7 组合总和)
    • [1.8 字⺟⼤⼩写全排列](#1.8 字⺟⼤⼩写全排列)
    • [1.9 优美的排列](#1.9 优美的排列)
    • [1.10 N 皇后](#1.10 N 皇后)
    • [1.11 有效的数独](#1.11 有效的数独)
    • [1.12 解数独](#1.12 解数独)
    • [1.13 单词搜索](#1.13 单词搜索)
    • [1.14 ⻩⾦矿⼯](#1.14 ⻩⾦矿⼯)
    • [1.15 不同路径 III](#1.15 不同路径 III)

前言

👧个人主页:@小沈YO.

😚小编介绍:欢迎来到我的乱七八糟小星球🌝

📋专栏:递归、回溯及搜索

🔑本章内容:综合练习

记得 评论📝 +点赞👍 +收藏😽 +关注💞哦~


一、综合练习示例:

1.1 找出所有⼦集的异或总和再求和

  1. 题⽬链接:1863. 找出所有⼦集的异或总和再求和
  2. 题⽬描述:
  3. 解法(递归):这道题的决策图具体可以看上个专题(子集)
    算法思路:
    所有⼦集可以解释为:每个元素选择在或不在⼀个集合中(因此,⼦集有 个)。本题我们需要求出所有⼦集,将它们的异或和相加。因为异或操作满⾜交换律,所以我们可以定义⼀个变量,直接记录当前状态的异或和。使⽤递归保存当前集合的状态(异或和),选择将当前元素添加⾄当前状态与否,并依次递归数组中下⼀个元素。当递归到空元素时,表⽰所有元素都被考虑到,记录当前状态(将当前状态的异或和添加⾄答案中)。
    例如集合中的元素为 [1, 2],则它的⼦集状态选择过程如下:
cpp 复制代码
	   []
	/      \
   []      [1] //第⼀个元素选择与否
  /  \     /  \
 [] [2] [1] [1, 2] //第⼆个元素选择与否,每个状态到这⼀层时需要记录异或和
  1. 函数设计:
  • 递归函数设计:void dfs(int val, int idx, vector& nums)
  • 参数:sum(当前状态的异或和),pos(当前需要处理的元素下标,处理过程:选择将其添加⾄当前状态或不进⾏操作);
  • 返回值:⽆;
  • 函数作⽤:选择对元素进⾏添加与否处理。
  1. 递归流程:
  • 递归结束条件:当前下标与数组⻓度相等,即已经越界,表⽰已经考虑到所有元素;
    a. 将当前异或和添加⾄答案中,并返回;
  • 考虑将当前元素添加⾄当前状态,当前状态更新为与当前元素值的异或和,然后递归下⼀个元素;
  • 考虑不选择当前元素,当前状态不变,直接递归下⼀个元素;
  1. C++代码
cpp 复制代码
class Solution {
    int sum=0;
    int ret=0;
public:
    int subsetXORSum(vector<int>& nums) 
    {
        dfs(nums,0);
        return ret;
    }
    void dfs(vector<int>&nums,int pos)
    {
        if(pos==nums.size())
        {
            ret+=sum;
            return;
        }
        //选当前的数字;
        sum^=nums[pos];
        dfs(nums,pos+1);
        sum^=nums[pos];
        //不选当前的数字:
        dfs(nums,pos+1);
    }
};
--------------------------------------------------------------------------------------------------
class Solution {
    int sum=0;
    int ret=0;
public:
    int subsetXORSum(vector<int>& nums) 
    {
        dfs(nums,0);
        return ret;
    }
    void dfs(vector<int>&nums,int pos)
    {
        ret+=sum;
        for(int i=pos;i<nums.size();i++)
        {
            sum^=nums[i];
            dfs(nums,i+1);
            sum^=nums[i];
        }
    }
};

1.2 全排列 II

  1. 题⽬链接:47. 全排列 II
  2. 题⽬描述:
  3. 解法:
    算法思路:
    因为题⽬不要求返回的排列顺序,因此我们可以对初始状态排序,将所有相同的元素放在各⾃相邻的位置,⽅便之后操作。因为重复元素的存在,我们在选择元素进⾏全排列时,可能会存在重复排列,例如:[1, 2, 1],所有的 下标排列 为:
cpp 复制代码
123
132
213
231
312
321

按照以上下标进⾏排列的结果为:

cpp 复制代码
121
112
211
211
112
121

可以看到,有效排列只有三种[1, 1, 2],[1, 2, 1],[2, 1, 1],其中每个排列都出现两次。因此,我们需要对相同元素定义⼀种规则,使得其组成的排列不会形成重复的情况:

  • 我们可以将相同的元素按照排序后的下标顺序出现在排列中,通俗来讲,若元素 s 出现 x 次,则排序后的第 2 个元素 s ⼀定出现在第 1 个元素 s 后⾯,排序后的第 3 个元素 s ⼀定出现在第 2 个元素 s 后⾯,以此类推,此时的全排列⼀定不会出现重复结果。
  • 例如:a1=1,a2=1,a3=2,排列结果为 [1, 1, 2] 的情况只有⼀次,即 a1 在 a2 前⾯,因为 a2 不会出现在 a1 前⾯从⽽避免了重复排列。
  • 我们在每⼀个位置上考虑所有的可能情况并且不出现重复;
  • 注意:若当前元素的前⼀个相同元素未出现在当前状态中,则当前元素也不能直接放⼊当前状态的数组,此做法可以保证相同元素的排列顺序与排序后的相同元素的顺序相同,即避免了重复排列出现。
  • 通过深度优先搜索的⽅式,不断地枚举每个数在当前位置的可能性,并在递归结束时回溯到上⼀个状态,直到枚举完所有可能性,得到正确的结果。
  1. 函数设计
  • 递归函数设计:void backtrack(vector& nums, int idx)
  • 参数:idx(当前需要填⼊的位置);
  • 返回值:⽆;
  • 函数作⽤:查找所有合理的排列并存储在答案列表中。
  1. 递归流程如下:
  • 定义⼀个⼆维数组 vv ⽤来存放所有可能的排列,⼀个⼀维数组 path⽤来存放每个状态的排列,⼀个⼀维数组 vis 标记元素,然后从第⼀个位置开始进⾏递归;
  • 在每个递归的状态中,我们维护⼀个步数 pos,表⽰当前已经处理了⼏个数字;
  • 递归结束条件:当 pos等于 nums 数组的⻓度时,说明我们已经处理完了所有数字,将当前数组存⼊结果中;
  • 在每个递归状态中,枚举所有下标 i,若这个下标未被标记,并且在它之前的相同元素被标记过,则使⽤ nums 数组中当前下标的元素:
    a. 将 vis[i] 标记为 1;
    b. 将 nums[i] 添加⾄ path数组末尾;
    c. 对第 pos+1 个位置进⾏递归;
    d. 将 vis[i] 重新赋值为 0,并删除 path末尾元素表⽰回溯;
  • 最后,返回 vv。
  1. C++代码
cpp 复制代码
class Solution {
    vector<int> path;
    vector<vector<int>> vv;
    int vis[10]={0};
public:
    vector<vector<int>> permuteUnique(vector<int>& nums) 
    {
        sort(nums.begin(),nums.end());
        dfs(nums,0);
        return vv;
    }
    void dfs(vector<int>& nums,int pos)
    {
        if(pos==nums.size())
        {
            vv.push_back(path);
            return;
        }
        for(int i=0;i<nums.size();i++)
        {
            //if(!vis[i]&&(i==0||nums[i]!=nums[i-1]||vis[i-1]))//符合条件进入循环
            if(vis[i]||(i!=0&&nums[i]==nums[i-1]&&!vis[i-1]))continue;//不符合条件直接跳过
            else
            {
                path.push_back(nums[i]);
                vis[i]=1;
                dfs(nums,pos+1);
                path.pop_back();
                vis[i]=0;
            }
        }
    }
};

1.3 电话号码的字⺟组合

  1. 题⽬链接:17. 电话号码的字⺟组合
  2. 题⽬描述:
  3. 解法:
    算法思路:
    每个位置可选择的字符与其他位置并不冲突,因此不需要标记已经出现的字符,只需要将每个数字对应的字符依次填⼊字符串中进⾏递归,在回溯是撤销填⼊操作即可。 在递归之前我们需要定义⼀个字典 hash,记录 2~9 各⾃对应的字符。
  4. 递归函数设计:void dfs(string digits,int pos)
    参数:pos(已经处理的元素个数)
    返回值:⽆
    函数作⽤:查找所有合理的字⺟组合并存储在答案列表中。
  5. 递归函数流程如下:
  • 递归结束条件:当 pos等于 digits 的⻓度时,将 path 加⼊到 vv 中并返回;
  • 取出当前处理的数字 digits,根据 hash取出对应的字⺟列表 ;
  • 遍历字⺟列表 ,将当前字⺟加⼊到组合字符串 path 的末尾,然后递归处理下⼀个数字(传⼊ pos+ 1,表⽰处理下⼀个数字);
  • 递归处理结束后,将加⼊的字⺟从 path 的末尾删除,表⽰回溯。
  • 最终返回 vv 即可。
  1. C++代码
cpp 复制代码
class Solution {
    vector<string> vv;
    string path;
    string hash[10]={"","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
public:
    vector<string> letterCombinations(string digits) 
    {
        if(digits=="")return vv;
        dfs(digits,0);
        return vv;
    }
    void dfs(string nums,int pos)
    {
        if(path.size()==nums.size())
        {
            vv.push_back(path);
            return;
        }
        for(int i=pos;i<nums.size();i++)
        {
            string s=hash[nums[i]-'0'];
            for(int j=0;j<s.size();j++)
            {
                path+=s[j];
                dfs(nums,i+1);
                path.pop_back();
            }
        }
    }
};

------------------------------------------------------------------------------------

class Solution {
    vector<string> vv;
    string path;
    string hash[10]={"","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
public:
    vector<string> letterCombinations(string digits) 
    {
        if(digits=="")return vv;
        dfs(digits,0);
        return vv;
    }
    void dfs(string digits,int pos)
    {
        if(pos==digits.size())
        {
            vv.push_back(path);
            return;
        }
        for(auto&ch:hash[digits[pos]-'0'])
        {
            path.push_back(ch);
            dfs(digits,pos+1);
            path.pop_back();
        }
    }
};

1.4 括号⽣成

  1. 题⽬链接:22. 括号⽣成
  2. 题⽬描述:
  3. 解法:
    算法思路:
    从左往右进⾏递归,在每个位置判断放置左右括号的可能性,若此时放置左括号合理,则放置左括号继续进⾏递归,右括号同理。⼀种判断括号是否合法的⽅法:从左往右遍历,左括号的数量始终⼤于等于右括号的数量,并且左括号的总数量与右括号的总数量相等。因此我们在递归时需要进⾏以下判断:
  • 放⼊左括号时需判断此时左括号数量是否⼩于字符串总⻓度的⼀半(若左括号的数量⼤于等于字符串⻓度的⼀半时继续放置左括号,则左括号的总数量⼀定⼤于右括号的总数量);
  • 放⼊右括号时需判断此时右括号数量是否⼩于左括号数量。
  1. 递归函数设计:void dfs()
    返回值:⽆;
    函数作⽤:查找所有合理的括号序列并存储在答案列表中。
  2. 递归函数参数设置为当前状态的字符串⻓度以及当前状态的左括号数量,递归流程如下:
  • 递归结束条件:当前状态字符串⻓度与 2*n 相等,记录当前状态并返回;
  • 若此时左括号数量⼩于字符串总⻓度的⼀半,则在当前状态的字符串末尾添加左括号并继续递归,递归结束撤销添加操作;
  • 若此时右括号数量⼩于左括号数量(右括号数量可以由当前状态的字符串⻓度减去左括号数量求得),则在当前状态的字符串末尾添加右括号并递归,递归结束撤销添加操作;
  1. C++ 代码:
cpp 复制代码
class Solution {
    vector<string> vv;
    string path;
    int _n;
    int left=0,right=0;
public:
    vector<string> generateParenthesis(int n) 
    {
        _n=n;
        dfs();
        return vv;
    }
    void dfs()
    {
        if(path.size()==2*_n)
        {
            vv.push_back(path);
            return;
        }
        if(left<_n)
        {
            path+='(';
            left++;
            dfs();
            left--;
            path.pop_back();
        }
        if(right<left)
        {
            path+=')';
            right++;
            dfs();
            path.pop_back();
            right--;
        }
    }
};

1.5 组合

  1. 题⽬链接:77. 组合
  2. 题⽬描述:
  3. 解法(回溯):
    算法思路:
    题⽬要求我们从 1 到 n 中选择 k 个数的所有组合,其中不考虑顺序。也就是说,[1,2] 和 [2,1] 等价。我们需要找出所有的组合,但不能重复计算相同元素的不同顺序的组合。对于选择组合,我们需要进⾏如下流程:
  • 所有元素分别作为⾸位元素进⾏处理;
  • 在之后的位置上同理,选择所有元素分别作为当前位置元素进⾏处理;
  • 为避免计算重复组合,规定选择之后位置的元素时必须⽐前⼀个元素⼤,这样就不会有重复的组合([1,2] 和 [2,1] 中 [2,1] 不会出现)。
  1. 递归函数设计:void dfs(int n,int pos)
    参数:pos(当前需要进⾏处理的位置);
    返回值:⽆;
    函数作⽤:某个元素作为⾸位元素出现时,查找所有可能的组合。
  2. 具体实现⽅法如下:
  • 定义⼀个⼆维数组和⼀维数组。⼆维数组⽤来记录所有组合,⼀维数组⽤来记录当前状态下的组合。
  • 遍历 pos 到 n,以当前数作为组合的⾸位元素进⾏递归
  • 递归流程如下:
    a. 结束条件:当前组合中已经有 k 个元素,将当前组合存进⼆维数组并返回。
    ▪ 剪枝:如果当前位置之后的所有元素放⼊组合也不能满⾜组合中存在 k 个元素,直接返回。
    b. 从当前位置的下⼀个元素开始遍历到 n,将元素赋值到当前位置,递归下⼀个位置。
  1. C++代码
cpp 复制代码
class Solution {
    vector<vector<int>> vv;
    vector<int> path;
    int _k;
public:
    vector<vector<int>> combine(int n, int k) 
    {
        _k=k;
        dfs(n,1);
        return vv;
    }
    void dfs(int n,int pos)
    {
        if(path.size()==_k)
        {
            vv.push_back(path);
            return;
        }
        for(int i=pos;i<=n;i++)
        {
            path.push_back(i);
            dfs(n,i+1);
            path.pop_back();
        }
    }
};

1.6 ⽬标和

  1. 题⽬链接:494. ⽬标和
  2. 题⽬描述:
  3. 解法(回溯):
    算法思路:
    对于每个数,可以选择加上或减去它,依次枚举每⼀个数字,在每个数都被选择时检查得到的和是否等于⽬标值。如果等于,则记录结果。
    需要注意的是,为了优化时间复杂度,可以提前计算出数组中所有数字的和 sum,以及数组的⻓度 len。这样可以快速判断当前的和减去剩余的所有数是否已经超过了⽬标值 target ,或者当前的和加上剩下的数的和是否⼩于⽬标值 target,如果满⾜条件,则可以直接回溯。
  4. 递归流程:
  • 递归结束条件:pos与数组⻓度相等,判断当前状态的 sum 是否与⽬标值相等,若是计数加⼀;
  • 选择当前元素进⾏加操作,递归下⼀个位置,并更新参数 sum;
  • 选择当前元素进⾏减操作,递归下⼀个位置,并更新参数 sum;
    • 特别地,此问题可以转化为另⼀个问题:若所有元素初始状态均为减,选择其中⼏个元素将他们的状态修改为加,计算修改后的元素和与⽬标值相等的⽅案个数。
  • 选择其中 x 个元素进⾏修改,并且这 x 个元素的和为 y;
  • 检查使得 -sum+2*y=target(移项:y=(sum+target)/2)成⽴的⽅案个数,即选择 x 个元素和为 (sum+target)/2 的⽅案个数;
    a. 若 sum+target 为奇数,则不存在这种⽅案;
  1. 递归流程:
    a. 传⼊参数:index(当前要处理的元素下标),sum(当前状态和),nums(元素数组),aim(⽬标值:(sum+target)/2);
    b. 递归结束条件:index 与数组⻓度相等,判断当前 sum 是否与⽬标值相等,若是返回 1,否则
    返回 0;
    c. 返回 递归选择当前元素 以及 递归不选择当前元素 函数值的和。
  2. C++代码
cpp 复制代码
class Solution {
    int cnt=0;
    int sum=0;
public:
    int findTargetSumWays(vector<int>& nums, int target) 
    {
        dfs(nums,target,0);
        return cnt;
    }
    void dfs(vector<int>&nums,int t,int pos)
    {
        if(pos==nums.size())
        {
            if(sum==t)cnt++;
            return;
        }
        sum+=nums[pos];
        dfs(nums,t,pos+1);
        sum-=nums[pos];

        sum+=(-nums[pos]);
        dfs(nums,t,pos+1);
        sum-=(-nums[pos]);
    }
};

1.7 组合总和

  1. 题⽬链接:39. 组合总和
  2. 题⽬描述:
  3. 解法:
    算法思路:
    candidates 的所有元素 互不相同,因此我们在递归状态时只需要对每个元素进⾏如下判断:
  • 跳过,对下⼀个元素进⾏判断;
  • 将其添加⾄当前状态中,我们在选择添加当前元素时,之后仍可以继续选择当前元素(可以重复选择同⼀元素)。
    • 因此,我们在选择当前元素并向下传递下标时,应该直接传递当前元素下标。
  1. 递归函数设计:void dfs(vector& candidates,int sum,int pos)
    参数:sum,pos
    返回值:⽆;
    函数作⽤:向下传递两个状态(跳过或者选择当前元素),找出所有组合使得元素和为⽬标值。
  2. 递归函数流程如下:
  • 结束条件:
    a. 当前需要处理的元素下标越界;
    b. 当前状态的元素和已经与⽬标值相同;
  • 跳过当前元素,当前状态不变,对下⼀个元素进⾏处理;
  • 选择将当前元素添加⾄当前状态,并保留状态继续对当前元素进⾏处理,递归结束时撤销添加操作。
  1. C++代码
cpp 复制代码
class Solution {
    vector<vector<int>> vv;
    vector<int> path;
    int target;
public:
    vector<vector<int>> combinationSum(vector<int>& candidates, int _target) 
    {
        target=_target;
        dfs(candidates,0,0);
        return vv;
    }
    void dfs(vector<int>& candidates,int sum,int pos)
    {
        if(sum>=target)
        {
            if(sum==target)vv.push_back(path);
            return;
        }
        for(int i=pos;i<candidates.size();i++)
        {
            path.push_back(candidates[i]);
            dfs(candidates,sum+candidates[i],i);
            path.pop_back();
        }
    }
};



class Solution {
    vector<vector<int>> vv;
    vector<int> path;
    int target;
public:
    vector<vector<int>> combinationSum(vector<int>& candidates, int _target) 
    {
        target=_target;
        dfs(candidates,0,0);
        return vv;
    }
    void dfs(vector<int>& candidates,int sum,int pos)
    {
        if(sum>=target)
        {
            if(sum==target)vv.push_back(path);
            return;
        }
        if(pos==candidates.size())return;
        for(int k=0;k*candidates[pos]<=target;k++)
        {
            if(k)path.push_back(candidates[pos]);
            dfs(candidates,sum+candidates[pos]*k,pos+1);
        }
        for(int k=1;k*candidates[pos]<=target;k++)
        {
            path.pop_back();
        }
    }
};

1.8 字⺟⼤⼩写全排列

  1. 题⽬链接:784. 字⺟⼤⼩写全排列
  2. 题⽬描述:
  3. 解法:
    算法思路:
    只需要对英⽂字⺟进⾏处理,处理每个元素时存在三种情况:
  • 不进⾏处理;
  • 若当前字⺟是英⽂字⺟并且是⼤写,将其修改为⼩写;
  • 若当前字⺟是英⽂字⺟并且是⼩写,将其修改为⼤写。
  1. 递归函数设计:void dfs(string& s,int pos)
    参数:pos(当前需要处理的位置);
    返回值:⽆;
    函数作⽤:查找所有可能的字符串集合,并将其记录在答案列表。
  2. 从前往后按序进⾏递归,递归流程如下:
  • 递归结束条件:当前需要处理的元素下标越界,表⽰处理完毕,记录当前状态并返回;
  • 对当前元素不进⾏任何处理,直接递归下⼀位元素;
  • 判断当前元素是否为⼩写字⺟,若是,将其修改为⼤写字⺟并递归下⼀个元素,递归结束时撤销修改操作;
  • 判断当前元素是否为⼤写字⺟,若是,将其修改为⼩写字⺟并递归下⼀个元素,递归结束时撤销修改操作;
  1. C++代码
cpp 复制代码
class Solution {
    vector<string> vv;
    string path;
public:
    vector<string> letterCasePermutation(string s) 
    {
        dfs(s,0);
        return vv;
    }
    void dfs(string& s,int pos)
    {
        if(pos==s.size())
        {
            vv.push_back(path);
            return;
        }
        if(s[pos]>='a'&&s[pos]<='z')
        {
            path+=s[pos];
            dfs(s,pos+1);
            path.pop_back();

            path+=(s[pos]-32);
            dfs(s,pos+1);
            path.pop_back();
        }
        else if(s[pos]>='A'&&s[pos]<='Z')
        {
            path+=s[pos];
            dfs(s,pos+1);
            path.pop_back();

            path+=(s[pos]+32);
            dfs(s,pos+1);
            path.pop_back();
        }
        else 
        {
            path+=s[pos];
            dfs(s,pos+1);
            path.pop_back();
        }
    }
};

1.9 优美的排列

  1. 题⽬链接:526. 优美的排列
  2. 题⽬描述:
  3. 解法:
    算法思路:
    我们需要在每⼀个位置上考虑所有的可能情况并且不能出现重复。通过深度优先搜索的⽅式,不断地枚举每个数在当前位置的可能性,并回溯到上⼀个状态,直到枚举完所有可能性,得到正确的结果。我们需要定义⼀个变量 ⽤来记录所有可能的排列数量,⼀个⼀维数组 visited 标记元素,然后从第⼀个位置开始进⾏递归;
  4. 递归函数设计: void dfs(int n,int pos)
    参数:pos(当前需要处理的位置);
    返回值:⽆;
    函数作⽤:在当前位置填⼊⼀个合理的数字,查找所有满⾜条件的排列。
  5. 递归流程如下:
  • 递归结束条件:当 pos 等于 n+1 时,说明已经处理完了所有数字,将当前数组存⼊结果中;
  • 在每个递归状态中,枚举所有下标 i,若这个下标未被标记,并且满⾜题⽬条件之⼀:
    a. 将 visx] 标记为 1;
    b. 对第 pos+1 个位置进⾏递归;
    c. 将 vis[x] 重新赋值为 0,表⽰回溯;
  1. C++代码
cpp 复制代码
class Solution {
    int cnt=0;
    int vis[20]={0};
public:
    int countArrangement(int n) 
    {
        dfs(n,1);
        return cnt;
    }
    void dfs(int n,int pos)
    {
        if(pos==n+1)
        {
            cnt++;
            return;
        }
        for(int i=1;i<=n;i++)
        {
            if((i%pos==0||pos%i==0)&&!vis[i])
            {
                vis[i]=1;
                dfs(n,pos+1);
                vis[i]=0;
            }
        }
    }
};

1.10 N 皇后

  1. 题⽬链接:51. N 皇后
  2. 题⽬描述:
  3. 解法:
    算法思路:
    ⾸先,我们在第⼀⾏放置第⼀个皇后,然后遍历棋盘的第⼆⾏,在可⾏的位置放置第⼆个皇后,然后再遍历第三⾏,在可⾏的位置放置第三个皇后,以此类推,直到放置了 n 个皇后为⽌。
    我们需要⽤⼀个数组来记录每⼀⾏放置的皇后的列数。在每⼀⾏中,我们尝试放置⼀个皇后,并检查是否会和前⾯已经放置的皇后冲突。如果没有冲突,我们就继续递归地放置下⼀⾏的皇后,直到所有的皇后都放置完毕,然后把这个⽅案记录下来。
    在检查皇后是否冲突时,我们可以⽤⼀个数组来记录每⼀列是否已经放置了皇后,并检查当前要放置的皇后是否会和已经放置的皇后冲突。对于对⻆线,我们可以⽤两个数组来记录从左上⻆到右下⻆的每⼀条对⻆线上是否已经放置了皇后,以及从右上⻆到左下⻆的每⼀条对⻆线上是否已经放置了皇后。
    • 对于对⻆线是否冲突的判断可以通过以下流程解决:
  • 从左上到右下:相同对⻆线的⾏列之差相同;
  • 从右上到左下:相同对⻆线的⾏列之和相同。
    因此,我们需要创建⽤于存储解决⽅案的⼆维字符串数组 vv,⽤于存储每个皇后的位置的⼀维整数数组 path,以及⽤于记录每⼀列和对⻆线上是否已经有皇后的布尔型数组 checkcol、 checkdig1和 checkdig2。
  1. 递归函数设计: void dfs(int n,int row)
    参数:row(当前需要处理的⾏数);
    返回值:⽆;
    函数作⽤:在当前⾏放⼊⼀个不发⽣冲突的皇后,查找所有可⾏的⽅案使得放置 n 个皇后后不发⽣冲突。
  2. 递归函数流程如下:
  • 结束条件:如果 row 等于 n ,则表⽰已经找到⼀组解决⽅案,此时将每个皇后的位置存储到字符串数组 board 中,并将 board 存储到 vv 数组中,然后返回;
  • 枚举当前⾏的每⼀列,判断该列、两个对⻆线上是否已经有皇后:
    a. 如果有皇后,则继续枚举下⼀列;
    b. 否则,在该位置放置皇后,并将 checkcol、 checkdig1 和 checkdig2 对应的位置设为 true ,表⽰该列和对⻆线上已经有皇后:
    i. 递归调⽤ dfs 函数,搜索下⼀⾏的皇后位置。如果该⽅案递归结束,则在回溯时需要将 checkcol、 checkdig1 和 checkdig2 对应的位置设为 false ,然后继续枚举下⼀列;
  1. C++代码
cpp 复制代码
class Solution {
    vector<vector<string>> vv;
    vector<string> path;
    int checkcol[10],checkdig1[20],checkdig2[20];
public:
    vector<vector<string>> solveNQueens(int n) 
    {
        path.resize(n);
        for(int i=0;i<n;i++)
        path[i].append(n,'.');
        dfs(n,0);
        return vv;
    }

    void dfs(int n,int row)
    {
        if(row==n)
        {
            vv.push_back(path);
            return;
        }
        for(int col=0;col<n;col++)
        {
            if(!checkcol[col]&&!checkdig1[row-col+n]&&!checkdig2[col+row])
            {
                path[row][col]='Q';
                checkcol[col]=checkdig1[row-col+n]=checkdig2[col+row]=1;
                dfs(n,row+1);
                path[row][col]='.';
                checkcol[col]=checkdig1[row-col+n]=checkdig2[col+row]=0;
            }
        }
    }
};

1.11 有效的数独

  1. 题⽬链接:36. 有效的数独
  2. 题⽬描述:
  3. 解法:
    算法思路:
    创建三个数组标记⾏、列以及 3*3 ⼩⽅格中是否出现 1~9 之间的数字即可。
  4. C++代码
cpp 复制代码
class Solution {
    int row[9][10]={0},col[9][10]={0};
    int grid[3][3][10]={0};
public:
    bool isValidSudoku(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]!='.')
                {
                    int tmp=board[i][j]-'0';
                    if(!row[i][tmp]&&!col[j][tmp]&&!grid[i/3][j/3][tmp])
                    {
                        row[i][tmp]=col[j][tmp]=grid[i/3][j/3][tmp]=1;
                    }
                    else return false;
                }
            }
        }
        return true;
    }
};

1.12 解数独

  1. 题⽬链接:37. 解数独
  2. 题⽬描述:
  3. 解法:
    算法思路:
    为了存储每个位置的元素,我们需要定义⼀个⼆维数组。⾸先,我们记录所有已知的数据,然后遍历所有需要处理的位置,并遍历数字 1~9。对于每个位置,我们检查该数字是否可以存放在该位置,同时检查⾏、列和九宫格是否唯⼀。
    我们可以使⽤⼀个⼆维数组来记录每个数字在每⼀⾏中是否出现,⼀个⼆维数组来记录每个数字在每⼀列中是否出现。对于九宫格,我们可以以⾏和列除以 3 得到的商作为九宫格的坐标,并使⽤⼀个三维数组来记录每个数字在每⼀个九宫格中是否出现。在检查是否存在冲突时,只需检查⾏、列和九宫格⾥对应的数字是否已被标记。如果数字⾄少有⼀个位置(⾏、列、九宫格)被标记,则存在冲突,因此不能在该位置放置当前数字。
    • 特别地,在本题中,我们需要直接修改给出的数组,因此在找到⼀种可⾏的⽅法时,应该停⽌递归,以防⽌正确的⽅法被覆盖。
  4. 初始化定义:
  • 定义⾏、列、九宫格标记数组以及找到可⾏⽅法的标记变量,将它们初始化为 false。
  • 定义⼀个数组来存储每个需要处理的位置。
  • 将题⽬给出的所有元素的⾏、列以及九宫格坐标标记为 true。
  • 将所有需要处理的位置存⼊数组。
  1. 递归函数设计: bool dfs(vector<vector>& board)
    返回值:⽆;
    函数作⽤:在当前坐标填⼊合适数字,查找数独答案。
  2. 递归流程如下:
  • 结束条件:已经处理完所有需要处理的元素。如果找到了可⾏的解决⽅案,则将标记变量更新为 true 并返回。
  • 获取当前需要处理的元素的⾏列值。
  • 遍历数字 1~9。如果当前数字可以填⼊当前位置,并且标记变量未被赋值为 true,则将当前位置的⾏、列以及九宫格坐标标记为 true,将当前数字赋值给 board 数组中的相应位置元素,然后对下⼀个位置进⾏递归。
  • 递归结束时,撤回标记
  1. C++代码
cpp 复制代码
class Solution {
    int checkrow[9][10]={0},checkcol[9][10]={0};
    int checkgrid[3][3][10]={0};
public:
    void solveSudoku(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]!='.')
                {
                    int tmp=board[i][j]-'0';
                    checkrow[i][tmp]=checkcol[j][tmp]=checkgrid[i/3][j/3][tmp]=1;
                }
            }
        }
        dfs(board);
    }
    bool dfs(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]=='.')
                {
                    for(int k=1;k<=9;k++)
                    {
                        if(!checkrow[i][k]&&!checkcol[j][k]&&!checkgrid[i/3][j/3][k])
                        {
                            board[i][j]=k+'0';
                            checkrow[i][k]=checkcol[j][k]=checkgrid[i/3][j/3][k]=1;
                            if(dfs(board))return true;//先调用dfs,dfs返回才进行if判断
                            board[i][j]='.';
                            checkrow[i][k]=checkcol[j][k]=checkgrid[i/3][j/3][k]=0;
                        }
                    }     
                   return false;      
                }
            }
        }
        return true;
    }
};

1.13 单词搜索

  1. 题⽬链接:79. 单词搜索
  2. 题⽬描述:
  3. 解法:
    算法思路:
    我们需要假设每个位置的元素作为第⼀个字⺟,然后向相邻的四个⽅向进⾏递归,并且不能出现重复使⽤同⼀个位置的元素。通过深度优先搜索的⽅式,不断地枚举相邻元素作为下⼀个字⺟出现的可能性,并在递归结束时回溯,直到枚举完所有可能性,得到正确的结果。
  4. 递归函数设计:bool dfs(vector<vector>& board,int a,int b,string word,int pos)
    参数:a(当前需要进⾏处理的元素横坐标),b(当前需要进⾏处理的元素横坐标),pos(当前已经处理的元素个数),word(当前的字符串状态);
    返回值:当前坐标元素作为字符串中下标 pos 的元素出现是否可以找到成⽴的字符串。
    函数作⽤:判断当前坐标的元素作为字符串中下标 pos的元素出现时,向四个⽅向传递,查找是否存
    在路径结果与字符串相同。
  5. 递归函数流程:
  • 遍历每个位置,标记当前位置并将当前位置的字⺟作为⾸字⺟进⾏递归,并且在回溯时撤回标记。
  • 在每个递归的状态中,我们维护⼀个步数 pos,表⽰当前已经处理了⼏个字⺟。
    ◦ 若当前位置的字⺟与字符串中的第 pos个字⺟不相等,则返回 false。
    ◦ 若当前 pos的值与字符串⻓度相等,表⽰存在⼀种路径使得 word 成⽴,返回 true。
  • 对当前位置的上下左右四个相邻位置进⾏递归,若递归结果为 true,则返回 true。
  • 若相邻的四个位置的递归结果都为 false,则返回 false。
    • 特别地,如果使⽤将当前遍历到的字符赋值为空格,并在回溯时恢复为原来的字⺟的⽅法,则在递归时不会重复遍历当前元素,可达到不使⽤标记数组的⽬的。
  1. C++代码
cpp 复制代码
class Solution {
    int dx[4]={0,0,1,-1};
    int dy[4]={1,-1,0,0};
    int vis[7][7]={0};
    int pos=0;
public:
    bool exist(vector<vector<char>>& board, string word) 
    {
        for(int i=0;i<board.size();i++)
        {
            for(int j=0;j<board[0].size();j++)
            {
                char tmp=word[pos];
                if(board[i][j]==tmp&&!vis[i][j])
                {
                    vis[i][j]=1;
                    if(dfs(board,i,j,word,pos+1))return true;
                    vis[i][j]=0;
                }
            }
        }
        return false;
    }
    bool dfs(vector<vector<char>>& board,int a,int b,string word,int pos)
    {
        if(pos==word.size())return true;
        for(int k=0;k<4;k++)
        {
            int x=a+dx[k],y=b+dy[k];
            if(x>=0&&x<board.size()&&y>=0&&y<board[0].size()&&!vis[x][y]&&board[x][y]==word[pos])
            {
                vis[x][y]=1;
                if(dfs(board,x,y,word,pos+1))return true;
                vis[x][y]=0;
            }
        }
        return false;
    }
};

1.14 ⻩⾦矿⼯

  1. 题⽬链接:1219. ⻩⾦矿⼯
  2. 题⽬描述:
  3. 解法:
    算法思路:
    枚举矩阵中所有的位置当成起点,来⼀次深度优先遍历,统计出所有情况下能收集到的⻩⾦数的最⼤值即可。
  4. C++代码
cpp 复制代码
class Solution {
    int dx[4]={0,0,1,-1};
    int dy[4]={1,-1,0,0};
    int vis[110][110]={0};
    int ret=0,ans=0;
    int n,m;
public:
    int getMaximumGold(vector<vector<int>>& grid) 
    {
        n=grid.size();m=grid[0].size();
        for(int i=0;i<n;i++)
        {
            for(int j=0;j<m;j++)
            {
                if(grid[i][j]!=0)
                {
                    ret+=grid[i][j];
                    vis[i][j]=1;
                    dfs(grid,i,j);
                    ret-=grid[i][j];
                    vis[i][j]=0;
                }
            }
        }
        return ans;
    }
    void dfs(vector<vector<int>>& grid,int a,int b)
    {
        ans=max(ans,ret);
        for(int k=0;k<4;k++)
        {
            int x=a+dx[k],y=b+dy[k];
            if(x>=0&&x<n&&y>=0&&y<m&&grid[x][y]!=0&&!vis[x][y])
            {
                ret+=grid[x][y];
                vis[x][y]=1;
                dfs(grid,x,y);
                ret-=grid[x][y];
                vis[x][y]=0;
            }
            //这里不能这样写
            //假设您从网格的一个边缘开始,并在第一次尝试向右移动时越界。
            //如果您在这个点更新 ans,那么您就没有机会探索从当前点向下或向左移动的可能性,这些路径可能包含更多的金币。
            // else if(x<0||x>=n||y<0||y>=m)
            // {
            //     ans=max(ans,ret);
            //     return;
            // }
        }
    }
};

1.15 不同路径 III

  1. 题⽬链接:980. 不同路径 III
  2. 题⽬描述:
  3. 解法:
    算法思路:
    对于四个⽅向,我们可以定义⼀个⼆维数组 dx,dy,⼤⼩为 4 ,每⼀维存储四个⽅向的坐标偏移量(详⻅代码)。题⽬要求到达⽬标位置时所有⽆障碍⽅格都存在路径中,我们可以定义⼀个变量记录 pos 当前状态中已⾛过的⽆障碍⽅格个数,则当我们⾛到⽬标地点时只需要判断 pos 是否为 cnt 即可。在移动时需要判断是否越界。
  4. 递归函数设计: void dfs(vector<vector>& grid,int a,int b,int pos)
    参数:a,b(当前需要处理元素的坐标)pos(当前前进⽆障碍⽅格个数);
    返回值:⽆;
    函数作⽤:判断当前位置的四个⽅向是否可以添加⾄当前状态,查找在满⾜条件下从起始⽅格到结束⽅格的不同路径的数⽬。
  5. 递归流程如下:
  • 递归结束条件:当前位置的元素值为 2,若此时可⾛的位置数量 pos 的值为 cnt,则 ret的值加⼀;
  • 遍历四个⽅向,若移动后未越界,⽆障碍并且未被标记,则标记当前位置,并递归移动后的位置,在回溯时撤销标记操作。
  1. C++代码
cpp 复制代码
class Solution {
    int dx[4]={0,0,-1,1};
    int dy[4]={1,-1,0,0};
    int vis[25][25]={0};
    int n,m;
    int cnt=2,ret=0;
    int bx,by;
public:
    int uniquePathsIII(vector<vector<int>>& grid) 
    {
        n=grid.size(),m=grid[0].size();
        for(int i=0;i<n;i++)
        {
            for(int j=0;j<m;j++)
            {
                if(grid[i][j]==0)cnt++;
                if(grid[i][j]==1)
                {
                    bx=i;
                    by=j;
                }
            }
        }
        vis[bx][by]=1;
        dfs(grid,bx,by,1);
        return ret;
    }
    void dfs(vector<vector<int>>& grid,int a,int b,int pos)
    {
        if(grid[a][b]==2)
        {
            if(pos==cnt)
                ret++;
            return;
        }
        for(int k=0;k<4;k++)
        {
            int x=a+dx[k],y=b+dy[k];
            if(x>=0&&x<n&&y>=0&&y<m&&grid[x][y]!=-1&&!vis[x][y])
            {
                vis[x][y]=1;
                dfs(grid,x,y,pos+1);
                vis[x][y]=0;
            }
        }
    }
};
相关推荐
zyhomepage10 分钟前
科技的成就(六十四)
开发语言·人工智能·科技·算法·内容运营
想做白天梦29 分钟前
多级反馈队列
java·windows·算法
潇雷30 分钟前
算法Day12|226-翻转二叉树;101-对称二叉树;104-二叉树最大深度;111-二叉树最小深度
java·算法·leetcode
爱编程— 的小李1 小时前
开关灯问题(c语言)
c语言·算法·1024程序员节
韭菜盖饭1 小时前
LeetCode每日一题3211---生成不含相邻零的二进制字符串
数据结构·算法·leetcode
极客代码2 小时前
C/C++ 随机数生成方法
c语言·开发语言·c++·算法
甜甜向上呀2 小时前
【数据结构】快速排序(三种实现方式)
算法·排序算法
旋转的油纸伞2 小时前
大模型,多模态大模型面试【LoRA,分类,动静态数据类型,DDPM,ControlNet,IP-Adapter, Stable Diffusion】
算法·leetcode·面试·职场和发展·散列表
XUE_DING_E2 小时前
Educational Codeforces Round 171
算法
Patience to do2 小时前
Android Studio项目(算法计算器)
android·算法·android studio