暴力搜索——回溯

暴力搜索算法浅显易懂 基础算法之 暴力搜索 算法说明 暴力算法也叫蛮力算法,之所以称为暴力,是因为该算法是枚举当前所有出现的情况, 从而得到需要的情况。该算法可以求的一些情况较少的问题的解,若问题规模太大,该算法便不适用。 算法分析 若给定一个集合 α ,求得该集合的所有子集,求得的子集即为某种情况,列出所有子集及是暴力算法。比如给定一个数组 [1, 3, 5] ,其所有子集为 [ ],[ 1 ],[ 3 ],[ 5 ... 阅读详情

理论

回溯的本质是穷举,穷举所有可能,然后选出我们想要的答案。一般可以解决如下几种问题:
组合问题:N个数里面按一定规则找出k个数的集合
切割问题:一个字符串按一定规则有几种切割方式
子集问题:一个N个数的集合里有多少符合条件的子集
排列问题:N个数按一定规则全排列,有几种排列方式
棋盘问题:N皇后,解数独等等
组合是不强调元素顺序的,排列是强调元素顺序。因此组合无序,排列有序。

例题

77. 组合
回溯就是递归,所以按照递归三部曲来做

  1. 参数:n,k固定参数,startIndex,从startindex开始往后取元素,path,存放一条支路的结果,re:存放所有支路的结果
    返回值:因为要遍历整棵树,所以不需要返回值
  2. 终止条件:当到达终止条件时存放结果,也就是path里的元素数等于k
  3. 本层递归:从startindex开始往后选择,递归调用下一层,回溯。
    for循环次数就是树的分支数,递归的深度就是树的深度。
class Solution {
public:
    vector<vector<int>>re;
    vector<int>path;
    void backtracking(int startIndex,int n,int k){
        if(path.size()==k){//到达叶子结点
            re.push_back(path);
            return ;
        }
        for(int i=startIndex;i<=n;i++){
            path.push_back(i);
            backtracking(i+1,n,k);//下一层就要从i+1开始选择了
            path.pop_back();//回溯
        }
    }
    vector<vector<int>> combine(int n, int k) {
        backtracking(1,n,k);
        return re;
    }
};

优化:
当startIndex开始往后的元素个数小于要找的个数,就不需要再找了。
即 n-i+1(还剩的个数) < k-path.size() (还要找的个数)
因此只需要从startIndex开始查找到n+1-k+path.size()

class Solution {
public:
    vector<vector<int>>re;
    vector<int>path;
    void backtracking(int startIndex,int n,int k){
        if(path.size()==k){//到达叶子结点
            re.push_back(path);
            return ;
        }
        for(int i=startIndex;i<=n+1-k+path.size();i++){
            path.push_back(i);
            backtracking(i+1,n,k);
            path.pop_back();//回溯
        }
    }
    vector<vector<int>> combine(int n, int k) {
        backtracking(1,n,k);
        return re;
    }
};

216. 组合总和 III

  1. 参数:n,k:找k个数并且和为n,startIndex:从startindex位置开始找
    返回值:空
  2. 终止条件:如果要找和为负数的集合,直接返回
    如果要找和0的集合并且当前已经找到了k个,保存结果
    如果n为0或者path.size()==k,直接返回
  3. 本层递归:从startindex开始往后取,递归,回溯
class Solution {
public:
    vector<int>path;
    vector<vector<int>>re;
    void backtracking(int k,int n,int startIndex){//找k个数,并且和为n的集合
        if(path.size()==k){//找够了k个数
            if(n==0){
                re.push_back(path);
            }
            return ;
        }
        for(int i=index;i<=9;i++){
            if(n-i<0){//当前位置往后的都比n大,自然也就没有必要再找了
                break;
            }
            path.push_back(i);
            f(k,n-i,i+1);//找从i+1开始,组成n-i的组合
            path.pop_back();
        }
    }
    vector<vector<int>> combinationSum3(int k, int n) {
        backtracking(k,n,1);
        return re;
    }
};

17. 电话号码的字母组合
每一种组合中集合的个数就是digits的长度,从当前位置对于的字符串中找一个字母,然后再去找下一个位置的字母

  1. 参数:digits 固定参数,index:从index开始找

  2. 终止条件:当找到digits长度大小的集合时,就保存一下

  3. 本层递归:映射出当前位置的数字字符对于的字符串,然后穷举每一个可能。

class Solution {
public:
    string number [10]={"","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};//数字字符串映射表
    vector<string>re;
    string path;
    void f(string & digits,int index){//从digits中的index位置开始找
        if(path.size()==digits.size()){
            re.push_back(path);
            return ;
        }
        string cur=number[digits[index]-'0'];//index位置对应字符串,对于digits="23",index==0,cur就是"abc"
        for(int i=0;i<cur.size();i++){
            path.push_back(cur[i]);
            f(digits,index+1);
            path.pop_back();
        }
        
       
    }
    vector<string> letterCombinations(string digits) {
        if(digits==""){
            return re;
        }
        f(digits,0);
        return re;
    }
};

39. 组合总和

和选硬币问题完全一样

  1. 参数:candidates 固定参数,index:从index往后开始找和为rest的组合
  2. 终止条件:rest小于0,直接返回
    rest等于0,保存结果
  3. 本层递归:从index往后选一个元素,然后去找减去index位置元素的组合

和上几个题的区别是本题元素可以重复选

class Solution {
public:
    vector<int>path;
    vector<vector<int>>re;
    void f(vector<int>& candidates,int index,int rest){
        if(rest<0){//非法情况
            return ;
        }
        if(rest==0){
            re.push_back(path);
            return ;
        }
        for(int i=index;i<candidates.size();i++){
            path.push_back(candidates[i]);
            f(candidates,i,rest-candidates[i]);//从i开始,找和
            //为rest-candidates[i]的组合
            path.pop_back();
        }
    }
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        f(candidates,0,target);
        return re;
    }
};

优化:先对candidates进行排序,如果rest-candidates[i]已经小于0了,后面就没有必要再尝试了

class Solution {
public:
    vector<int>path;
    vector<vector<int>>re;
    void f(vector<int>& candidates,int index,int rest){
        if(rest<0){//非法情况
            return ;
        }
        if(rest==0){
            re.push_back(path);
            return ;
        }
        for(int i=index;i<candidates.size();i++){
            if(rest-candidates[i]<0){//剪枝
                break;
            }
            path.push_back(candidates[i]);
            f(candidates,i,rest-candidates[i]);//从i开始,找和
            //为rest-candidates[i]的组合
            path.pop_back();
        }
    }
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        sort(candidates.begin(),candidates.end());
        f(candidates,0,target);
        return re;
    }
};

40. 组合总和 II

本题难点在于如何去重,可以先排序,这样相同的元素肯定紧挨着,在每一层递归下设一个哈希表,用来标记有没有使用过这个元素,只有没有使用过的才去跑递归
因为candidates里可能有重复的元素,所以同一个组合里会有重复元素,但是不同的组合元素要不同,也就是同一树层去重
剪枝的操作和上一题一样。

class Solution {
public:
    vector<int>path;
    vector<vector<int>>re;
    void f(vector<int>& candidates,int index,int rest){//从index开始往后找和为rest的组合
        if(rest==0){
            re.push_back(path);
            return ;
        }
        unordered_map<int,bool>visit;
        for(int i=index;i<candidates.size() && rest-candidates[i]>=0 ;i++){
            if(!visit[candidates[i]]){//去重,如果已经使用了candidates[i]元素就剪掉
                visit[candidates[i]]=true;
                path.push_back(candidates[i]);
                f(candidates,i+1,rest-candidates[i]);
                path.pop_back();
            }
            
        }
        
    }
    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        sort(candidates.begin(),candidates.end());
        f(candidates,0,target);
        return re;
    }
};

省空间的写法,因为已经排过序了,所以相同的元素肯定紧靠着,只要判断它和它的前一个元素是否相等(同一树层下)

class Solution {
public:
    vector<int>path;
    vector<vector<int>>re;
    void f(vector<int>& candidates, int index, int rest) {//从index开始往后找和为rest的组合
        if (rest == 0) {
            re.push_back(path);
            return;
        }
        unordered_map<int, bool>visit;
        for (int i = index; i < candidates.size() && rest - candidates[i] >= 0; i++) {
            if (i > index && candidates[i] == candidates[i - 1]) {//i>index是保证在同一树层下
                continue;
            }
            path.push_back(candidates[i]);
            f(candidates, i + 1, rest - candidates[i]);
            path.pop_back();
        }

    }
    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        sort(candidates.begin(), candidates.end());
        f(candidates, 0, target);
        return re;
    }
};

131. 分割回文串

  1. 参数:s 固定参数,index:从index位置开始分割
  2. 终止条件:当index等于s的长度时保存结果
  3. 本层递归:要找从index开始的回文子串,i初始为index 表示从index位置开始分割,例如s=“aab”,index=0,i
    开始等于0,就是a作为一个子串,然后去切割ab;如果i等于1,就是aa作为一个子串,然后去切割ab。注意要判断子串是否为回文串。
class Solution {
public:
    vector<string>path;
    vector<vector<string>>re;
    bool isPalindrome(const string& s){//判断是否为回文串
        int l=0;
        int r=s.size()-1;
        while(l<r){
            if(s[l++]!=s[r--]){
                return false;
            }
        }
        return true;
    }
    void f(string& s,int index){//找从index位置开始分割的子串
        if(index==s.size()){
        
            re.push_back(path);
            return ;
        }
        for(int i=index;i<s.size();i++){
            string substr=s.substr(index,i-index+1);//截取从index到i的子串
            if(isPalindrome(substr)){//只有substr是回文串才去跑递归
                path.push_back(substr);
                f(s,i+1);
                path.pop_back();
            }
        }
    }
    vector<vector<string>> partition(string s) {
        f(s,0);
        return re;
    }
};

93. 复原 IP 地址

类似于切割字符串,直接在原字符串上修改就好了

  1. 参数:s,startindex:从startindex位置开始找,pointNum:s中 . 的个数
  2. 终止位置:pointNum为3,判断startindex到结束的字符串是否合法
  3. 本层递归:也是先判断从startindex位置到i的字符串是否合法,若合法,在i+1位置插入 . ,然后找i+2开始字符串。
class Solution {
public:
    vector<string>re;
    bool isValid(string& s,int start,int end){//判断是否合法
        if(start>end){
            return false;
        }
        if(s[start]=='0'){
            if(start==end){
                return true;
            }
            else{
                return false;
            }
        }
       int num=0;
       for(int i=start;i<=end;i++){
           num=num*10+(s[i]-'0');
           if(num>255){//当前num已经超过255了,后续一定也大于255
               return false;
           }
       }
       return true;
    }
    void f(string s,int startIndex,int pointNum){
        if(pointNum==3){//已经添加了3个点了,只需要判断startIndex到结束是否是合法的
            if(isValid(s,startIndex,s.size()-1)){
                re.push_back(s);
            }
            return ;
        }

        for(int i=startIndex;i<s.size();i++){
            if(isValid(s,startIndex,i)){//相当于在i后面加. 需要提前判断startindex到i是否是合法的
                s.insert(s.begin()+i+1,'.');//在i后面插入 . 
                pointNum++;
                f(s,i+2,pointNum);//因为插入了一个.  所以从i+2开始
                pointNum--;
                s.erase(s.begin()+i+1);
            }
             else {//不符合的话,后续一定也是不符合的,因为后续都包含这一部分。
                break;
            }
        }
    }
    vector<string> restoreIpAddresses(string s) {
        f(s,0,0);
        return re;
    }
};

78. 子集

集合是无序的,也就是{1,2}和{2,1}是同一个集合,所以每次for循环都是从index开始,如果是排列的话,就要从0开始。

class Solution {
public:
    vector<int>path;
    vector<vector<int>>re;
    void f(vector<int>& nums,int index){
        re.push_back(path);//子集出现在递归过程中,不仅仅只是终止情况
        if(index==nums.size()){
            return ;
        }
        for(int i=index;i<nums.size();i++){
            path.push_back(nums[i]);
            f(nums,i+1);
            path.pop_back();
        }
    }
    vector<vector<int>> subsets(vector<int>& nums) {
        f(nums,0);
        return re;
    }
};

90. 子集 II

和组合中II类似,都是同一层去重

class Solution {
public:
    vector<vector<int>>re;
    vector<int>path;
    void f(vector<int>& nums,int index){
        re.push_back(path);
        if(index==nums.size()){
            return ;
        }
        for(int i=index;i<nums.size();i++){
            if(i>index &&nums[i]==nums[i-1]){//同一层去重
                continue;
            }
                path.push_back(nums[i]);
                f(nums,i+1);
                path.pop_back();
          
        }
    }
    vector<vector<int>> subsetsWithDup(vector<int>& nums) {
        sort(nums.begin(),nums.end());
        f(nums,0);
        return re;
    }
};

491. 递增子序列

需要注意的是递增子序列至少2个元素。
去重逻辑和上一个一样了,因为相同的不一定是紧挨着的,所以采用set去重。

class Solution {
public:
    vector<int>path;
    vector<vector<int>>re;
    void f(vector<int>& nums, int index) {
        if (path.size() > 1) {
            re.push_back(path);
        }
        if (index == nums.size()) {
            return;
        }
        unordered_set<int> uset;
        for (int i = index; i < nums.size(); i++) {
            if (uset.find(nums[i]) == uset.end()) {//本层第一次出现
                uset.insert(nums[i]);//标记使用
                if (path.size() > 0) {//path不为空要判断是否递增
                    int lastNum = path.back();//path最后一个元素
                    if (nums[i] >= lastNum) {
                        path.push_back(nums[i]);
                        f(nums, i + 1);
                        path.pop_back();
                    }
                }
                else {//path为空的时直接放

                    path.push_back(nums[i]);
                    f(nums, i + 1);
                    path.pop_back();
                }
            }

        }
    }
    vector<vector<int>> findSubsequences(vector<int>& nums) {

        f(nums, 0);
        return re;
    }
};

优化:用数组做哈希表

class Solution {
public:
    vector<int>path;
    vector<vector<int>>re;
    void f(vector<int>& nums, int index) {
        if (path.size() > 1) {
            re.push_back(path);
        }
        if (index == nums.size()) {
            return;
        }
        //unordered_set<int> uset;
        int used[201]={0};//0表示为使用过,1表示使用过
        for (int i = index; i < nums.size(); i++) {
            if (used[nums[i]+100]==0) {//本层第一次出现
                used[nums[i]+100]=1;//标记使用
                if (path.size() > 0) {//path不为空要判断是否递增
                    int lastNum = path.back();//path最后一个元素
                    if (nums[i] >= lastNum) {
                        path.push_back(nums[i]);
                        f(nums, i + 1);
                        path.pop_back();
                    }
                }
                else {//path为空的时直接放

                    path.push_back(nums[i]);
                    f(nums, i + 1);
                    path.pop_back();
                }
            }

        }
    }
    vector<vector<int>> findSubsequences(vector<int>& nums) {

        f(nums, 0);
        return re;
    }
};

46. 全排列

注意排列和组合的区别:排列有顺序,组合无顺序

排列问题可以不需要index,但需要借助used数组
这个去重是同一树枝下去重,也就是在同一条支路下,一个元素只能选择一次。

class Solution {
public:
    vector<vector<int>>re;
    vector<int>path;
    
    void f(vector<int>& nums,vector<int>& used){
        if(path.size()==nums.size()){
            re.push_back(path);
            return ;
        }
        for(int i=0;i<nums.size();i++){
            if(used[i]==0){//只选择本树枝下没有用过的
                used[i]=1;
                path.push_back(nums[i]);
                f(nums,used);
                path.pop_back();
                used[i]=0;
            }
        }
    }
    vector<vector<int>> permute(vector<int>& nums) {
        vector<int>used(nums.size());
        f(nums,used);
        return re;
    }
};

解2 :交换实现
如果一个集合有3个元素,那么它的排列方式有6种,第一个位置3种选择,第二个位置2种选择,第三个位置1种选择,相乘起来就是6种选择。

class Solution {
public:
    vector<vector<int>>re;
    void f(vector<int>& nums,int index){//从index开始的排列
        if(index==nums.size()){
            re.push_back(nums);
            return ;
        }
        for(int i=index;i<nums.size();i++){//从index开始往后的元素都可以放在index位置,相当于在index位置选一个元素
            swap(nums[index],nums[i])
            f(nums,index+1);//找index+1的全排列
            swap(nums[index],nums[i]);
        }
    }
    vector<vector<int>> permute(vector<int>& nums) {
        f(nums,0);
        return re;
    }
};

47. 全排列 II

只需要去重就好了

class Solution {
public:
    vector<vector<int>>re;
    vector<int>path;
    void f(vector<int>& nums,int index){
        if(index==nums.size()){
            re.push_back(nums);
            return ;
        }
        //unordered_set<int>uset;
        int visit[21]={0};
        for(int i=index;i<nums.size();i++){
            if(visit[nums[i]+10]==0){
                visit[nums[i]+10]=1;
                swap(nums[index],nums[i]);
                f(nums,index+1);
                swap(nums[index],nums[i]);
            }
        }
    }
    vector<vector<int>> permuteUnique(vector<int>& nums) {
        f(nums,0);
        return re;
    }
};

332. 重新安排行程

  1. 参数:vectorre;
    nordered_map<string,map<string,int>>targets;//unordered_map<开始地,map<目的地,航班数(0:表示不能再飞往此地了)>>targets targets表示从开始地到目的地所有航班的映射
    vector<vector>& tickets
    返回值:bool,找到一个解法就返回
  2. 终止条件:re.size()==tickets.size()+1,n趟航班就n+1个目的地。
  3. 本层递归:
    优先考虑目的地字典序靠前的航班,可以选择的条件是:本航班还有机票——target.second>0

-为什么第一次找到的解法就是字典序靠前的解法?
map默认是按字典序进行排序的,所以如果有多种解法,会先尝试字典序小的。

class Solution {
public:
    vector<string>re;
    unordered_map<string,map<string,int>>targets;//unordered_map<开始地,map<目的地,航班数(0:表示不能再飞往此地了)>>targets  targets表示从开始地到目的地所有航班的映射
    bool f(vector<vector<string>>& tickets){
        if(re.size()==tickets.size()+1){
            return true;
        }
        string curPlace=re[re.size()-1];//从curPlace出发
        for(pair<const string,int>&target:targets[curPlace]){//从curPlace出发的所有目的地
            if(target.second>0){//表示从curPlace还可以飞的航班
                target.second--;
                re.push_back(target.first);
                if(f(tickets)){//找到了一个就返回,因为map默认是按字典序排的,所以第一个找到的一定是符合要求的
                    return true;
                }
                re.pop_back();
                target.second++;
            }
        }

        return false;
    }
    vector<string> findItinerary(vector<vector<string>>& tickets) {
        for(int i=0;i<tickets.size();i++){
            vector<string>curFlight=tickets[i];//<开始地,目的地>
            targets[curFlight[0]][curFlight[1]]++;//增加映射关系
        }
        re.push_back("JFK");
        f(tickets);
        return re;
    }
};

51. N 皇后

思路:一行一行的摆,只需要判断是否共列 共斜线就好了
共斜线的简便判法:abs(row1 - row2) == abs(col1,col2) (row1,col1)和(row2,col2)共斜线

  1. 参数:vector<vector>re存结果;vectorpath;//每一行摆皇后的情况;ectorrecord;//记录每一行皇后摆的位置 record[i]=j:表示i行j列放皇后;string ss;//固定参数,哪一列上放皇后,就将相应位置修改为Q;
  2. 终止位置:index==n表示已经摆完了n行皇后(0-n-1)
  3. 本层递归:从本行的第一列到第n列尝试,如果当前位置合法,就记录下皇后的位置,然后递归跑下一行。
class Solution {
public:
    vector<vector<string>>re;
    vector<string>path;//每一行摆皇后的情况
    vector<int>record;//记录每一行皇后摆的位置 record[i]=j:表示i行j列放皇后
    bool isValid(int i, int j, int n) {//判断i行j列放皇后合不合法
        for (int row = 0; row < i; row++) {
            if (record[row] == j || abs(row - i) == abs(record[row] - j)) {//共列 共斜线
                return false;
            }
        }
        return true;
    }
    void f(int index, int n, string ss) {
        if (index == n) {
            re.push_back(path);
            return;
        }
        for (int j = 0; j < n; j++) {
            if (isValid(index, j, n)) {
                record[index] = j;
                string cur = ss;
                cur[j] = 'Q';
                path.push_back(cur);
                f(index + 1, n, ss);
                path.pop_back();
            }
        }
    }
    vector<vector<string>> solveNQueens(int n) {
        string ss;//固定参数,哪一列上放皇后,就将相应位置修改为Q
        for (int i = 0; i < n; i++) {
            ss += '.';
        }
        record.resize(n);
        f(0, n, ss);
        return re;

    }
};

37. 解数独
和N皇后类似,但比N皇后更复杂,N皇后每行只放一个皇后,而本题是要填满整个方格。
N皇后是一行一行的填,本题要一格一格的填,因此至少需要两层for循环

  1. 参数:board
    返回值:找到一个方案就返回,题目也说了只有一种方案。
  2. 终止条件:不需要写,表格填满了自然就终止
  3. 本层递归:从0行0列到8行8列,找到第一个空白的格子,然后尝试1~9的数字,若合法,继续放下一个格子。
class Solution {
public:
    bool isValid(int i,int j,char val,vector<vector<char>>& board){//判断(i,j)填k是否合法,此时i行j列位置是空的
        for(int col=0;col<9;col++){//判断同行
            if(board[i][col]==val){
                return false;
            }
        }
        for(int row=0;row<9;row++){//判断同列
            if(board[row][j]==val){
                return false;
            }
        }
        int startRow=i/3*3;//(i,j)所在的3*3
        int startCol=j/3*3;
        for(int row=startRow;row<startRow+3;row++){//判断同3*3
            for(int col=startCol;col<startCol+3;col++){
                if(board[row][col]==val){
                    return false;
                }
            }
        }
        return true;
    }
    bool f(vector<vector<char>>& board){
        for(int i=0;i<9;i++){
            for(int j=0;j<9;j++){
                if(board[i][j]!='.'){
                    continue;
                }
                for(char k='1';k<='9';k++){//从1~9尝试
                    if(isValid(i,j,k,board)){
                        board[i][j]=k;
                        if(f(board)){//找到一个方案就返回
                            return true;
                        }
                        board[i][j]='.';

                    }
                }
                return false;//1到9都不行,说明该方案没有解
            }
        }
        return true;//所有的格都填满了
    }
    void solveSudoku(vector<vector<char>>& board) {
        f(board);
        
    }
};

总结

回溯是递归的副产品,只要有递归就会有回溯,所以回溯法也经常和二叉树遍历,深度优先搜索混在一起,因为这两种方式都是用了递归。回溯法就是暴力搜索,并不是什么高效的算法,最多再剪枝一下。

  1. 组合问题
    组合无顺序
    for循环代表树的分支,横向遍历,递归深度代表树的高度,纵向遍历,回溯不断调整结果集

  2. 切割问题
    类似与组合问题,参数的index就代表切割线

  3. 子集问题
    子集问题也属于组合,不仅仅在终止收集结果

  4. 排列问题
    排列有顺序
    可以用交换的方式,也可以加一个used数组

  5. 去重问题
    同一树层去重,只需要在函数内部创建一个visit数组,只供本层使用
    这个visit数组也可以是set,如果题目中没有明确规定结点的范围,就要使用set了

  6. 安排行程问题
    有递归的地方就有回溯,深度优先搜索也是用递归来实现的,所以往往伴随着回溯。
    本题难在如何选择正确的容器

  7. 棋盘问题
    一层for循环的递归:N皇后,一行只放一个
    二层for循环的递归:解数独,一格放一个
    这两道题目的树的分支都是固定的,N皇后的分支固定为n,每行就有n个位置可以选择
    解数独的分支固定为9,从1到9选择一个数来填
    但是解数独的递归深度明显大于N皇后。

回溯--一种暴力搜索算法 简单了解下回溯?回溯是递归的副产品,只要有递归就会有回溯。,如果想让回溯法高效一些,可以加一些的操作,但也改不了回溯法就是穷举的本质。什么是组合,什么是排列?之前在dp(上)中遇到过求组合数/排列数的两种题型,。例如:{1, 2} 和 {2, 1} 在组合上,就是一个集合,因为不强调顺序,而要是排列的话,{1, 2} 和 {2, 1} 就是两个集合了。组合无序,排列有序。如何理解回溯法?回溯法解决的都是在集合中递归查找子集,。 阅读详情

相关推荐

暴力搜索内存 直接内存搜索枚举内存的原理和实现

暴力搜索内存 直接内存搜索枚举内存的原理和实现

【算法】浅析暴力搜索算法【附完整示例】

暴力搜索算法(Brute Force Algorithm),也称为穷举搜索算法,是一种尝试所有可能的解决方案,直到找到最优解的算法。暴力搜索算法是一种简单直接的搜索算法,通过枚举所有可能的解决方案,逐一验证,最终找到最优解。虽然对于某些问题,暴力搜索算法的效率不高,但它提供了一种解决问题的思路和方法。通过本文的介绍,相信大家对暴力搜索算法的原理、使用和意义有了更深入的了解。在实际问题求解过程中,我们可以根据问题的特点,灵活运用暴力搜索算法,找到最优解。

Young_Pro的博客 3932

Linux下libdrm实战:从零搭建一个简单的DRM显示程序(附完整代码)

本文是一份Linux下libdrm的实战指南,详细介绍了如何从零开始搭建一个简单的DRM显示程序。通过对比传统Framebuffer的局限性,阐述了现代DRM架构的核心优势与核心对象(FrameBuffer、Plane、CRTC等)的协作流程。文章提供了完整的代码示例,演示了如何打开设备、创建并填充Dumb Buffer、设置CRTC以依次显示红绿蓝纯色画面,并涵盖了编译、运行及进阶开发要点,为嵌入式Linux图形开发提供了扎实的入门基础。

weixin_29183637的博客 406

算法模版:暴力搜索之DFS【沈七】

算法模版:暴力搜索之DFS前言基本概念算法思想模板 前言 唤我沈七就行。 又是拖更的两周~ 因为开学将至,学校竞赛班也要在开学前的月底来一场测试,所以我就加快了学习算法的进度,最近两周涉猎了DFS、BFS、背包DP、线性DP。也整理了不少笔记,很快就会陆续更新啦~ 今天带来的是骗分神器–DFS 基本概念 DFS:是Depth First Search的简称 ,即深度优先搜索算法: 是一种用于遍历或搜索树或图的算法。 沿着树的深度遍历树的节点,尽可能深的搜索树的分支。 当节点v的所在边都己被探寻过或者在搜寻时

只想和你一起进步 7514

SIwave仿真手册——软件基础(一)

最近在学习SIWAVE,在这里将自己的笔记整理一下,做一个专题。希望对大家有帮助,这个会持续更新的。仅限于软件操作学习,不涉及信号完整性的讨论。

luohuo9844的博客 4万+

ARM架构UOS实战:从源码编译Qt 5.15.2到定制Qt Creator的全流程解析

本文详细解析了在ARM架构统信UOS系统上,从源码编译Qt 5.15.2到定制Qt Creator开发环境的完整流程。内容涵盖环境准备、依赖部署、源码配置、编译优化、错误处理及IDE集成,旨在帮助开发者在国产化平台上构建自主可控的高性能Qt开发环境,解决特定ARM平台的兼容性与性能优化问题。

mango的博客 410

字符串搜索算法:暴力搜索,KMP

最近脑子有点昏昏沉沉,喝点那种红枣泡的白酒居然神奇的好了一些,感觉很舒服。看来喝少量的酒可以让人更清醒,长期喝可能有养生的效果?写道这里去百度了下,发现红枣还真有养生效果。对于长期坐在电脑旁的人,不止眼睛,其实整个身体状况就注定不会很好,平时还是要注意养生。虽然现在整个行业很卷又是互联网的寒冬,但还是尽量抽出一点时间出去走走运动运动,这样人更精神做事效率也会更高。前段时间有个大佬左耳朵才40多岁就心梗去世了,应该是平时没注意自己身体或者没有精力管自己的身体健康问题?

Myd 1039

四轮两驱小车(四):STM32驱动5路灰度传感器PID循迹

之前买了一批5路灰度传感器,想用这传感器进行循迹,无奈网上和官方的资料提供的还是比较少,这里还是做一下当初的学习记录。

weixin_51651698的博客 6万+

暴力搜索技术(2)

构成树的下一层节点,检查是否出现目标状态G,若未出现,就对该层所有状态节点,分别顺序利用规则。生成再下一层的所有状态节点,对这一层的所有状态节点检查是否出现G,若未出现,继续按上面思想生成在下一层的所有状态节点,这样一层一层往下展开。在一个位置,只要有路,就一直走到最深处,直到无路可走,再回退一步,看在上一步的位置能不能换个方向继续往下走。(3)对于一些重要的问题蛮力法可以产生一些合理的算法,他们具备一些实用价值,而且不受问题规模的限制。(4)继续前进,如果无路可走,回退到上一步,换个方向再走;

m0_69865681的博客 1187

告别手动标注!PDMS NakiToolkit插件安装与初体验:以Pipeline工具为例

本文详细介绍了PDMS NakiToolkit插件的安装与使用,特别是其Pipeline工具如何通过自动化标注和材料统计功能大幅提升三维工厂设计效率。从环境准备到实战演示,帮助工程师告别繁琐的手动标注工作,实现项目交付周期的大幅缩短。

weixin_30047059的博客 350

基于 Python 的 LIF 模型:探索神经元同步与小世界网络

首先,我们定义了一个LIFNeuronself.soma.L = self.soma.diam = 12.6157 # 固定神经元形状self.cm = cm # 电容self.V_rest = V_rest # 静息电位self.V = V_rest # 初始化膜电位在初始化过程中,每个神经元被赋予初始的静息电位,并且其膜电位会在后续的模拟过程中动态变化。

qq_36224726的博客 1423

【DFS系列 | 暴力搜索与回溯剪枝】DFS问题实战:如何通过剪枝优化暴力搜索效率

深度优先搜索(DFS)是解决组合、路径等问题的核心算法,但暴力枚举效率低下。如何通过回溯和剪枝优化DFS,使其更高效?本文通过实战案例,解析DFS从穷举到优化的关键技巧,提升算法效率与性能。

专注于 C/C++ 后端开发,涵盖基础语法、Linux系统与网络、MySQL数据库、常见算法及项目经验。 1435

告别玄学调试:用STM32CubeMX和逻辑分析仪搞定DP83848的RMII与SMI接口

本文详细介绍了如何使用STM32CubeMX和逻辑分析仪调试DP83848的RMII与SMI接口,帮助开发者超越表象,直接观察底层通信波形。通过实战案例和高级调试技巧,解决常见问题如时钟偏移、信号完整性等,提升网络接口的稳定性和可靠性。

weixin_30752699的博客 299

暴力搜索 回溯法 —— 八皇后问题

暴力搜索 枚举暴力搜索的核心就是暴力,即把所有的可能性都列出来,然后一一试验,最后得到解。下面来看看一些暴力搜索的例子:1. 除法输入正整数n,从小到大输出所有形如abcde / fghij = n 的表达式,其中a~j 是 数字0~9的一个排列,2<= n <=79 样例输入: 62 样例输出: 79546 / 01283 = 62 94736 / 01528 = 62

XXX 2303

C++怎么学?零基础也可以,一份完整学习路线图助你搞定!

提供一条清晰、系统且实践导向的C++学习路径。从基础语法、面向对象、泛型编程、异常处理,逐步深入STL、智能指针、并发编程等现代C++高级特性。无论是新手还是希望提升,通过系统学习与项目实践,助您将理论知识转化为实际解决问题的能力。

Lion_Long的博客 4022

Outlook Express 6 (OE6) for Win7/Win8/Win10

英文版 Outlook Express6.0, 兼容Win7/Win10/WinXP,解压就可用。

【高频考点】回溯(暴力搜索)

如果集合中元素有重复,但只能用集合中的,就需要用used标记/idx去重;3)组合数是无序的(用过的不能再用):因此for遍历起始值是当前元素的idx,递归的索引是从当前的下一个元素开始。-选取一个a之后,在bcdef中再去选取第2个,选取b之后在cdef中再选取第3个。-切割一个a之后,在bcdef中再去切割第2段,切割b之后在cdef中再切割第3段。相当于没有终止条件的组合,是统计所有节点(组合是统计所有叶子节点)*如果元素是可重复选取的,递归的i的idx不需要+1。递归-纵向遍历-树的深度。

m0_57082284的博客 334

迈瑞BS系列全自动生化分析仪LIS协议接口手册(内含HL7和ASTM)

迈瑞BS系列全自动生化分析仪LIS协议接口手册,内含HL7和ASTM)

算法(6)-暴力搜索回溯-排列、组合 (子序列)

有一类题目-子序列(不需连续)- 最长递增子序列LIS、 最长公共子序列LCS子串,子数组数(需要连续) - 最大和子数组排列:英文为 Permutation 或者 Arrangement,因此在数学公式里常用P 或者 A 表示。排列数:从n个不同的元素中取m个元素,按一定的顺序组成的组合,成为一个排列;​组合:英文是 Combination,因此在数学公式里常用 C 表示。

sinat_40624829的博客 447

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

回溯法(暴力搜索)

leetcode--回溯

luseysd的博客 2319

【基础算法】从入门到实战:递归型枚举与回溯剪枝,暴力搜索的初级优化指南

本文介绍了算法设计中暴力搜索与自适应搜索的核心概念,重点探讨了递归型枚举与回溯剪枝的优化方法。通过决策树分析,展示了子集枚举和组合型枚举的递归实现原理,并提供了对应的代码示例。文章强调回溯与剪枝在提升搜索效率中的作用,为算法竞赛和编程实践提供了初级优化指南,帮助读者掌握高效搜索的基本技巧。

遇见,获得,失去,成长,释怀,完结。我与旧事归于尽,来年依旧迎花开。 447

搜索算法 暴力 与 回溯

实际上是一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就返回,尝试别的路径。暴搜就是通过不断的搜索,依次判断,求得最终的答案,因此,暴搜的时间复杂度通常很大,相应的数据范围较小。暴搜本质还是运用了递归的思想,重复调用自身函数求得答案。

Zhang_Qin123的博客 1628

万字长文详解回溯算法,从基础方法到进阶剪枝,体验暴力搜索的艺术

从基础到进阶,从原理到实战讲清有关回溯算法的大部分内容!

2505_93363502的博客 1060

2021-02-26 洛谷P1966挖地雷——dfs暴力搜索(回溯法)

摘要: dfs暴力搜索。以图中任意一个定点i为起点开始搜索,通过dfs搜索每一条路径,找出权重最大的一条。 题目简述(问题转化): n个点,有向加权图,给出邻接矩阵。求一条最大权重的路径 洛谷P1966 算法分析: 由于点数n很小,因此可以采用dfs暴力搜索的方法。 分析题目可知,每到一个点,都可以挖到一定数量的地雷,可将这个条件转化为上一个点到下一个点之间的边的权重大小。 这道题和简单的图论中的深度搜索不一样。这里面的每一个点可以遍历任意多次,因此不可以简单的用一个visit数组,将之前遍历过的点全

下次一定用long long 219

暴力求解法(枚举,搜索,回溯,剪枝策略)

暴力求解法,介绍枚举,排列,搜索剪枝,回溯

qq_51539256的博客 2909

【递归、搜索与回溯算法必刷42题】暴力回溯算法:494. 目标和 + 39. 组合总和

给定整数数组nums、整数target;数组每个数字前添加或,拼接成表达式,返回运算结果等于target的不同表达式数量。无重复元素数组candidates、目标值target;数字可无限次选取,返回所有和等于 target 的不重复组合。判定组合不同:选取数字数量不同即不同。

艾莉丝努力练剑的博客,感谢大家一直以来的对艾莉丝的支持和鼓励,uu们如果对C/C++方向感兴趣的话,可以通过艾莉丝主页的微信名片加艾莉丝的好友,和努力练剑的艾莉丝一起加油! 569

回溯法:暴力搜索中的 “聪明策略”

而回溯法,正是一种在暴力枚举基础上优化的 “聪明策略”—— 它像走迷宫一样,一路探索,走不通就退回上一步换条路,大大减少了不必要的计算。它不像动态规划那样有很高的思维门槛,更多的是一种 “套路化” 的算法 —— 只要掌握了路径、选择列表、结束条件这三个核心要素,套用通用模板,再根据具体问题调整细节,就能解决大部分回溯类题目。它的本质是深度优先搜索(DFS) 的一种应用,通过递归的方式,一步步构建可能的解。如果不符合条件,就回溯—— 撤销当前的选择,回到上一步,尝试其他可能的选择。,返回其所有可能的全排列。

Ancestors_of的博客 430
上一篇: 滑动窗口模板
下一篇: 并查集-初步了解
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值