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

news2024/11/25 22:35:35

目录

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(洪水灌溉算法) 算法简介

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

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

 

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

AI比诺奖得主更聪明。。。

B站&#xff1a;啥都会一点的研究生公众号&#xff1a;啥都会一点的研究生 AI圈又发生了哪些新鲜事&#xff1f; 特斯拉Optimus人形机器人亮相&#xff1a;日常任务好帮手 特斯拉在Cybercab活动上展示了其新款人形机器人Optimus&#xff0c;这款机器人能够执行拿包裹、浇水等…

CentOS快速配置网络Docker快速部署

CentOS快速配置网络&&Docker快速部署 CentOS裸机Docker部署1.联通外网2.配置CentOS镜像源3.安装Docker4.启动Docker5.CentOS7安装DockerCompose Bug合集ERROR [internal] load metadata for docker.io/library/java:8-alpineError: Could not find or load main class …

20241013组会

文章&#xff1a;A learnable end-edge-cloud cooperative network for driving emotion sensing 引言&#xff1a; 驾驶员的情绪状态直接影响安全驾驶。在“车-人-路-云”一体化控制框架下&#xff0c;我们提出了一种端-边-云协同情感感知网络模型&#xff08;EEC-Net&#x…

Android平台RTSP|RTMP播放器PK:VLC for Android还是SmartPlayer?

好多开发者&#xff0c;希望在Android端低延迟的播放RTMP或RTSP流&#xff0c;本文就目前市面上主流2个直播播放框架&#xff0c;做个简单的对比。 VLC for Android VLC for Android 是一款功能强大的多媒体播放器&#xff0c;具有以下特点和功能&#xff1a; 广泛的格式支持…

FPAG学习(5)-三种方法实现LED流水灯

目录 1.移位实现LED流水灯 1.1创建工程及源文件代码 1.1.1源代码 1.1.2仿真代码 1.1.3仿真 1.2实验结果 1.2.1总结 2.循环移位实现LED流水灯 3.38译码器实现LED流水灯 3.1原理 3.2源程序 1.移位实现LED流水灯 1.1创建工程及源文件代码 1.1.1源代码 利用计数器计数到…

【深度学习】经典的深度学习模型-01 开山之作:CNN卷积神经网络LeNet-5

【深度学习】经典的深度学习模型-01 开山之作&#xff1a;CNN卷积神经网络LeNet-5 Note: 草稿状态&#xff0c;持续更新中&#xff0c;如果有感兴趣&#xff0c;欢迎关注。。。 0. 论文信息 article{lecun1998gradient, title{Gradient-based learning applied to document r…

【智能算法应用】长鼻浣熊优化算法求解二维路径规划问题

摘要 本文采用长鼻浣熊优化算法 (Coati Optimization Algorithm, COA) 求解二维路径规划问题。COA 是一种基于长鼻浣熊的觅食和社群行为的智能优化算法&#xff0c;具有快速收敛性和较强的全局搜索能力。通过仿真实验&#xff0c;本文验证了 COA 在复杂环境下的路径规划性能&a…

【微服务】springboot3 集成 Flink CDC 1.17 实现mysql数据同步

目录 一、前言 二、常用的数据同步解决方案 2.1 为什么需要数据同步 2.2 常用的数据同步方案 2.2.1 Debezium 2.2.2 DataX 2.2.3 Canal 2.2.4 Sqoop 2.2.5 Kettle 2.2.6 Flink CDC 三、Flink CDC介绍 3.1 Flink CDC 概述 3.1.1 Flink CDC 工作原理 3.2 Flink CDC…

数据结构:栈的创建、使用以及销毁

这里写目录标题 栈的结构与概念栈底层结构的选取栈的代码实现&#xff08;stack&#xff09;头文件&#xff08;stack.h&#xff09;栈的初始化栈的销毁入栈出栈获取栈顶数据获取栈大小代码的测试 栈的结构与概念 栈&#xff1a;⼀种特殊的线性表&#xff0c;其只允许在固定的…

【算法篇】动态规划类(1)(笔记)

目录 一、理论基础 1. 大纲 2. 动态规划的解题步骤 二、LeetCode 题目 1. 斐波那契数 2. 爬楼梯 3. 使用最小花费爬楼梯 4. 不同路径 5. 不同路径 II 6. 整数拆分 7. 不同的二叉搜索树 一、理论基础 1. 大纲 动态规划&#xff0c;英文&#xff1a;Dynamic Programm…

企业水、电、气、热等能耗数据采集系统

介绍 通过物联网技术&#xff0c;采集企业水、电、气、热等能耗数据&#xff0c;帮企业建立能源管理体系&#xff0c;找到跑冒滴漏&#xff0c;从而为企业节能提供依据。 进一步为企业实现碳跟踪、碳盘查、碳交易、谈汇报的全生命过程。 为中国碳达峰-碳中和做出贡献。 针对客…

【C++进阶】set的使用

1. 序列式容器和关联式容器 前面&#xff0c;我们已经接触过STL中的部分容器如&#xff1a;string、vector、list、deque、array、forward_list等&#xff0c;这些容器统称为序列式容器&#xff0c;因为逻辑结构为线性序列的数据结构&#xff0c;两个位置存储的值之间⼀般没有紧…

【工具箱】Flash基础及“SD NAND Flash”的测试例程

目录 一、“FLASH闪存”是什么&#xff1f; 1. 简介 2. 分类 3. 性能 4.可靠性 5.易用性 二、SD NAND Flash 1. 概述 2. 特点 3. 引脚分配 4. 数据传输模式 5. SD NAND寄存器 6. 通电图 7. 参考设计 三、STM32测试例程 1. 初始化 2. 单数据块测试 3. 多数据块…

场景题 - 画三角形并只点击三角形触发事件

简介 画一个三角形并仅点击三角形区域才会触发点击事件。 可以拆解成&#xff1a; 画个三角形绑定点击事件&#xff08;涉及点击区域&#xff09; 这里提供更多更好用的方法&#xff0c;svg polygon绘制三角形、canvas、css clip-path:polygon( ) 裁剪可视区域&#xff0c;并…

文件和目录的权限管理

定义&#xff1a; 文件和目录的权限管理在操作系统中至关重要&#xff0c;特别是在多用户环境下&#xff0c;它决定了不同用户对文件和目录的访问和操作权限。 一、基本权限类型及表示方法 在Linux系统中&#xff0c;文件和目录的权限分为三类&#xff1a;读取权限&#xff08;…

谷歌-BERT-第一步:模型下载

1 需求 需求1&#xff1a;基于transformers库实现自动从Hugging Face下载模型 需求2&#xff1a;基于huggingface-hub库实现自动从Hugging Face下载模型 需求3&#xff1a;手动从Hugging Face下载模型 2 接口 3.1 需求1 示例一&#xff1a;下载到默认目录 from transform…

南邮-软件安全--第一次实验报告-非爆破计算校验值

软件安全第一次实验报告&#xff0c;切勿直接搬运&#xff08;改改再交&#xff09; 实验要求 1、逆向分析目标程序运行过程&#xff0c;找到程序的关键校验点&#xff1b; 2、以非爆破的方式正确计算crackme的校验值&#xff1b; 内容 使用x32dbg对文件进行分析 打开文件…

思迈特:在AI时代韧性增长的流量密码

作者 | 曾响铃 文 | 响铃说 “超级人工智能将在‘几千天内’降临。” 最近&#xff0c;OpenAI 公司 CEO 山姆奥特曼在社交媒体罕见发表长文&#xff0c;预言了这一点。之前&#xff0c;很多专家预测超级人工智能将在五年内到来&#xff0c;奥特曼的预期&#xff0c;可能让这…

构建可扩展的高校学科竞赛平台:SpringBoot案例分析

2相关技术 2.1 MYSQL数据库 MySQL是一个真正的多用户、多线程SQL数据库服务器。 是基于SQL的客户/服务器模式的关系数据库管理系统&#xff0c;它的有点有有功能强大、使用简单、管理方便、安全可靠性高、运行速度快、多线程、跨平台性、完全网络化、稳定性等&#xff0c;非常…

高效管理学科竞赛:SpringBoot平台的创新应用

1系统概述 1.1 研究背景 随着计算机技术的发展以及计算机网络的逐渐普及&#xff0c;互联网成为人们查找信息的重要场所&#xff0c;二十一世纪是信息的时代&#xff0c;所以信息的管理显得特别重要。因此&#xff0c;使用计算机来管理高校学科竞赛平台的相关信息成为必然。开发…