当前位置: 首页 > news >正文

专题十一_递归_回溯_剪枝_综合练习_算法专题详细总结

目录

1. 找出所有⼦集的异或总和再求和(easy)

解析:

方法一:

解法二:

总结:

2. 全排列 Ⅱ(medium)

 解析:

解法一:只关心“不合法”的分支

解法二:只关心“合法”的分支

总结:

3. 电话号码的字⺟组合(medium)

解析:

 1.然后就开始考虑函数头:

2.考虑函数体:

3.出口条件:

总结:

4. 括号⽣成(medium)

解析:

1.函数头:画出决策树

2.函数体:函数体就是来判断当选择'(' 或者 ‘)’ 的边界条件:

3.出口条件

总结:

5. 组合(medium)

解析:

1,函数头:

 2.函数体:

3.出口条件:

总结:

6. ⽬标和(medium)

解析:

解法一:设置全局变量

解法二:优化,使用局部变量,不用恢复现场

总结:

7. 组合总和(medium)

解析:

函数头:这里要递归遍历所有的子集,从第0层传入

函数体:

出口条件:

总结:

8.组合总和II

解析:

总结:

9.组合总和III

解析:

总结:

10.字母大小写全排列

解析:

总结:

11. 优美的排列(medium)

解析:

画决策树

出口条件:

函数头:

函数体:

总结:

从这里开始就要进入二维数组的递归回溯+剪枝了:

12. N 皇后(hard)

解析:

画决策树,很重要很重要!!!​编辑

全局变量:

函数头:设置n*n大小的棋盘传入dfs,i来设置当前是第几行也就是层级

函数体:

 出口条件:只要记录层级i到达n层就说明已经可以放了n个皇后在棋盘上,然后这个时候只需要将棋盘添加到ret内即可。

总结:

13. 有效的数独(medium)

 解析:

画图!画图!画图!​编辑

总结:

14. 解数独(hard)

决策树!!!

解析:

总结:

从这题开始就要进入矩阵搜索的板块了!:有点洪水灌溉的意思了;

15. 单词搜索(medium)

解析:

这题切入点就是在二维矩阵里找到跟word完全相同的字符串,要保证它每个字符都相连,那么我们就应该在主函数内去寻找word[0],然后不断遍历所有的word[0],直到有一个能返回true,否者最后就返回false。

这里就需要定义dx,dy数组,用向量的方式来方便我们进行前后左右进行查找

然后定义x,y分别就是当前位置的前后左右的值的下标,就开始判断这个下标是否会越界,是否是满足条件的字符,是否是被访问过等一系列问题。 

总结:

16. ⻩⾦矿⼯(medium)

解析:

写多了,也就是属于自己的模板题了,要学会多总结。

总结:

17. 不同路径 Ⅲ(hard)

解析:

出口条件:

在就是函数体:

总结:

看到这里终于结束了,为了写这篇文章的总结,真是花了4天时间,每天写一点,每天写一点,因为学校有课的原因平时还要继续学校C++,Linux,HTLM5,CSS3等等一大堆不同的语法内容,可能不能做到太频繁的更新,但是我绝对在保证质量的前提下不停的写博客,绝对不会断更,最后关于暴搜,深搜,回溯小总结:

下期介绍:floodfill(洪水灌溉算法) 算法简介


这一章是递归回溯算法专题的综合练习,一起有十几个题目,已经足够能够完成对递归回溯的清晰认识了,废话不多说,直接上例题:

1. 找出所有⼦集的异或总和再求和(easy)

题目意思很简单,就是求出所有数组的子集,然后进行异或运算。

解析:

方法一:

我第一次写的时候,就是想到很无脑的办法,把所有子集全部存入数组里,然后,最后再把数组里面的所有子集进行^异或运算进行相加。

那么依然是要画决策树:

我想这着第一步就是要进行全局变量的构建:dp[][] ,one[] 

递归函数体dfs() 那么在每次进入dfs()的时候都要进行填入dp[],这样才能遍历到每一个子集。
并且要用i来记录是第k层,每次传入都要传入i+1层,这样才能让函数结束,不然就会陷入死循环

函数头dfs(nums,i);

class Solution {
public:int ret = 0;
vector<vector<int>> dp;
vector<int> one;void dfs(vector<int>& nums,int k)
{dp.push_back(one);for (int i = k; i < nums.size(); i++){one.push_back(nums[i]);dfs(nums,i+1);one.pop_back();}
}int subsetXORSum(vector<int>& nums) {dfs(nums,0);for (int i = 0; i < dp.size(); i++){for (int j = 0; j < dp[i].size(); j++){cout << dp[i][j] << " ";}cout << endl;}for (int i = 0; i < dp.size(); i++){if (dp[i].size() == 0) ret += 0;else if (dp[i].size() == 1) ret += dp[i][0];else{int num = dp[i][0];for (int j = 1; j < dp[i].size(); j++){num ^= dp[i][j];}ret += num;}}cout << ret << endl;return ret;
}
};

解法二:

解法一真的有点过于冗余,虽然思想都是一样的就是求出所有的子集,但是明明可以不用浪费数组空间,只需要用变量path来求出每一个子集的异或结果,然后相加到sum中即可。

其中path都只需要在for循环内进行^=异或运算,包括恢复现场都可以利用^异或运算的消消乐原理。

class Solution {
public:int sum=0,path=0;int subsetXORSum(vector<int>& nums) {dfs(nums,0);return sum;}void dfs(vector<int>& nums,int k){sum+=path;for(int i=k;i<nums.size();i++){path^=nums[i];dfs(nums,i+1);path^=nums[i];}}
};

总结:

这一题相对来说还是十分简单的,就是上一个专题的最后一题求子集,只要递归函数体能写对,那怎么写这题都能过。

2. 全排列 Ⅱ(medium)

题意很简单就是对全排列后的所有数组添加到ret后去掉所有重复的数组。

 解析:

在这题里面剪枝策略十分重要,因为要关心去掉所有重复的元素构成的相同子集,那么就有两种剪枝策略:

1.只关心“不合法”的分支

2.只关心“合法”的分支

那么在接下来的两种剪枝讨论里需要考虑的是nums[i-1]==nums[i] 这件事,那么就说明数组可能出现[1,2,1,1] 也可能出先[1,1,1,2]两种情况,那么我们就要最开始就要对数组进行排序,这样才能讲相同数字产生的相同效果全部剪掉。

sort(nums.begin(),nums.end());

解法一:只关心“不合法”的分支

那么如果能看到这里,关于全排列的递归实现不用多解释,这题全排列思路跟上一个专题的全排列一模一样,唯一不同的就是剪枝策略。

那么我们看上面的图,考虑到剪枝只考虑“不合法”的情况,就是说明在这种不合法的时候就跳过这个条件,防止进入下一层。

1.不合法,第一步就是说明check[i]==true,证明这个数字已经在上面的某一层被用过了,不能再重复使用。

2. 或者是再选择一个元素后,前面存在相同的元素,那么就要考虑前面这个相同的元素跟我的关系,来确定我是否能被使用。如nums[i-1]==nums[i]此时前一个元素等于我此时的元素,并且前一个元素跟我属于同一层,仍然没有被使用过,就证明出现了很多相同的元素,产生了相同的效果,这样就要把此时的数字给剪掉,跳过当前的元素,依次类推,知道后面不满足这个不合法的条件。再就是为了判断数组是否越界,就要考虑了下标i不能等于0的情况否则nums[i-1]会越界。

class Solution {
public:vector<vector<int>> ret;bool check[8];vector<int> path;vector<vector<int>> permuteUnique(vector<int>& nums) {sort(nums.begin(),nums.end());dfs(nums);return ret; }void dfs(vector<int>& nums){if(path.size()==nums.size()){ret.push_back(path);return;}for(int i=0;i<nums.size();i++){if(check[i]||(i!=0&&nums[i]==nums[i-1]&&check[i-1])) continue;else{path.push_back(nums[i]);check[i]=true;dfs(nums);path.pop_back();check[i]=false;}}}
};

解法二:只关心“合法”的分支

那么只关心合法的分支就是要保证再满足合法的条件下才能进入添加数字到path数组内;

那么考虑合法的条件:

1.当check[i]==false 说明当前数字没有被使用过,可以添加到数组path内,但是这不能作为唯一的标准。

2.并且包括如果i==0 说明当前元素是nums第一个元素,不会出现越界和与后面元素相等冲突的情况,可以直接添加后进入下一层。
nums[i]!=nums[i-1] 说明前一个元素和我当前的元素并不相等,我当前的元素就跟i=0一个性质,能够直接加入到path内。
如果前面条件都不满足了,就说明一定nums[i]==nums[i-1] 一定成立,这是一个隐含条件,因为再上一个已经判断过了。就证明上一个元素跟我当前的元素是相等的。那么想要再这种条件下进入下一层,就必须要让上一个元素nums[i-1]再上面几层就已经被使用过,不会对我产生影响,就要保证check[i-1]==true;

class Solution {
public:vector<vector<int>> ret;bool check[8];vector<int> path;vector<vector<int>> permuteUnique(vector<int>& nums) {sort(nums.begin(),nums.end());dfs(nums);return ret; }void dfs(vector<int>& nums){if(path.size()==nums.size()){ret.push_back(path);return;}for(int i=0;i<nums.size();i++){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);path.pop_back();check[i]=false;}}}
};

总结:

全排列II跟全排列I一样,思路是一模一样,就只是再剪枝的策略上有所不同,只需要画清楚决策树,就可以完美的解决剪枝的策略。

3. 电话号码的字⺟组合(medium)

题目意思挺简单的,就是给你一个字符串数字,然后针对每个数字的按键的字符串中的字符进行组合,求出所有情况的子集。

解析:

一眼就知道,这题肯定用hash表存起来,你可以选自数组当哈希,也可以创建哈希,都是一样的。

 string hash[10]={"","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};

 1.然后就开始考虑函数头:

因为要知道每一层的数字,就要传入字符串digits,还要知道当前是第几层,这样会避免死循环,传入层数k

dfs(digits,k);

2.考虑函数体:

此时由于记录数字的字符串digits我们就要一层for()来访问数字中hash所记录的字符串,然后再通过hash里面的字符串来访问字符,就又是一层for(),此时要单独拿string s来记录该字符串,后面就是最硬的规则,添加字符到path() ,然后进行递归添加,回来后恢复现场,进行删除。

这里注意:

s.erase() 的话会全部删除所有字符,除非往里面添加迭代器,指定删除。

这里就可以用s.pop_back()来指定删除。

3.出口条件:

就当字符串path长度等于叶子节点的时候,就是digits添加完所有可以添加的字符后就可以进行push_back()到ret内了。

class Solution {
public:vector<string> ret;unordered_map<int,string> hash;string path;vector<string> letterCombinations(string digits) {if(digits=="") return ret;hash[2]="abc",hash[3]="def",hash[4]="ghi",hash[5]="jkl",hash[6]="mno",hash[7]="pqrs",hash[8]="tuv",hash[9]="wxyz";dfs(digits,0);return ret;}void dfs(string digits,int k){if(path.size()==digits.size()){ret.push_back(path);return;}for(int i=k;i<digits.size();i++){for(int j=0;j<hash[digits[i]-'0'].size();j++){string s=hash[digits[i]-'0'];path+=s[j];dfs(digits,i+1);path.pop_back();}}}
};

总结:

依然是很常规的递归回溯算法,只要画好决策树,确实没有什么很难很难理解的。

4. 括号⽣成(medium)

题目意思还是比较简单,就是给一个数字n,要求将n对括号进行合法摆放,然后进行添加。

解析:

依然是简单的递归回溯问题:

1.函数头:画出决策树

那么就是从开始就是考虑同一个问题,是选 '(' 还是选择 ')' ,那么带着这个相同的子问题,设计函数头,n是定义的函数头,要他来判断函数结束的位置,  k就是定义我现在递归到了第几层,记录层数。

2.函数体:函数体就是来判断当选择'(' 或者 ‘)’ 的边界条件:

1).选择'(' ,要考虑的相对来说就比较少,只需要考虑递归到最深的深度后,'('个数不要超过n个即可,那么此时要用变量left来记录'('的个数,添加到path字符串中。

if(left < n)

2).选择')' 要看考虑如果第一个是')'怎么办的情况,或者')'比'('个数多怎么办,这些都是非法的问题。那么这两个问题都可以归结到一个问题上,只要左括号个数大于右括号,那么现在的右括号就可以进行添加 ,用right来记录右括号的个数

if(left > right)

3.出口条件

出口条件还是比较简单,子需要到达第k==n*2层 就说明path已经添加了所有的括号,直接ret.push_back()即可。 

class Solution {
public:vector<string> ret; string path;int left=0,right=0;vector<string> generateParenthesis(int n) {dfs(n,0);return ret;}void dfs(int n,int k){if(k==n*2){ret.push_back(path);return;}cout<<path<<endl;//选(if(left<n){path+='(';left++;dfs(n,k+1);path.pop_back();left--;}//选)if(left>right){right++;path+=')';dfs(n,k+1);right--;path.pop_back();}}
};

总结:

这题还是比较简单的,只是简单的递归回溯加剪枝,减去那些不必要进入的层级,只要加一个判断条件即可。

5. 组合(medium)

题目意思很简单,就是【1-n】,然后有数字k,保证不含相同子集的数字个数为k。

解析:

这题就是求子集问题,真的很简单,前面已经练习很多遍了。

1,函数头:

因为要记录我当前递归的层数m 和 当前可以加入path的值pos,所以

dfs(m,pos);

 2.函数体:

就是简单记录我当前要加入path的值pos 然后依旧老规矩,添加path 进入递归(添加层数m+1,当前数字+1)保证下一层是从我当前数字的下一个位置开始的。

3.出口条件:

就是当我递归的层数到了k层,就说明path已经添加完了,直接ret.push_back()就行。

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(0,1);return ret;}void dfs(int m,int pos){if(m==k){ret.push_back(path);return;}for(int i=pos;i<=n;i++){path.push_back(i);dfs(m+1,i+1);path.pop_back();}}
};

总结:

这题跟前面题目一模一样,真的很简单,可以自己练练手。

6. ⽬标和(medium)

题目意思很简单,就是通过递归的方式求出所有可能数字的+或者-最后得到target

解析:

解法一:设置全局变量

任然是跟前面题目大差不差,就是利用递归的方法,利用全局变量,进行递归,考虑当前数字是+还是-,然后进行递归dfs,然后恢复现场。但是这种时间复杂度特别高,时间感人,还可以继续考虑优化。

class Solution {
public:int n;int ret=0,target,num=0;int findTargetSumWays(vector<int>& nums, int _target) {n=nums.size();target=_target;dfs(nums,0);return ret;}void dfs(vector<int>& nums,int k){if(k==nums.size()){if(num==target) ret++;return;}//加法num+=nums[k];dfs(nums,k+1);num-=nums[k];//减法num-=nums[k];dfs(nums,k+1);num+=nums[k];}
};

解法二:优化,使用局部变量,不用恢复现场

class Solution {
public:int n;int ret=0,target,num=0;int findTargetSumWays(vector<int>& nums, int _target) {n=nums.size();target=_target;dfs(nums,0,0);return ret;}void dfs(vector<int>& nums,int k,int pos){if(k==nums.size()){if(pos==target) ret++;return;}//加法dfs(nums,k+1,pos+nums[k]);//减法dfs(nums,k+1,pos-nums[k]);}
};

总结:

再写这种类似的递归回溯题目时候,可以先尝试考虑使用全局变量,如果行不通,在考虑局部变量进行优化。

7. 组合总和(medium)

题目意思很简单,就是要求子集来得到子集的和等于target

解析:

重要的事说一遍!!!画决策树!

 题目意思说一个数字可以重复使用,但是问题就是不能出现相同的子集。

那就说明再选取数字2后,得到2的所有情况,那后面所有数字的情况都不能包含2,即后面的所有数字都不能包含前面的数字。那么递归的时候,就要保证此时还能遍历到当前的数字,而不包括前面的数字,那么就是传入到下一层的时候,此时for里面的i还应该等于上一层的i,所以传入的函数dfs(candidates,i)这里的i不用++

函数头:这里要递归遍历所有的子集,从第0层传入

dfs(canditates,k);

函数体:

从当前层的第i个数字开始往后遍历所有的数字的所有相加的情况,直到满足出口条件了,就结束递归因为要从当前值开始遍历相加后面所有值的情况,这里就要用到for循环,再for内进行递归dfs(i),这里条件值传入i,是因为为了保证下一层相加的值仍然能从当前只开始,不会跳过相加到最后全是当前值的情况。

出口条件:

当遇到比目标值大或者等于的时候就不用再递归下去,直接进行返回上一层。

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

总结:

写到这里来说,像这种题,递归回溯剪枝,他就没有固定的模板,但是每题的思路又大差不差,所以还是很值得深思,只要会求出所有子集问题,就大概能解决这些题目。~

8.组合总和II

题目意思很简单,跟组合总和I不同的就是这里面存在多个相同的数字,但是每个数字只能使用一次,并且要设计不能含有相同数字的子集。

解析:

这题画决策树会发现,跟之前做的一道题非常类似

本题由于存在多个相同的数字再同一个数组内,但是如果深一点考虑,就会发现如果当前一个数字递归完所有结果后,到第二个相同的数字时,就会出现完全相同的递归结果,那么就要想办法取消后面相同数字的递归结果,但是又不能直接取消,因为再第一个数字进行递归的时候还是需要的。

那么就要考虑剪枝的策略,剪掉第二个以上的相同的数字,但是再第一个数字的位置不能剪掉。
那么,这里考虑不合法的情况:

if(i!=0&&candidates[i-1]==candidates[i]&&check[i-1]==false) continue;

这里就是为了防止越界,i!=0,因为当i等于0的时候,此时一定是合法的,考虑到不合法的情况就是再前一个数字跟我当前的数字相等的时候,此时前一个数字还是false,就表明此时是从我当前的数字开始的第一层,前面相同的数字还没用过,但其实是再上一轮循环就已经被使用过了,这里就已经可以看出我是已经被重复的元素,如果上一个数字是true就表明,上一个相同的数字再上一层被使用过,还能证明他是第一个相同的元素,那么我当前的元素仍然可以进入循环。

class Solution {
public:int n,target,sum=0;vector<vector<int>> ret;vector<int> path;bool check[101];vector<vector<int>> combinationSum2(vector<int>& candidates, int _target) {sort(candidates.begin(),candidates.end());n=candidates.size();target=_target;dfs(candidates,0);return ret;}void dfs(vector<int>& candidates,int k){if(sum>=target){if(sum==target) ret.push_back(path);return;}for(int i=k;i<n;i++){if(i!=0&&candidates[i-1]==candidates[i]&&check[i-1]==false) continue;else {check[i]=true;path.push_back(candidates[i]);sum+=candidates[i];dfs(candidates,i+1);path.pop_back();sum-=candidates[i];check[i]=false;}}}
};

总结:

这些题目都大差不差,都是利用相同的递归回溯问题,要认真思考递归下去回来后的情况,想清楚每一个细节,绝对可以cv。

9.组合总和III

题目意思还是比较简单的,就是求出1-9的子集,每个子集要有k个数,和要等于目标值n。

解析:

要不要定义数组都行,反正也只开9个空间,如何依旧是简单的递归加回溯,这里唯一要注意的就是递归的层数跟当数字开始进行递归,后面要添加的数字个数k个要区分开,所以函数头要传入两个参数:

函数头:dfs(0,0) 一个代表递归的当前的层数,一个代表添加数字的个数,只有添加的数字个数达到了k个,才能证明可以进行返回上一层,或者sum>n就要进行返回

函数体:依旧是简单的递归回溯问题由于每个数字只能出现一次,那么遍历的数字只能往后走,不能往前看。传入(i+1,w+1);

出口条件:当添加w添加的数字到达k个或者sum总和大于n了就可以进行返回了。

class Solution {
public:vector<vector<int>> ret;vector<int> path;int n,k;int sum=0;int a[9]={1,2,3,4,5,6,7,8,9};vector<vector<int>> combinationSum3(int _k, int _n) {k=_k,n=_n;dfs(0,0);return ret;}void dfs(int pos,int w){if(w>=k||sum>=n){if(w==k&&sum==n) ret.push_back(path);return;}for(int i=pos;i<9;i++){path.push_back(a[i]);sum+=a[i];dfs(i+1,w+1);path.pop_back();sum-=a[i];}}
};

总结:

依旧是简单的递归回溯问题,多练练就全会了。

10.字母大小写全排列

题目意思很简单就是遍历整个字符串,然后对每一个字符进行是否要修改两种选择。

解析:

唯一需要单独考虑的就是当前的字符是不是字母,然后进行是否要替换两种选择,如果要替换就单独+-32, 然后再考虑不用改变字符的情况。

class Solution {
public:vector<string> ret;int n;string path;string s;vector<string> letterCasePermutation(string _s) {if(_s=="") return ret;s=_s;n=s.size();dfs(0);return ret;}void dfs(int pos){if(pos==n){ret.push_back(path);return;}//变if(s[pos]>='a'&&s[pos]<='z'){path+=s[pos]-32;dfs(pos+1);path.pop_back();//不变path.push_back(s[pos]);dfs(pos+1);path.pop_back();}else if(s[pos]>='A'&&s[pos]<='Z'){path+=s[pos]+32;dfs(pos+1);path.pop_back();//不变path.push_back(s[pos]);dfs(pos+1);path.pop_back();}else {path+=s[pos];dfs(pos+1);path.pop_back();}  }
};

总结:

这题又跟上面求子集不同,只需要遍历整个字符串,然后进行是否选择当前字符进行修改即可。

11. 优美的排列(medium)

题目意思有点难理解,要多举例子,就是优美数组要满足能够被下标整除,或者整除下标即可,下标是从1开始的,求出所有满足条件的不同顺序的数组,那么这里就说明每一个优美数组都要完全包含[1-n]的数。

解析:

画决策树

因为下标是从1开始,这里就是一个坑点,如果不注意的话,能一直死再这里。那么pos传入的时候就可以设置成1,来表示传入现在第几层。

出口条件:

当传入1-n所有元素后,就说名此时已经有n层,但是pos是从1开始的,那么就是再pos==n+1层结束。

函数头:

dfs(1); 只要记录当前层数,传入pos即可。

函数体:

下标从1开始那么就要判断剪枝条件,当前数字是false才能进入,并且要满足两个条件里面的一个才行,所以这里用bool check[]数组来标记当前的数组是否被使用过。

这里for(i) i就充当的是数组[1-n],pos就相当于添加到优美数组的下标,也是层级。

if(check[i]==false&&(i%pos==0||pos%i==0))

class Solution {
public:int ret,n;bool check[16];int countArrangement(int _n) {n=_n;dfs(1);return ret;}void dfs(int pos){if(pos==n+1){ret++;return;}for(int i=1;i<=n;i++){if(check[i]==false&&(i%pos==0||pos%i==0)){check[i]=true;dfs(pos+1);check[i]=false;}}}
};

总结:

这题又跟上面决策树又不一样,要遍历整个数组进行剪枝,所以要用到check数组来判断当前位置的数组是否合法。

从这里开始就要进入二维数组的递归回溯+剪枝了:

12. N 皇后(hard)

N皇后问题相信大家再学习过程中绝对遇到过,如果没遇到,那真的不是一个完美的学习算法的过程。
就是再N皇后问题中,设置棋盘大小为N,那么那么就决定了在这个棋盘上,放下一个皇后后,它的这一列,这一个对角线和反对角线都是不被运行有皇后的。

解析:

画决策树,很重要很重要!!!

比如我们从3*3的棋盘大小出发,分别考虑行号和列号,对于行号,应该考虑的是,在每一行都进行遍历它的列号,那么对于行号应该只是在函数头进行传递,从第0行开始进行传递,直到最后一行结束,那么就在每一层即每一行内进行循环判断当前的列是否满足能够被按行皇后。

全局变量:

col[] //判断当前列是否存在皇后

dig1[] //判断当前位置的对角线是否存在皇后

dig2[] //判断当前位置的斜对角线是否存在皇后

函数头:设置n*n大小的棋盘传入dfs,i来设置当前是第几行也就是层级

dfs(path,i)

函数体:

我觉得这题虽然有点难,但是练习了上面那么多道题目,这个函数体还是非常简单的。
主要就是剪枝问题,只要考虑道能够满足合法的条件就能够进入dfs进入下一层:

if(col[j]==false&&dig1[i-j+n]==false&&dig2[j+i+n]==false)

当当前列,当前对角线,斜对角线都是满足false的时候就能够放入皇后,那么就可以进入下一层,那么这里就要注意改变col,dig1,dig2后返回到该层的时候就要恢复现场

                    path[i][j]="Q";
                    col[j]=true;
                    dig1[i-j+n]=true;
                    dig2[j+i+n]=true;
                    dfs(path,i+1);
                    path[i][j]=".";
                    col[j]=false;
                    dig1[i-j+n]=false;
                    dig2[j+i+n]=false;

 出口条件:只要记录层级i到达n层就说明已经可以放了n个皇后在棋盘上,然后这个时候只需要将棋盘添加到ret内即可。

class Solution {
public:bool col[10];bool dig1[30];bool dig2[30];vector<vector<string>> ret;int n;vector<vector<string>> solveNQueens(int _n) {n=_n;vector<vector<string>> path(n,vector<string>(n,"."));dfs(path,0);return ret;}void dfs(vector<vector<string>>& path,int i){if(i==n){vector<string> nums;for(int i=0;i<n;i++){string s;for(int j=0;j<n;j++){s+=path[i][j];}nums.push_back(s);}ret.push_back(nums);return;}for(int j=0;j<n;j++){if(col[j]==false&&dig1[i-j+n]==false&&dig2[j+i+n]==false){path[i][j]="Q";col[j]=true;dig1[i-j+n]=true;dig2[j+i+n]=true;dfs(path,i+1);path[i][j]=".";col[j]=false;dig1[i-j+n]=false;dig2[j+i+n]=false;}}}
};

总结:

N皇后问题虽然难,但是只要有前面题目的铺垫,现在在看这题,真的能发现自己的进步。只要画好决策树,真的就是信手拈来。

13. 有效的数独(medium)

就是要判断当前数独的行和列不能出现相同的数字,并且在当前位置的3*3小格就是9个数字内也不能出现相同的数字

 解析:

画图!画图!画图!

此题不是说非要太满整个数独盘,只需要判断当前的数独盘是不是已经出现了重复的元素即可。

那么此题就是判断各行和各列是否出现了相同的数字,因为在这个数独盘上只有数字[1-9],那么就可以分别证明行,列上是否出现了相同的数字。

设置bool 数组来设置每个数字在当前行或者当前列或者这个3*3的方格中是否存在相同的数字,因为我们要时刻记录[1-9]这些数字内的每一个数字,都是否重复存在,就将每一个数字单独进行记录。

那么定义行

row[9][10];

cal[9][10];

grid[3][3][10];

 用两层for循环进行遍历二维数组的每一个数字,分别用bool行 和 bool列 来进行观察这个数字之前是否存在过,即row[]第一个[]表示行或列下标 row[][]第二个[]表示当前的数字,如果出现过就为true,此时就直接返回false,否则继续判断

上面是对整行和整列的判断,那么对于3*3的各种又要单独进行考虑:

grid[3][3][10];

因为可以观察到这个9*9的格子在下标[0-9]中有0-2,3-5,6-8三种表示,然后分别除3就会完美的被分成3*3的格子从而将完美的将9*9的格子分成9个3*3的格子。所以就定义除一个三维数组
grid[3][3][10];分别表示:行数/3,列数/3,当前位置的数字,就可以判断这个数字是否在这个3*3的格子内出现过。 

class Solution {
public:bool row[9][10];bool cal[9][10];bool grid[3][3][10];bool isValidSudoku(vector<vector<char>>& board) {return dfs(board);}bool dfs(vector<vector<char>>& board){for(int i=0;i<9;i++){for(int j=0;j<9;j++){int sum=0;char _sum=board[i][j];if(_sum=='.') continue;else sum=_sum-'0';if(row[i][sum]==false&&cal[j][sum]==false){row[i][sum]=true;cal[j][sum]=true;}else return false;if(grid[i/3][j/3][sum]==false){grid[i/3][j/3][sum]=true;}else return false;}}return true;}
};

总结:

观察后这题不用进行递归,只要进行两层for循环遍历完整个二维数组就可以得到最后的结果~,但也是为了后面的题目做铺垫。

14. 解数独(hard)

题目意思很简单,就是把题目给的数独填满!

决策树!!!

解析:

通过决策树可以看到,如果我们开始就把这个数独已经存在的数字放入bool数组内,然后在开始填入就会方便很多。

这题通过决策树可以看到最主要的就是剪枝问题,因为每次在这个空格处填入的数字都是从1开始遍历填的,所以先来一次两层循环直到遇到了空格处,按照常规,进行第一步剪枝条件:

if(row[i][k]==false&&cal[j][k]==false&&grid[i/3][j/3][k]==false)

从[1-9]开始的每一个数字进行填入,遇到可以填入的数字,就把当前位置的bool数组修改为true,保证后面不会填入重复的数字,然后进行递归下去,此时就要重新进入dfs函数,重新进入循环就是开始寻找这一行的下一个空格,直到遇到上图我画的情况,在这一行的最后一个数还没有得到返回true的结果,那么就说明此时遍历完所有的[1-9]的情况就要直接返回false,说明当前这个空格已经失败了,不可取,说明上一层也要进行改变。

这题最主要的就是:在递归这里要判断下层返回回来的是不是false,如果是false,就证明下层遇到了重复的元素,并且不能进行填入,说明我当前层和上层都要进行改变;如果返回的树true,就证明可以告诉上层,当前填的值是正确的。

 if(dfs(board)) return true; //重点理解

class Solution {
public:bool row[9][10];bool cal[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++){char _sum=board[i][j];int sum=0;if(_sum!='.'){sum=_sum-'0';row[i][sum]=true;cal[j][sum]=true;grid[i/3][j/3][sum]=true;}   }}dfs(board);}bool dfs(vector<vector<char>>& board){for(int i=0;i<9;i++)for(int j=0;j<9;j++){char _sum=board[i][j];int sum=0;if(_sum=='.'){for(int k=1;k<=9;k++){if(row[i][k]==false&&cal[j][k]==false&&grid[i/3][j/3][k]==false){row[i][k]=true;cal[j][k]=true;grid[i/3][j/3][k]=true;board[i][j]=k+'0';if(dfs(board)) return true; //重点理解//恢复现场row[i][k]=false;cal[j][k]=false;grid[i/3][j/3][k]=false;board[i][j]='.';}}return false; //重点理解}}return true; //重点理解}
};

总结:

这一题是很值得深度思考的一题,很能帮助我们进行解决剪枝的问题,还是建议多思考,多判断这题进行递归的条件和返回值的位置,多判断在哪个位置进行递归,哪个位置进行返回,我相信会有巨大的收获。

从这题开始就要进入矩阵搜索的板块了!:有点洪水灌溉的意思了;

    int dx[4]={0,-1,0,1};
    int dy[4]={-1,0,1,0};

    for(int k=0;k<4;k++)
        {
            int x=i+dx[k];
            int y=j+dy[k];
            if(x>=0&&x<m&&y>=0&&y<n&&board[x][y]==word[pos]&&visit[x][y]==false)
            {
                visit[x][y]=true;
                if(dfs(board,word,x,y,pos+1)) return true;
                visit[x][y]=false;
            }
        }
        return false;

15. 单词搜索(medium)

题目意思还是很简答的,就是找到相连的所有格子的字串完全等于word。所以这里就要一个字符一个字符进行比较。

解析:

这题切入点就是在二维矩阵里找到跟word完全相同的字符串,要保证它每个字符都相连,那么我们就应该在主函数内去寻找word[0],然后不断遍历所有的word[0],直到有一个能返回true,否者最后就返回false。

这里最重要的一点就是,在主函数内进行准备递归之前,要将当前位置的字符的visit设置为true,这样才能让递归下去的字符串不出错。

visit[i][j]=true;

那么就从word[0]进入后,就要开始前后左右来寻找word的下一个字符,那么就是要在四个方位上去寻找,如果有相同的就进在进入下一层,那么记录查找到字符串的第几个字符pos 就进行+1,去寻找下一个字符。

    int dx[4]={0,-1,0,1};
    int dy[4]={-1,0,1,0};

这里就需要定义dx,dy数组,用向量的方式来方便我们进行前后左右进行查找

            int x=i+dx[k];
            int y=j+dy[k];

然后定义x,y分别就是当前位置的前后左右的值的下标,就开始判断这个下标是否会越界,是否是满足条件的字符,是否是被访问过等一系列问题。 

   if(x>=0&&x<m&&y>=0&&y<n&&board[x][y]==word[pos]&&visit[x][y]==false)

判断完成后,就开始进行递归,这里需要注意的就是visit数组要手动设置为true,然后递归到下一层寻找下一个字符,直到遍历完前后左右4个位置的字符都不满足的话,就返回false;若pos==word.size()就说明已经找到了所有的字符,就可以返回true。

class Solution {
public:int n,m;bool visit[16][16];int dx[4]={0,-1,0,1};int dy[4]={-1,0,1,0};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]){visit[i][j]=true;bool ret=dfs(board,word,i,j,1);if(ret) return true;visit[i][j]=false;}}}return false;}bool dfs(vector<vector<char>>& board,string word,int i,int j,int pos){if(pos==word.size()) return true;for(int k=0;k<4;k++){int x=i+dx[k];int y=j+dy[k];if(x>=0&&x<m&&y>=0&&y<n&&board[x][y]==word[pos]&&visit[x][y]==false){visit[x][y]=true;if(dfs(board,word,x,y,pos+1)) return true;visit[x][y]=false;}}return false;}
};

总结:

这是相当于洪水灌溉类第一个题目吧,二维矩阵搜索,还是比较简单的,也有参考意义,适合大家多思考多总结~

16. ⻩⾦矿⼯(medium)

题目意思很简单,就是从每一个不为0的位置开始进入然后一直以洪水灌溉的方式进行访问,不能回退,得到最大的收益,并且不能访问为0的位置。

解析:

这题跟上题简直一模一样,不过多赘述,就是在主函数开始从每一个不为0的位置开始进行访问,然后利用洪水灌溉的模式加上每一个位置的值,进行递归式访问,+到sum上,一直都让sum跟ret取最大值,直到遍历完所有的结果返回最大的ret。

同样跟上题一模一样,就是设置向量,来控制前后左右的位置,依旧是判断当前位置的周围位置,即前后左右x,y是否会越界:

        for(int k=0;k<4;k++)
        {
            int x=dx[k]+i;
            int y=dy[k]+j;

            if(x>=0&&x<m&&y>=0&&y<n&&grid[x][y]!=0&&visit[x][y]==false)
            {
                sum+=grid[x][y];
                ret=max(ret,sum);
                visit[x][y]=true;
                dfs(grid,x,y);
                visit[x][y]=false;
                sum-=grid[x][y];
            }
        }

写多了,也就是属于自己的模板题了,要学会多总结。

这里唯一需要注意的就是要在刚进入这个dfs的时候也要进行比较大小,因为这个时候的数字可能比前面所包含的sum和都要大,所以要单独进行比较一下。

class Solution {
public:int ret=0,sum=0;int dx[4]={0,-1,0,1};int dy[4]={-1,0,1,0};bool visit[16][16];int n,m;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]!=0){visit[i][j]=true;sum+=grid[i][j];ret=max(ret,sum);dfs(grid,i,j);sum-=grid[i][j];visit[i][j]=false;}}}return ret;}void dfs(vector<vector<int>>& grid,int i,int j){for(int k=0;k<4;k++){int x=dx[k]+i;int y=dy[k]+j;if(x>=0&&x<m&&y>=0&&y<n&&grid[x][y]!=0&&visit[x][y]==false){sum+=grid[x][y];ret=max(ret,sum);visit[x][y]=true;dfs(grid,x,y);visit[x][y]=false;sum-=grid[x][y];}}}
};// [0, 0, 34,0,5, 0, 7,0,0, 0]
// [0, 0, 0, 0,21,0, 0,0,0, 0]
// [0, 18,0, 0,8, 0, 0,0,4, 0]
// [0, 0, 0, 0,0, 0, 0,0,0, 0]
// [15,0, 0, 0,0, 22,0,0,0, 21]
// [0, 0, 0, 0,0, 0, 0,0,0, 0]
// [0, 7, 0, 0,0, 0, 0,0,38,0]

总结:

题目不难,主要就是要学会自己总结模板,但也不要死记硬背,理解了也就自然敲的出来了。

17. 不同路径 Ⅲ(hard)

题目意思很简单,就是从1位置开始出发,一直走到2,但是这期间的所有路径要经过全部的0.

解析:

不要看他是一个困难题,如果这题用动态规划确实很难,但是用暴搜确实很暴力,但是也变得非常简单了。

画图可以知道,只要从1开始走,到2结束,那么记录中间的所有0的个数即可,那么我就先提前记录所有0的个数和1的位置,然后进行进入dfs。

出口条件:

这里的出口条件要单独拿出来说一下,这里的出口条件就是在从1进入后开始遍历所有0的位置,当sum中0的个数==count后并不能代表ret就可以++,而是要额外判断,结束的位置的周围是否有2的存在,如果有,就说明能够从2出去,没有就会失败!

在就是函数体:

感觉越界没什么好讲的,前面这么多题,函数体都是一模一样的,全部都是设置x,y即周围位置的值,可以让它进行查找0的存在,然后不停的进行递归,知道最后如果失败了就进行回溯。 

        for(int k=0;k<4;k++)
        {
            int x=i+dx[k];
            int y=j+dy[k];
            if(x>=0&&x<m&&y>=0&&y<n&&grid[x][y]==0&&visit[x][y]==false)
            {
                visit[x][y]=true;
                sum++;
                dfs(grid,x,y);
                visit[x][y]=false;
                sum--;
            }
        }

class Solution {
public:int n,m;int dx[4]={0,-1,0,1};int dy[4]={-1,0,1,0};bool visit[20][20];int count=0,ret=0,sum=0;int uniquePathsIII(vector<vector<int>>& grid) {m=grid.size(),n=grid[0].size();int xi,yi;for(int i=0;i<m;i++)for(int j=0;j<n;j++){if(grid[i][j]==0) count++;else if(grid[i][j]==1) xi=i,yi=j;}        dfs(grid,xi,yi);return ret;}void dfs(vector<vector<int>>& grid,int i,int j){if(count==sum){for(int k=0;k<4;k++){int x=i+dx[k];int y=j+dy[k];if(x>=0&&x<m&&y>=0&&y<n&&grid[x][y]==2) ret++;}return;}for(int k=0;k<4;k++){int x=i+dx[k];int y=j+dy[k];if(x>=0&&x<m&&y>=0&&y<n&&grid[x][y]==0&&visit[x][y]==false){visit[x][y]=true;sum++;dfs(grid,x,y);visit[x][y]=false;sum--;}}}
};

总结:

虽然这题是困难题,但是用洪水灌溉思想真的很简单,就是暴力搜索,在二维矩阵内查找所有0的个数,并且求出合法的路径个数。

看到这里终于结束了,为了写这篇文章的总结,真是花了4天时间,每天写一点,每天写一点,因为学校有课的原因平时还要继续学校C++,Linux,HTLM5,CSS3等等一大堆不同的语法内容,可能不能做到太频繁的更新,但是我绝对在保证质量的前提下不停的写博客,绝对不会断更,最后关于暴搜,深搜,回溯小总结:

算法原理并不难

考察的是:代码能力,思路转化为代码

下期介绍:floodfill(洪水灌溉算法) 算法简介

性质相同的一个连通块,斜对角不算联通,只有上下左右才算联通。就是不断的在每一个位置进行深度优先遍历,在每一个位置进行上下左右扫描,知道走不动了就进行回溯。

这一期对我的收获巨大,创作不易,希望能对你也能产生巨大帮助!!!~

 

相关文章:

专题十一_递归_回溯_剪枝_综合练习_算法专题详细总结

目录 1. 找出所有⼦集的异或总和再求和&#xff08;easy&#xff09; 解析&#xff1a; 方法一&#xff1a; 解法二&#xff1a; 总结&#xff1a; 2. 全排列 Ⅱ&#xff08;medium&#xff09; 解析&#xff1a; 解法一&#xff1a;只关心“不合法”的分支 解法二&…...

java中Runnable接口是什么?基本概念、工作原理、优点、`Runnable`与`Thread`的对比、与`Callable`接口的对比、实际场景

Runnable接口是Java提供的一种用于实现多线程的接口&#xff0c;通常用来定义任务的具体逻辑。与Thread类不同&#xff0c;Runnable接口只提供一种抽象方法run()&#xff0c;没有任何与线程的生命周期、管理相关的功能。它的主要作用是与Thread类或线程池&#xff08;如Executo…...

Mybatis Plus连接使用ClickHouse也如此简单

通过阅读列式数据库ClickHouse官网&#xff0c;不难看出它有支持JDBC规范的驱动jar包&#xff0c;可以直接集成到Object Relational Mapping框架等&#xff0c;下面我用SpringBootMybatisPlus环境连接ClickHouse来演示一下 集成步骤 1.Maven引入ClickHouse提供的JDBC依赖 <…...

什么社交平台可以找到搭子?分享多款找搭子必备的人气软件

在这个丰富多彩的世界里&#xff0c;我们常常渴望有一个志同道合的搭子&#xff0c;一起分享生活的点滴&#xff0c;共同探索未知的领域。无论是追寻美食的舌尖之旅&#xff0c;还是踏上充满惊喜的旅途&#xff1b;无论是在健身房挥洒汗水…… 找到一个合适的搭子&#xff0c;都…...

STM32 RTC实时时钟 F407 寄存器

RTC介绍 STM32F1: RTC模块拥有一组连续计数的计数器&#xff0c;在相应软件配置下&#xff0c;可提供时钟日历的功能。 即在F1系列&#xff0c;RTC的日历部分只有一个32位的寄存器 该寄存器直接存放 时间戳 的值&#xff0c;即&#xff1…...

矩阵等价、向量组等价、线性方程组同解与公共解的关系

矩阵等价 矩阵 A 、 B 等价 ⇔ 两矩阵秩相等 R ( A ) R ( B ) ⇔ 每个矩阵的行秩等于列秩&#xff0c;两个矩阵的行秩与列秩分别相等 ⇔ 若行满秩则列向量组等价 ⇔ 若列满秩则行向量组等价 \begin{align} 矩阵A、B等价\\ &\Leftrightarrow 两矩阵秩相等R(A)R(B)\\ &\…...

[Linux] Linux 进程程序替换

标题&#xff1a;[Linux] Linux 进程程序替换 个人主页水墨不写bug &#xff08;图片来源于网络&#xff09; 目录 O、前言 一、进程程序替换的直观现象&#xff08;什么是进程程序替换&#xff1f;&#xff09; 二、进程程序替换的原理 三、进程程序替换的函数&#xff08…...

【Linux系统编程】第三十一弹---深入理解静态库:从零开始制作与高效使用的完全指南

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】【C详解】【Linux系统编程】 目录 1、静态库 1.1、怎么做静态库 1.2、怎么使用静态库 1、静态库 1.1、怎么做静态库 在Linux环境下&#xff0c;通常使用GCC&am…...

FFmpeg 简介及其下载安装步骤

目录 一、FFmpeg 简介 二、FFmpeg 安装步骤 2.1 打开官网 2.2 选择FFmpeg系统版本 2.3 下载FFmpeg压缩包 2.4 将下载好的压缩包进行解压 2.5 设置环境变量 2.5.1 在搜索栏中搜索【环境变量】&#xff0c;然后单击将其打开 2.5.2 找到系统变量中的【Path】&#xff0c;点…...

使用CSS+SVG实现加载动画

使用CSSSVG实现加载动画 效果展示 CSS知识点 SVG元素使用SVG相关CSS属性运用 整体页面布局 <section><div class"box"><div class"loader"><svg><circle cx"40" cy"40" r"40"></circl…...

物联网(IoT)的未来发展:智能互联时代的到来

物联网&#xff08;IoT&#xff09;的未来发展&#xff1a;智能互联时代的到来 物联网&#xff08;IoT&#xff09;正在迅速改变我们与世界互动的方式。无论是智能家居、智慧城市&#xff0c;还是工业自动化&#xff0c;物联网技术通过设备互联、数据采集和智能控制&#xff0…...

斯坦福 CS229 I 机器学习 I 构建大型语言模型 (LLMs)

1. Pretraining -> GPT3 1.1. Task & loss 1.1.1. 训练 LLMs 时的关键点 对于 LLMs 的训练来说&#xff0c;Architecture&#xff08;架构&#xff09;、Training algorithm/loss&#xff08;训练算法/损失函数&#xff09;、Data&#xff08;数据&#xff09;、Evalu…...

Java->排序

目录 一、排序 1.概念 2.常见的排序算法 二、常见排序算法的实现 1.插入排序 1.1直接插入排序 1.2希尔排序(缩小增量法) 1.3直接插入排序和希尔排序的耗时比较 2.选择排序 2.1直接选择排序 2.2堆排序 2.3直接选择排序与堆排序的耗时比较 3.交换排序 3.1冒泡排序…...

linux 大小写转换

var"TM_card_INFo" # 把变量中的第一个字符换成大写 echo ${var^} # 把变量中的所有小写字母&#xff0c;全部替换为大写 echo ${var^^} # 把变量中的第一个字符换成小写 echo ${var,} # 把变量中的所有大写字母&#xff0c;全部替换为小写 echo ${var,,} 参考…...

Linux——传输层协议

目录 一再谈端口号 1端口号范围划分 2两个问题 3理解进程与端口号的关系 二UDP协议 1格式 2特点 3进一步理解 3.1关于UDP报头 3.2关于报文 4基于UDP的应用层协议 三TCP协议 1格式 2TCP基本通信 2.1关于可靠性 2.2TCP通信模式 3超时重传 4连接管理 4.1建立…...

centos系列,yum部署jenkins2.479.1,2024年长期支持版本

centos系列&#xff0c;yum部署jenkins2.479.1&#xff0c;2024年长期支持版本 0、介绍 注意&#xff1a;jenkins建议安装LTS长期支持版本&#xff0c;而不是安装每周更新版本&#xff0c;jenkins安装指定版本 openjdk官网下载 Index of /jenkins/redhat-stable/ | 清华大学开…...

正则表达式-“三剑客”(grep、sed、awk)

1.3正则表达式 正则表达式描述了一种字符串匹配的模式&#xff0c;可以用来检查一个串是否含有某种子串&#xff0c;将匹配的子串替换或者从某个串中取出符号某个条件的子串等&#xff0c;在linux中代表自定义的模式模版&#xff0c;linux工具可以用正则表达式过滤文本。Linux…...

数智时代的新航向:The Open Group 2024生态系统架构·可持续发展年度大会邀您共筑AI数字新时代

在全球可持续发展和数字化转型双重驱动下&#xff0c;企业正面临着前所未有的挑战与机遇。如何在激烈的市场竞争中&#xff0c;实现业务增长的同时&#xff0c;履行社会责任&#xff0c;达成可持续发展的目标&#xff1f;The Open Group 2024生态系统架构可持续发展年度大会将于…...

TensorFlow 的核心概念

TensorFlow 是一个开源的机器学习框架&#xff0c;由 Google 开发和维护。它提供了一个强大的工具集&#xff0c;用于构建和训练各种机器学习模型。 TensorFlow 的核心概念是计算图&#xff08;Computational Graph&#xff09;。计算图由节点&#xff08;Nodes&#xff09;和…...

SpringBoot教程(二十四) | SpringBoot实现分布式定时任务之Quartz(动态新增、修改等操作)

SpringBoot教程&#xff08;二十四&#xff09; | SpringBoot实现分布式定时任务之Quartz&#xff08;动态新增、修改等操作&#xff09; 前言数据库脚本创建需要被调度的方法创建相关实体类创建业务层接口创建业务层实现类控制层类测试结果 前言 我这边的SpringBoot的版本为2…...

Matlab详细学习教程 MATLAB使用教程与知识点总结

Matlab语言教程 章节目录 一、Matlab简介与基础操作 二、变量与数据类型 三、矩阵与数组操作 四、基本数学运算与函数 五、图形绘制与数据可视化 六、控制流与逻辑运算 七、脚本与函数编写 八、数据导入与导出 九、Matlab应用实例分析 一、Matlab简介与基础操作 重点内容知识…...

【ELKB】Kibana使用

搭建好ELKB后访问地址&#xff1a;http://localhost:5601 输入账号密码登录以后 左侧导航有home、Analysis、Enterprise search 、Observability、Security、Management home&#xff1a;首页Analysis&#xff1a;工具来分析及可视化数据Enterprise search&#xff1a;企业级搜…...

ChatGPT免费使用:人工智能在现代社会中的作用

随着人工智能技术的不断发展&#xff0c;越来越多的应用程序和工具开始使用GPT作为其语言模型。但是&#xff0c;这些应用程序和工具是否收费&#xff1f;如果是免费的&#xff0c;那么他们是如何盈利的&#xff1f;在本文中&#xff0c;我们将探讨ChatGPT免费使用的背后原理&a…...

腾讯音乐:从 Elasticsearch 到 Apache Doris 内容库升级,统一搜索分析引擎,成本直降 80%

导读&#xff1a; 为满足更严苛数据分析的需求&#xff0c;腾讯音乐借助 Apache Doris 替代了 Elasticsearch 集群&#xff0c;统一了内容库数据平台的内容搜索和分析引擎。并基于 Doris 倒排索引和全文检索的能力&#xff0c;支持了复杂的自定义标签计算&#xff0c;实现秒级查…...

CubeMX的FreeRTOS学习

一、FreeRTOS的介绍 什么是FreeRTOS&#xff1f; Free即免费的&#xff0c;RTOS的全称是Real Time Operating system,中文就是实时操作系统。 注意&#xff1a;RTOS不是指某一个确定的系统&#xff0c;而是指一类的操作系统。比如&#xff1a;us/OS&#xff0c;FreeRTOS&…...

C语言初始:数据类型和变量

、 一.数据类型介绍 人有黄人白人黑人&#xff0c;那么数据呢&#xff1f; 我们大家可以看出谁是黄种人&#xff0c;谁是白种人&#xff0c;谁是黑种人&#xff0c;这是因为他们是类似的。 数据也是有类型的&#xff0c;就譬如整数类型&#xff0c;字符类型&#xff0c;浮点…...

Linux shellcheck工具

安装工具 通过linux yum源下载&#xff0c;可能因为yum源的问题找不到软件包&#xff0c;或者下载的软件包版本太旧。 ShellCheck的源代码托管在GitHub上(推荐下载方式)&#xff1a; GitHub - koalaman/shellcheck: ShellCheck, a static analysis tool for shell scripts 对下…...

FLINK SQL时间属性

Flink三种时间属性简介 在Flink SQL中&#xff0c;时间属性是一个核心概念&#xff0c;它主要用于处理与时间相关的数据流。Flink支持三种时间属性&#xff1a;事件时间&#xff08;event time&#xff09;、处理时间&#xff08;processing time&#xff09;和摄入时间&#…...

android——Groovy gralde 脚本迁移到DSL

1、implementation的转换 implementation com.github.CymChad:BaseRecyclerViewAdapterHelper:*** 转换为 implementation ("com.github.CymChad:BaseRecyclerViewAdapterHelper:***") 2、plugin的转换 apply plugin: kotlin-android-extensions 转换为&#x…...

工程项目管理中的最常见概念!蓝燕云总结!

01 怎么理解工程项目管理&#xff1f; 建设工程项目管理指的是专业性的管理&#xff0c;并非行政事务管理。建设工程项目管理是对工程项目全生命周期的管理&#xff0c;确保项目能够按计划的进度、成本和质量完成。 建设工程项目不同阶段管理的主要内容不同&#xff0c;通常…...