算法学习(十六)—— 综合练习
目录
1863. 找出所有子集的异或总和再求和
47. 全排列 Ⅱ
17. 电话号码的字母组合
22. 括号生成
77. 组合
494. 目标和
39. 组合总和
784. 字母大小写全排列
526. 优美的排列
51. N皇后
36. 有效的数独
37. 解数独
79. 单词搜索
1219. 黄金矿工
980. 不同路径 Ⅲ
1863. 找出所有子集的异或总和再求和
1863. 找出所有子集的异或总和再求和 - 力扣(LeetCode)
题目意思和我们之前的求子集很相似,只是这里多了几步,就是要把每个子集的结果都异或一遍,然后把所有值相加,下面来分析下这道题:
- 还是老样子,步骤为:①先画决策树 ②再设计代码:全局变量,dfs函数体,回溯,剪枝以及递归出口
- 先画决策树,如下图:
- 首先是全局变量,int sum,作用是保存所有子集异或后相加的结果;int path,记录的是当前这一层两个数异或的结果,就是不再存1和2这两个数,直接存它们异或的结果
- 然后就是dfs函数头的设计,和之前是一样的,dfs(nums, pos),pos 表示本次递归要从哪个数开始枚举
- 然后就是回溯,这个我们可以利用一下异或运算的“消消乐”,就是1异或2,然后再异或一个2,之后的结果仍然是1,2的两次异或相互抵消,所以回溯的时候再异或一下即可
- 最后就是递归出口,就是每次进入递归的时候,都要 sum += path
class Solution {
public:int path;int sum;int subsetXORSum(vector<int>& nums) {dfs(nums, 0);return sum; }void dfs(vector<int>& nums, int pos){sum += path;for(int i = pos; i < nums.size(); i++){path ^= nums[i];dfs(nums, i + 1);path ^= nums[i]; //恢复现场}}
};
47. 全排列 Ⅱ
47. 全排列 II - 力扣(LeetCode)
全排列我们在上一篇文章中已经讲过大体思路了:算法学习(十五)—— 回溯算法-CSDN博客
这道题只是多了“有重复元素”这一点,大逻辑是一样的,所以这里我们重点搞一下剪枝,其它的就不再赘述,下面来分析下这道题:
- 这道题题目给我们的数组中是有重复元素的,所以全排列后的结果可能会有重复,所以题目就是要求我们干掉重复的全排列,返回不重复的结果;说到“干掉”,最先想到的就是“剪枝”操作,所以这道题的重点就是在“剪枝”上
- 先画决策树,如下图:
所以在这道题中,我们的剪枝只会发生在两种情况:
- 同一个节点的所有分支中,相同的元素只能选择一次
- 同一个数只能用一次,用check数组来搞
然后就是如何用代码实现剪枝,其实和前面一样,就是加几个if判断即可,对于剪枝实现,我们有下面两种思考方式:
- 只关心“不合法”的分支:就是在递归之前坐下判断,如果这个分支不合法,就直接返回不再递归。当 check[i] == true || nums[i] == nums[i - 1] && check[i - 1] = false 时,就是该数已经使用过或者和前面一个数重复了,所以不合法,至于后面的与操作,以上面决策树的 "1 1 __ __" 为例,这个结果里的两个 1 其实是在不同层次的,第一个1是上一层的,第二个1是本层的,所以两个层次的不做比较;还有一个问题,当i == 0时,再 i - 1就越界了,所以还要加一个条件,最后的判断条件就是:check[i] == true || (i != 0 && nums[i] == nums[i - 1] && check[i - 1] == false)
- 只关心“合法”的分支:就是当选择条件成立时再进入递归。check[i] == false,首先是必须要这个位置的值未被使用,才有机会进入递归;i == 0 || nums[i] != nums[i - 1] || check[i - 1] == true,就是两个数不一样,或者两个数一样但是处于不同层时,可以进递归;最后的判断条件就是check[i] == false && (i == 0 || nums[i] != nums[i - 1] || check[i - 1] == true
细节处理:
- 如果题目给我们的是[1, 2, 1, 3, 1] 这样的数组,那么当我们递归到第二个1时,还要去前面看第一个1有没有用过,就很麻烦,所以,我们可以在最开始把数组排序一下
class Solution {
public:vector<vector<int>> vv;vector<int> path;bool check[9];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(check[i] == true || (i != 0 && nums[i] == nums[i - 1] && check[i - 1] == false)) continue;path.push_back(nums[i]);check[i] = true; //表明该位置已被使用dfs(nums, pos + 1);path.pop_back(); //恢复现场check[i] = false;//思路二:只考虑合法分支// if(check[i] == false && (i == 0 || nums[i] != nums[i - 1] || check[i - 1] == true))// {// path.push_back(nums[i]);// check[i] = true; //表明该位置已被使用// dfs(nums, pos + 1);// path.pop_back(); //恢复现场// check[i] = false;// }}}
};
17. 电话号码的字母组合
17. 电话号码的字母组合 - 力扣(LeetCode)
这道题很简单,看示例1并参照图片很容易解决,但是,当给我们的数字只有两个的时候,我们可以用两层for循环解决,但是,当给我们的数字是9位数甚至更大时,再用循环嵌套就不太实际了,下面我们来分析下这道题:
- 这道题还是用递归来搞的,先画决策树,如下图:
- 然后是全局变量的设计,首先搞一个字符串数组,用来解决数组与字符串的映射关系
- 然后是一个path,保存每个路径的值;最后就是ret,用来保存最终结果
- 然后就是 dfs函数体的设计:dfs(digits, pos),就是根据题目给的数字翻译成字符串,然后pos代表字符串的具体某个位置
- 最后就是回溯过程,就是把path的最后一个字符干掉即可
- 最后就是递归出口,就是当递归到叶子节点时,保存path的结果即可
class Solution {
public:vector<string> ret;string path;string hash[10] = {"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};vector<string> letterCombinations(string digits) {if(digits.size() == 0) return ret;dfs(digits, 0); //从0开始翻译到最后一个位置return ret;}void dfs(const string& s, int pos){if(pos == s.size()){ret.push_back(path);return;}//把该字符串所有情况翻译一下,加到path后面即可for(auto e : hash[s[pos] - '0']) //把字符转为数字,并找到对应的字符串{path.push_back(e);dfs(s, pos + 1);path.pop_back();}}
};
22. 括号生成
22. 括号生成 - 力扣(LeetCode)
题目很简单,就是给我们一个数字,要我们找到相同数量的有效括号的集合并返回,下面来分析下这道题:
- 先来分析下什么是“有效括号的组合”:①左括号数量 = 右括号数量 ②从头开始的任意一个字串,左括号的数量 >= 右括号的数量;只要上面有一个点点不合法,那么就不是“有效括号的组合”
- 我们要列举出括号,假设 n = 3,那么只需要6个空,然后往这6个空中暴力枚举符合要求的括号即可,先画决策树,如下图:
- 首先是全局变量,left表示左括号的数量,right表示右括号数量,n表示括号的对数;然后就是path表示递归的路径
- 然后就是dfs,不需要参数,因为参数已经在全局里设置好了;然后就是dfs干的事,其实也很简单,当左括号合法时,把左括号加到path里,右括号同理,这样就可以了
- 回溯时,一样的,干掉path的最后一个
- 递归出口,就是当right = n也就是右括号为n的时候,说明左括号和右括号全搞好了,就是到了叶子节点,直接返回即可
class Solution {
public:int left, right;vector<string> ret;string path;vector<string> generateParenthesis(int n) {dfs(n);return ret;}void dfs(int n){if(right == n) {ret.push_back(path);return;}if(left < n) //先加左括号{path += "(";left++;dfs(n);path.pop_back(); //恢复现场left--;}if(right < left) //再加右括号{path += ")";right++;dfs(n);path.pop_back();right--;}}
};
77. 组合
77. 组合 - 力扣(LeetCode)
这种排列类型的题都是很相似的,所以我们的解题思路和决策树也都很相似,下面来分析下这道题:
- 先画决策树,如下图:
- 枚举的时候,只需要从该数的下一个数开始,比如 1 往后枚举是从 2 开始,2 往后枚举是从 3 开始,所以并不需要全局变量来辅助剪枝,只要控制下递归参数即可:dfs(pos),表示接下来的操作从哪个位置开始
- 然后需要 path 记录路径,然后回溯时,把最后的数干掉即可,当碰到叶子节点时就可返回结果
class Solution {
public:vector<vector<int>> ret;vector<int> path;int N, K;vector<vector<int>> combine(int n, int k) {N = n, K = k;dfs(1);return ret;}void dfs(int pos){if(path.size() == K){ret.push_back(path);return;}//从起始位置往后枚举for(int i = pos; i <= N; i++){path.push_back(i);dfs(i + 1); //从添加的这个数的下一个位置开始path.pop_back();}}
};
494. 目标和
494. 目标和 - 力扣(LeetCode)
这道题其实我们小学时就见过,就是给你一个正数数组,给里面每个数都添加加号或减号并能使式子的结果 == target,要我们返回可以通过这种方法构造运算结果 == target 的不同表达式的数目,下面来分析下这道题:
- 先画决策树,如下图:
- 所以这道题其实很简单,所以我们来搞两种形式的代码:①path 是全局变量时候的代码 ②path 作为参数时的代码
- 代表性题目就是我们之前搞得子集那道题:算法学习(十五)—— 回溯算法-CSDN博客
代码一,就是当path为全局变量的时候:
class Solution
{int path, ret;int a;
public:int findTargetSumWays(vector<int>& nums, int target) {a = target;dfs(nums, 0);return ret;}void dfs(vector<int>& nums, int pos){if(pos == nums.size()){//当数组的数已经枚举完并且path已经等于目标值,说明已经找到一种结果if(path == a) ret++; return;}//加法path += nums[pos];dfs(nums, pos + 1);path -= nums[pos]; //恢复现场就是和前面反着来//减法path -= nums[pos];dfs(nums, pos + 1);path += nums[pos]; }
};
代码二,就是path作为参数传递,思路和“子集‘的解法二类似:
class Solution
{int ret, a;
public:int findTargetSumWays(vector<int>& nums, int target) {a = target;dfs(nums, 0, 0);return ret;}void dfs(vector<int>& nums, int pos, int path) //path表示当前节点已经前面枚举之后相加或相减的结果{if(pos == nums.size()){//当数组的数已经枚举完并且path已经等于目标值,说明已经找到一种结果if(path == a) ret++; return;}//加法dfs(nums, pos + 1, path + nums[pos]);//减法dfs(nums, pos + 1, path - nums[pos]);}
};
可以看到能是能通过,但是时间复杂度都很高,这是因为这道题的最优解其实不是爆搜,而是动态规划,我们后面学习动态规划时还会遇到
但是作为学习者,这道题我们需要掌握的就是当path作为全局和参数时,代码大体是如何写的
39. 组合总和
39. 组合总和 - 力扣(LeetCode)
组合总和有四道题,但是第一道是最经典的。题目给我们一个不重复的整数数组,同一个数字可以无限选取,然后题目要求我们从数组中选一些数加起来 == target,要我们找出能选多少不同的组合,下面来分析下这道题:
解法一:
- 先画决策树,如下图:
解法一代码:
class Solution {
public:int a;vector<vector<int>> ret;vector<int> path;vector<vector<int>> combinationSum(vector<int>& candidates, int target) {a = target;dfs(candidates, 0, 0);return ret;}void dfs(vector<int>& nums, int pos, int sum){if(sum == a){ret.push_back(path);return;}if(sum > a || pos == nums.size()) return;for(int i = pos; i < nums.size(); i++){path.push_back(nums[i]);dfs(nums, i ,sum + nums[i]); //由于可以选重复的数,所以第二个参数是i,不是i + 1path.pop_back();}}
};
解法二:
解法一是根据某一个位置来展开枚举的,解法二咱们就根据数来展开
- 假设还是 [2, 3, 5] target = 8,这个例子,我们先看2,我们可以一次枚举0个2,1个2,2个2,3个2,4个2,5个2,得到的结果就是0,2,4,6,8,10,由于10已经大于target,所以枚举的2的个数到5时停止枚举,把结果为8的情况统计一下
- 然后我们固定0个2的结果0,再次枚举3,也是枚举0个3,1个3,2个3,3个3,结果为0,3,6,9,9大于8了,停止枚举,由于没有结果为8,不统计
- 然后我再次固定0个3的结果,往后再次枚举4的个数,就这样再次一直枚举,和上面相同的步骤
- 然后再次返回到0个2的结果哪里,然后再次 固定 1个2 的结果2再次以相同的步骤再次枚举3和4,这就是解法二
- 解法二的回溯需要等一个数全部枚举完后再回溯,这样可以减少很多不必要的加减运算
解法二代码:
class Solution {
public:int a;vector<vector<int>> ret;vector<int> path;vector<vector<int>> combinationSum(vector<int>& candidates, int target) {a = target;dfs(candidates, 0, 0);return ret;}void dfs(vector<int>& nums, int pos, int sum){if(sum == a){ret.push_back(path);return;}if(sum > a || pos == nums.size()) return;//解法二for(int i = 0; i * nums[pos] + sum <= a; i++) //加上sum可以减少枚举次数{if(i) path.push_back(nums[pos]);dfs(nums, pos + 1 , sum + i * nums[pos]);}//恢复现场for(int i = 1; i * nums[pos] + sum <= a; i++){path.pop_back();}}
};
784. 字母大小写全排列
784. 字母大小写全排列 - 力扣(LeetCode)
题目很简单,看示例就能看懂,下面来分析下这道题:
- 先画决策树,如下图:
class Solution {
public:string path;vector<string> ret;vector<string> letterCasePermutation(string s) {dfs(s, 0);return ret;}void dfs(string s, int pos){if(path.size() == s.length()){ret.push_back(path);return;}char ch = s[pos];//改变if((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')){char tmp = ch;if(ch >= 'a' && ch <= 'z') tmp -= 32;else tmp += 32;path.push_back(tmp);dfs(s, pos + 1);path.pop_back();}//不改变path.push_back(s[pos]);dfs(s, pos + 1);path.pop_back();}
};
526. 优美的排列
526. 优美的排列 - 力扣(LeetCode)
- 这道题有两道工序要搞,一个就是先找到全排列,然后判断这个排列是否满足“优美”条件
- 先画决策树,如下图:
class Solution
{
public:bool check[16];int ret;int countArrangement(int n) {dfs(1, n);return ret;}void dfs(int pos, int n){if(pos == n + 1){ret++;return;}for(int i = 1; i <= n; i++){if(!check[i] && (pos % i == 0 || i % pos == 0)){check[i] = true;dfs(pos + 1, n);check[i] = false; //恢复现场}}}
};
51. N皇后
51. N 皇后 - 力扣(LeetCode)
国际象棋中皇后的攻击机制如上面所说,n皇后问题,就是要我们在一个 n * n 大小的棋盘内放皇后棋子,返回所有符合 n皇后 机制的棋盘,如示例图和示例1,下面来分析下这道题:
- 这道题的难点就是 “剪枝 + 代码能力”,这道题重点就是要搞一个策略,来判断一个位置能不能放皇后,有两种策略:
- 策略一:就是每一个格子来考虑能不能放皇后,判断完成后再去下一个格子搞,这样遍历完所有的格子即可,虽然这种方法时间复杂度是常数,但是代码非常不好写很麻烦,不推荐用
- 策略二:我们每一次我考虑一行的皇后应该放在哪里,如下图:
所以,现在最主要的问题就是:如何“剪枝” ?
- 剪枝的目的就是考虑当前这个位置能否放上皇后
- 方法一:无脑循环,就是假设当前位置有皇后,然后对各个方向直线上判断有没有其它的皇后,这种可以解决,但是时间复杂度太高,需要4个循环,为Log(4n * 2的n次方),但是这道题能过;以这个思路我们还可以优化一下,以上图为例,我们每一行只放一个皇后,所以可以少一次循环
- 方法二:类似哈希表的策略,这个在五子棋这种类似棋盘的问题都可以用;我们可以搞三个数组,bool row[n] 表示行,bool col[n] 表示列,如下图:
- 然后行列是这样搞的,那么搞下斜对角线的数组,这个需要一点数学知识,如下图:
- 然后负对角线也是相同的道理,这里不再赘述,公式为 y + x = b,就是在数组的 y + x 的位置判断是 true 还是 false 即可
class Solution
{
public:int N;vector<vector<string>> ret;vector<string> path;bool checkCol[20], checkDig1[20], checkDig2[20];vector<vector<string>> solveNQueens(int n) {N = n;//先初始化path,相当于初始化棋盘path.resize(N);for(int i = 0; i < n; i++){path[i].append(N, '.');}dfs(0); //仅需传入一个参数,表示要从哪一行开始去枚举return ret;}void dfs(int row){if(row == N) //我一行一行去填,填到越界了,说明已经把 path 里面合法的皇后填完了{ret.push_back(path);return;}for(int col = 0; col < N; col++) //尝试在这一行放皇后{//当列,正对角线,负对角线都没有皇后时,就可以放,由于是以行枚举的,所以不必判断行if(!checkCol[col] && !checkDig1[row - col + N] && !checkDig2[row + col]){path[row][col] = 'Q';checkCol[col] = true;checkDig1[row - col + N] = true;checkDig2[row + col] = true;dfs(row + 1);//恢复现场path[row][col] = '.';checkCol[col] = false;checkDig1[row - col + N] = false;checkDig2[row + col] = false;}}}
};
36. 有效的数独
36. 有效的数独 - 力扣(LeetCode)
这道题其实不是回溯题,可以说是哈希表那一类题;数独这道题最重要的其实也是剪枝的操作,而只要把这道题搞懂了,再搞下面的解数独那道题就会很简单,下面来分析下这道题:
- 数独就是:每一行,每一列以及每个3x3的小方格都不能出现重复的数,题目要求就是给我们一个已经填了部分数的数独,要我们判断这是不是一个有效的数独
- 我们先搞定行跟列的要求,可以学习“N皇后”的经验,搞两个 bool 类型的数组 row[9][10],以这个数组为例,row[2][4] 就是表示“第二行是否出现了4这个数组”,为true就是出现了,否则为false;列同理。就是 col[9][10],所以 col[7][9] 就表示第7列是否存在9这个数
- 然后就是搞定每一个 3x3 的小方格了,如果要暴力的话,就是用两个for循环暴力去找;但是我们也可以搞一个哈希表,把每个 3x3 的小方格看成一个整体,那么一个数独就是有9个小方格,所以数组就是 bool grid[3][3][10],前面两个数就是表示方格的位置,而第三个数就是表示这个方格有没有这个数
- 而且这个方格的位置都很好搞,要想知道一个 1x1 的小小方格在哪个 3x3 的小方格里,只要把这个 1x1 的方格的位置都 “/3” 即可,就能快速知道它在哪一个 3x3 方格里
- 上面的方法就是典型的用“空间换时间”的方法
class Solution
{
public:bool row[9][10];bool col[9][10];bool grid[3][3][10];bool isValidSudoku(vector<vector<char>>& board){for(int i = 0; i < 9; i++){for(int j = 0; j < 9; j++) //遍历每一个时仅需判断每一个数字即可{if(board[i][j] != '.') //不是点的时候就是数字{int num = board[i][j] - '0';//判断这个数字是否是有效的if(row[i][num] || col[j][num] || grid[i / 3][j / 3][num]) return false;elserow[i][num] = col[j][num] = grid[i / 3][j / 3][num] = true;}}}return true;}
};
37. 解数独
37. 解数独 - 力扣(LeetCode)
数独的规则这里不再赘述,而有了上一道题的经验之后,这道题虽然标的是困难,但是难度每那么大了,下面来分析下这道题:
- 依旧是依次遍历这个二维数组,遇到一个空格就往里面填数,依次填 1-9 的数,然后决策树就有9个分支(碍于篇幅问题,这里就不画决策树了哈),然后就是判断填在这里的9个数合不合法,判断方式也很简单,就是上一题的那3个数组,当不合法时,直接返回不再往下递归
class Solution
{
public:bool row[9][10];bool col[9][10];bool grid[3][3][10];void solveSudoku(vector<vector<char>>& board) {for(int i = 0; i < 9; i++){for(int j = 0; j < 9; j++){if(board[i][j] != '.'){int num = board[i][j] - '0';row[i][num] = col[j][num] = grid[i / 3][j / 3][num] = true;}}}dfs(board);}bool dfs(vector<vector<char>>& board){for(int i = 0; i < 9; i++){for(int j = 0; j < 9; j++){if(board[i][j] == '.'){//开始填数for(int num = 1; num <= 9; num++){if(!row[i][num] && !col[j][num] && !grid[i / 3][j / 3][num]){board[i][j] = num + '0';row[i][num] = col[j][num] = grid[i / 3][j / 3][num] = true;if(dfs(board) == true) return true;//填下一个数时一定要恢复现场board[i][j] = '.';row[i][num] = col[j][num] = grid[i / 3][j / 3][num] = false;}}//当1-9全部填完之后依旧没有返回true,说明这种情况不行return false;}}}return true;}
};
79. 单词搜索
79. 单词搜索 - 力扣(LeetCode)
给我们一个矩阵,然后矩阵有一些字母,然后再给我们一个单词,要我们设计一个算法判断该单词的字母在矩阵中是否连续存在,并且和单词的顺序一样,如示例,下面来分析下这道题:
- 如下图:
- 根据上面的图片中的坐标,我们就可以开始画决策树了,如下图:
- 仔细看就可以发现,我们只需要对上面树做一次深度优先遍历即可
- 然后就是函数体的设计,就是给我一个位置,我要去匹配周围节点的值,所以函数体头设计就是bool dfs(board, i, j, s, pos),表示我要在[i, j]这个位置上下左右去匹配s的pos这个位置的字符存不存在,然后就是返回值,失败了就去试另一条路
- 然后就是回溯和剪枝,回溯就是这条路匹配失败往回走的那一段,然后剪枝同理
细节:二维矩阵搜索中,经常要注意的细节
- 不能走重复的路。假设题目给的数组是 A A C D,然后s = A A A A,如果按照上面的逻辑,然后到第二个A时,是上下左右去找的,那么就会找到前面的A,这样就会出问题,所以我们要规避这样的情况
- 解决方法①:就是建立一个和原矩阵同等规模大小的矩阵visit[][],这个矩阵是bool类型的,当一个位置用过时,把visit的对应位置设置成true表示这个位置已经用过了,所以每次往周围遍历时,要先判断下这个数组
- 但是很多人认为上面的方法可能会浪费空间,所以我们有了另一个解决方法
- 解决方法②:直接修改原矩阵的值。就是匹配了一个字母之后,先记录这个字母方便回溯,然后把这个字母修改成 ' . ',这种方法在笔试中可以用,但是在面试中得问下面试官,就是原矩阵允不允许修改
- 但是这道题可以用方法②,但是如果某些题的搜索过程很复杂的话,恢复现场也就会很麻烦,这种情况下还是老老实实用方法①吧
class Solution
{
public:bool vis[7][7];int m, n; bool exist(vector<vector<char>>& board, string word) {m = board.size(), n = board[0].size();for(int i = 0;i < m; i++){for(int j = 0; j < n; j++){if(board[i][j] == word[0]) //先找第一个字母{vis[i][j] = true;if(dfs(board, i, j, word, 1) == true) return true; //从[i][j]位置上下左右匹配word[1]位置的字符vis[i][j] = false;}}}return false;}int dx[4] = {0, 0, 1, -1}; //搞两个数组,数组中存的是x轴和y轴的偏移量(方法二)int dy[4] = {-1, 1, 0, 0};bool dfs(vector<vector<char>>& board, int i, int j, string& word, int pos){if(pos == word.size()) return true; //当要匹配的位置和word大小一样了,那么就表示已经找到了一种情况//写法一:上下左右去找,可以搞4个函数,不推荐//写法二:用向量的方式,定义上下左右四个位置,这个方法是我们在搜索问题中经常用的,推荐for(int k = 0; k < 4; k++){int x = i + dx[k], y = j + dy[k]; //循环会执行四次if((x >= 0 && y >= 0) && (x < m && y < n) && (!vis[x][y] && board[x][y] == word[pos])) //当坐标没有越界,vis矩阵中该元素还没使用过并且能找到和word对应字母匹配{vis[x][y] = true;if(dfs(board, x, y, word, pos + 1) == true) return true;vis[x][y] = false; //恢复现场}}return false;}
};
1219. 黄金矿工
1219. 黄金矿工 - 力扣(LeetCode)
这个题目和我们玩的那个黄金矿工还是有区别的。
这道题和上一道单词搜索很像,题目给我们一个二维数组,然后我们任意值不为0的位置出发,可以往上下左右四个方向遍历,每一次遍历将该位置的数相加,然后要我们找到相加的数的最大值,要求是每次遍历的位置必须是连续的,不能遍历值为0的位置,每个位置只能遍历一次,下面来分析下这道题:
- 这道题最基本的遍历路线和上面单词搜索很相似的,如下图:
- 其余的步骤比如说函数设计细节处理啥的,和上面单词搜索是一样的,这里不再赘述
class Solution
{bool vis[16][16];int dx[4] = {0, 0, 1, -1};int dy[4] = {1, -1, 0, 0};int m, n;int path, ret;
public:int getMaximumGold(vector<vector<int>>& grid) {m = grid.size(), n = grid[0].size();for(int i = 0; i < m; i++){for(int j = 0; j < n; j++){if(grid[i][j]){vis[i][j] = true;path = grid[i][j];dfs(grid, i, j);vis[i][j] = false;}}}return ret;}void dfs(vector<vector<int>>& g, int i, int j){ret = max(ret, path);for(int k = 0; k < 4; k++){int x = i + dx[k], y = j + dy[k];if((x >= 0 && y >= 0) && (x < m && y < n) && (!vis[x][y] && g[x][y] != 0)){vis[x][y] = true;path += g[x][y];dfs(g, x, y);path -= g[x][y];vis[x][y] = false;}}}
};
980. 不同路径 Ⅲ
980. 不同路径 III - 力扣(LeetCode)
这道题的前面两个题的“不同路径Ⅰ”和 “不同路径Ⅱ”推荐用动态规划来解决,但是这道题不建议用dp,这道题建议用爆搜
题目给我们一个网格,网格里有很多数字,不同的数字有不同的效果,要我们返回从起始位置到结束位置的所有路径的数量,但是有一个要求,就是必须要把所有的0的位置都走一次,而且每个0只能走一次,下面来分析下这道题:
class Solution
{bool vis[21][21];int dx[4] = {1, -1, 0, 0};int dy[4] = {0, 0, 1, -1};int m, n, step;int beginX, beginY;int ret;
public:int uniquePathsIII(vector<vector<int>>& grid) {m = grid.size(), n = grid[0].size();for(int i = 0; i < m; i++){for(int j = 0; j < n; j++){if(grid[i][j] == 0) {step++;}else if(grid[i][j] == 1){beginX = i;beginY = j;step += 2; //加上开始和结束的步数}}}vis[beginX][beginY] = true;dfs(grid, beginX, beginY, 1); //从起始位置开始遍历return ret;}void dfs(vector<vector<int>>& g, int i, int j, int count){if(g[i][j] == 2){if(count == step) ret++;return;}for(int k = 0; k < 4; k++){int x = i + dx[k], y = j + dy[k];if((x >= 0 && y >= 0) && (x < m && y < n) && (!vis[x][y] && g[x][y] != -1)){vis[x][y] = true;dfs(g, x, y, count + 1);vis[x][y] = false;}}}
};
相关文章:

算法学习(十六)—— 综合练习
目录 1863. 找出所有子集的异或总和再求和 47. 全排列 Ⅱ 17. 电话号码的字母组合 22. 括号生成 77. 组合 494. 目标和 39. 组合总和 784. 字母大小写全排列 526. 优美的排列 51. N皇后 36. 有效的数独 37. 解数独 79. 单词搜索 1219. 黄金矿工 980. 不同路径 Ⅲ…...

kratos源码分析:熔断器
文章目录 为什么需要熔断Google sre弹性熔断算法kratos Breaker源码分析公共接口sre实现上报请求结果判定是否熔断 为什么需要熔断 一般来说,当服务器过载(overload)时,需要给client返回服务过载的报错 但是拒接请求也有成本&…...

CTF_1
CTF_Show 萌新赛 1.签到题 <?php if(isset($_GET[url])){system("curl https://".$_GET[url].".ctf.show"); }else{show_source(__FILE__); }?> 和 AI 一起分析 1.if(isset($_GET[url]))检查GET请求中是否存在名为url的参数。 curl 2.curl…...

【系统】Mac crontab 无法退出编辑模式问题
【系统】Mac crontab 无法退出编辑模式问题 背景一、问题回答1.定位原因:2.确认编辑器类型3.确保编辑器进入正确3.1 确认是否有crontab调度任务3.2 进入编辑器并确保编辑器正常3.3 保存操作 4.确认crontab任务存在5.确保脚本的可执行性和正确性 二、后续 背景 之前…...

K8s中 statefulset 和deployment的区别
在 Kubernetes 中,StatefulSet 和 Deployment 是两种管理 Pod 的控制器,它们的主要区别在于 状态管理 和 Pod 的标识。以下是详细对比: 1. 功能定位 Deployment 用途:用于 无状态应用 的部署,例如 Web 服务、API 服务…...

springboot中的AOP以及面向切面编程思想
快速入门体验AOP aop中相关概念 实现导入依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> 新建aop文件夹,里面声明XXXAspect类 @Aspect // 声…...

降低Mobx技术债问题-React前端数据流方案调研整理
我们现在主要是使用Mobx,但是Mobx的易于上手和灵活度也带来了很多预期以外的问题,随着项目的增长我们的代码技术债变得愈加沉重,不同的模块杂糅一起、单一store无限膨胀。 为此我们的调研是希望能找到一个更好的state配置、数据流的约定方案。…...

RabbitMQ消息可靠性保证机制7--可靠性分析-rabbitmq_tracing插件
rabbitmq_tracing插件 rabbitmq_tracing插件相当于Firehose的GUI版本,它同样能跟踪RabbitMQ中消息的注入流出情况。rabbitmq_tracing插件同样会对流入流出的消息进行封装,然后将封装后的消息日志存入相应的trace文件中。 # 开启插件 rabbitmq-plugins …...

SQL进阶技巧:如何求解直接线上最多的点数?
目录 0 问题描述 1 数据准备 2 问题分析 3 求解优化 步骤一:构建 “斜率键” 并统计点的数量(核心步骤) 步骤二:找出最多的点数(最终结果) 0 问题描述 “平面上最多的点数” 问题通常是指在一个二维平面中给定了若干个点的坐标(例如以 (x,y) 的形式表示),要求找…...

【老白学 Java】泛型应用 - 卡拉 OK(四)
泛型应用 - 卡拉 OK(四) 文章来源:《Head First Java》修炼感悟。 上文说到,解决了按歌名排序的问题后,老白立刻想到了按歌手名字排序的问题。 老白决定趁热打铁,尝试着实现自定义排序方式。 Collections…...

android studio更改应用图片,和应用名字。
更改应用图标,和名字 先打开AndroidManifest.xml文件。 更改图片文件名字( 右键-->构建-->重命名(R))...

SQL Server 表值函数使用示例
在 SQL Server 中,表值函数(Table-Valued Functions, TVFs)是一种用户定义函数,它可以返回一个表。表值函数有两种类型:内联表值函数(Inline Table-Valued Function)和多语句表值函数(Multi-Statement Table-Valued Function)。下面分别介绍这两种类型的表值函数及其使…...

SpringBoot项目的创建方式
目录 1.通过idea创建SpringBoot项目 2.在idea中通过aliyun创建SpringBoot 3.通过spring官网下载再用idea打开 5.通过mavenjava项目改为springboot项目 6.测试springboot 第二种方法使用的是idea2021版本,其余方法使用idea2017版本 1.通过idea创建SpringBoot项…...

微服务设计(第2版)读书笔记
微服务概述 什么是微服务? 答:微服务(microservice)是基于业务领域建模的,可独立发布的服务。它会把业务内聚的功能封装起来,并通过网络供其他服务访问。将这样的服务组合起来构建出更复杂的系统。 微服务…...

idea无法识别文件,如何把floder文件恢复成model
前景: 昨天,我在之前的A1214模块包下新增了一个demo类,然后又新建了一个A1216模块,写了算法题,后面打算用git提交,发现之前的A1214模块下的demo类和新建的模块源文件都已经被追踪了,都是绿色的&…...

vscode的keil assistant 中搜索不到全局变量
搜不到 但是在包含的文件中输入 ../../../,就是全局搜索的结果 我的文件结构是:\Desktop\LVGL文件系统移植(lvgl8.3)\Projects\MDK-ARM 盲猜是keil assistant 当前文件夹打开的时候是进入到了MDK-ARM文件夹层次&…...

html+css网页设计 美食 餐饮杰12个页面
htmlcss网页设计 美食 餐饮杰12个页面 网页作品代码简单,可使用任意HTML辑软件(如:Dreamweaver、HBuilder、Vscode 、Sublime 、Webstorm、Text 、Notepad 等任意html编辑软件进行运行及修改编辑等操作)。 获取源码 1ÿ…...

重撸设计模式--代理模式
文章目录 定义UML图代理模式主要有以下几种常见类型:代理模式涉及的主要角色有:C 代码示例 定义 代理模式(Proxy Pattern)属于结构型设计模式,它为其他对象提供一种代理以控制对这个对象的访问。 通过引入代理对象&am…...

Redis性能调优:深入剖析变慢原因及应对策略
如果观察到,这个实例的运行延迟是正常 Redis 基准性能的 2 倍以上,即可认为这个 Redis 实例确实变慢了。 1.如何查看实例的运行延迟 (1)redis-cli -h 127.0.0.1 -p 6379 --intrinsic-latency 60 执行该命令,就可以测…...

Python联合Halcon的详细教程
以下是一份Python联合Halcon的详细教程: 一、简介 Halcon是一款由德国MVTec公司开发的高级机器视觉软件,提供了一套强大的图像处理库,包括特征检测、识别、测量等功能。Python联合Halcon是一种将Python编程语言与计算机视觉库Halcon集成的应…...

raid 状态查看 storcli64
场景 当磁盘报错的时候使用该命令排查 fdisk -l /dev/sdb fdisk: cannot open /dev/sdb: Input/output error进一步使用 smartctl 排查 smartctl -a /dev/sdb 输出 smartctl 7.1 2019-12-30 r5022 [x86_64-linux-5.4.0-144-generic] (local build) Copyright (C) 2002-19, B…...

时间管理系统|Java|SSM|JSP|
【技术栈】 1⃣️:架构: B/S、MVC 2⃣️:系统环境:Windowsh/Mac 3⃣️:开发环境:IDEA、JDK1.8、Maven、Mysql5.7 4⃣️:技术栈:Java、Mysql、SSM、Mybatis-Plus、JSP、jquery,html 5⃣️数据库可…...

使用Docker启用MySQL8.0.11
目录 一、Docker减小镜像大小的方式 1、基础镜像选择 2、减少镜像层数 3、清理无用文件和缓存 4、优化文件复制(COPY和ADD指令) 二、Docker镜像多阶段构建 1、什么是dockers镜像多阶段构建 1.1 概念介绍 1.2 构建过程和优势 2、怎样在Dockerfil…...

Qt之修改窗口标题、图标以及自定义标题栏(九)
Qt开发 系列文章 - titles-icons-titlebars(九) 目录 前言 一、修改标题 二、添加图标 三、更换标题栏 1.效果演示 2.创建标题栏类 3.定义相关函数 4.使用标题栏类 总结 前言 在我们利用Qt设计软件时,经常需要修改窗口标题、更改软…...

每天40分玩转Django:Django测试
Django测试 一、今日学习内容概述 学习模块重要程度主要内容测试基础⭐⭐⭐⭐⭐TestCase、断言方法模型测试⭐⭐⭐⭐⭐模型方法、数据验证视图测试⭐⭐⭐⭐请求处理、响应验证表单测试⭐⭐⭐⭐表单验证、数据处理覆盖率测试⭐⭐⭐⭐coverage配置、报告生成 二、测试基础示例…...

JS子页面调用父页面函数,监听刷新事件
目录 1.子页面调用父页面的函数 2.监听刷新事件 1.子页面调用父页面的方法 我们先来说说什么是子页面,在我这里子页面就是域名一样,然后使用iframe引入的页面就是我所说的子页面,为什么需要用到这个功能,是为了实现跨页面交互与…...

Element@2.15.14-tree checkStrictly 状态实现父项联动子项,实现节点自定义编辑、新增、删除功能
背景:现在有一个新需求,需要借助树结构来实现词库的分类管理,树的节点是不同的分类,不同的分类可以有自己的词库,所以父子节点是互不影响的;同样为了选择的方便性,提出了新需求,选择…...

详细介绍如何使用rapidjson读取json文件
本文主要详细介绍如何使用rapidjson库来实现.json文件的读取,分为相关基础介绍、结合简单示例进行基础介绍、结合复杂示例进行详细的函数实现介绍等三部分。 一、相关基础 1、Json文件中的{} 和 [] 在 JSON 文件中,{} 和 [] 分别表示不同的数据结构&…...

【Qt】显示类控件:QLabel、QLCDNumber、QProgressBar、QCalendarWidget
目录 QLabel QFrame 例子: textFormat pixmap、scaledContents alignment wordWrap、indent、margin buddy QLCDNumber 例子: QTimer QProgressBar 例子: QCalendarWidget 例子: QLabel 标签控件,用来显示…...

设计模式-访问者设计模式
介绍 访问者模式(Visitor),表示一个作用于某对象结构中的各元素的操作,它使你可以在不改变个元素的类的前提下定义作用于这些元素的新操作。 问题:在一个机构里面有两种员工,1.Teacher 2.Engineer 员…...