回溯(Flash back)学习

Posted 楠c

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了回溯(Flash back)学习相关的知识,希望对你有一定的参考价值。

回溯是一种通过穷举所有可能情况来找到所有解的思想。如果一个候选解最后被发现并不是可行解,回溯思想会舍弃它,并在前面的一些步骤做出一些修改,并重新尝试找到可行解。回溯一般会结合在搜索算法中

电话号码组合

在这里插入图片描述

Map建立号码与字符串映射。
在代码中,DFS里,curStr+e即一个回溯的过程。例如当前是cur是空串,e依次被映射为abc,先从a开始,继续调用DFS,由于index++,所以e依次被映射到def,所以curStr现在是a,就开始和def两两组合,符合条件入结果数组。执行完后回溯,curStr成为b,又开始新的一轮。

class Solution {
private:
    unordered_map<char,string> Map;
    void DFS(const string& digits,vector<string>& Allret,string curStr,int idx)
    {
        if(idx==digits.size())
        {//只有和字符串大小相等时才加入数组当中,然后结束当前深搜
           Allret.push_back(curStr);
           return;
        }
        //取出当前字符对应的字符串
        string curMap=Map[digits[idx]];
        for(char e:curMap)
        {
            //curStr+e,即为一个回溯的过程
            //从DFS返回他就又成为了curStr
            //curStr+e是个临时变量,所以形参不能是引用
            DFS(digits,Allret,curStr+e,idx+1);
        }
    }
public:
    vector<string> letterCombinations(string digits) {
         vector<string> Allret;
         if(digits.size()==0)
          return Allret;
         Map={{'2',"abc"},{'3',"def"},{'4',"ghi"},{'5',"jkl"},{'6',"mno"},{'7',"pqrs"},{'8',"tuv"},{'9',"wxyz"}};
         

         DFS(digits,Allret,"",0);


         return Allret;

    }
};

组合总和

最精华的部分就是DFS中的for循环,从prev开始,因为题目中的2 2 3和3 2 2这种组合不能重复出现,即现在是3,不能向前回退去找2。又因为当前元素可以重复,所以在此调用的时候,需要从i开始,即形参prev的实参为i。

class Solution {
private:
    void DFS(vector<int>& candidates, int target,vector<vector<int>>& ret,vector<int>& temp,int sum,int prev)
    {
        //大于返回,等于,先添加进结果数组再返回。
        if(sum>=target)
          {
              if(sum==target)
                {
                    ret.push_back(temp);
                }
              return;
          }
          
          //从上一个开始,例如2 2 3,回溯之后为2 2,可以从2 2开始继续试探
          //不能从第一个重新开始,因为有可能造成 2 2 3 和 3 2 2这种情况,就成为了重复的组合
       for(int i=prev;i<candidates.size();i++)
       {
           temp.push_back(candidates[i]);
           //回溯
            //1. 当前Dfs结束sum+candidates[i],就会变成sum
            //2. temp里面也要删
            //prev继续传i表示,上一个相加的位可以重复使用
           DFS(candidates,target,ret,temp,sum+candidates[i],i);
           temp.pop_back();
       }
    }
public:
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
           vector<vector<int>> ret;
           if(candidates.empty())
              return ret;
           vector<int> temp;
           DFS(candidates,target,ret,temp,0,0);

           return ret;
    }
};

二进制手表

分钟和小时的组合不一样,所以要区分开,依旧是i=prev,prev代表上次的位置,因为它不允许向前回退,但又由于上次的元素不能重复,所以下次调用时,形参的prev实参需要变成i+1。
回溯的过程,结合代码注释,在体会体会。

class Solution {
private:
    vector<int>  v;
    // v={1,2,4,8,16,32};
    void DFS(vector<string>& ret,int hour,int minutes,int prev,int turnedOn)
    {
        //边界
        if(hour>=12||minutes>=60)
        {
         return;
        }
         //每次减一,等于0,说明组合满足,时放入结果数组
         //例如turned=2,就是两两组合
         if(turnedOn==0)
         {
             //m和h:先用整形计算,后转换成字符串
             string m=to_string(minutes);
             //字符串小于两位,前面补个0
             m=m.size()>=2?m:'0'+m;
             string h=to_string(hour);
             ret.push_back(h+":"+m);
             return;
         }
         
         for(int i=prev;i<10;i++)
         {
             //小于6,先和分钟组合
             if(i<6)
             {                //从i=0开始,turnedOn为2-1。
                //第一次搜索以1为基准,i<6算分钟,去匹配 1+2 1+4 1+8 1+16 1+32(即0:03,0:05,....)
                                 //  i>6算小时,去匹配 1:01 2:01 4:01 8:01
                //回溯回来以2为基准,   i<6算分钟,去匹配 2+4 2+8 2+16 2+32(即0:06,0:10,...)
                                 //  i>6算小时,去匹配 1:02 2:02 4:02 8:02
                //处理完2回溯回来到3
                                          //i+1表示下一次不算本身,例如以1为基准组合,从2开始。以2为基准组合,从4开始
                 DFS(ret,hour,minutes+v[i],i+1,turnedOn-1);
             }//大于6,和小时组合
             else
             {
                 DFS(ret,hour+v[i-6],minutes,i+1,turnedOn-1);
             }
         }

    }
public:
    vector<string> readBinaryWatch(int turnedOn) {
         vector<string> ret;
         if(turnedOn>=9)
           return ret;
           //小时和分钟的前四个一样,那么写成一个,等会做个区分就可以
           //用两个vector也可
         v={1,2,4,8,16,32};
         DFS(ret,0,0,0,turnedOn);

         return ret;

    }
};

字符串排列

在这里插入图片描述
题目的样例要求向前回退,即b可以匹配a,所以在循环中我们每次都要从0开始,但是这样肯定会有aaa,aab,aac,等这种一个元素重复使用的情况。
在这里插入图片描述
在这里插入图片描述
vis是一个标记数组
在这里插入图片描述
在这里插入图片描述
这样就避免了重复使用一个元素。
在这里插入图片描述

class Solution {
private:
    void DFS(string s,unordered_set<string>& ret,vector<bool>& vis,string curStr,int index)
    {
        //放入哈希set当中,这样做的原因是防止字符串中有重复的例如"aab"

        if(index==s.size())
        {
            ret.insert(curStr);
            return;
        }
        for(int i=0;i<s.size();i++)
        {
            if(vis[i])
            {
            vis[i]=false;
            DFS(s,ret,vis,curStr+s[i],index+1);
            vis[i]=true;
            }
        }
    }
public:
    vector<string> permutation(string s) {
      
      unordered_set<string> ret;
      vector<bool> vis(s.size(),true);
      DFS(s,ret,vis,"",0);
      
      //去重之后再放进数组中返回
      vector<string> ret1(ret.begin(),ret.end());
      return ret1;

    }
};

在和这两个对比一下
在这里插入图片描述
在这里插入图片描述

活字印刷

可以向前回退,但重复元素不能使用。即循环从0开始,且需要标记数组

class Solution {
private:
    void DFS(string tiles,unordered_set<string>& ret,vector<bool>& vis,string curStr)
    {
        
        if(curStr!="")
        {
        ret.insert(curStr);
        }

        for(int i=0;i<tiles.size();i++)
        {
            if(vis[i])
            {
                vis[i]=false;
                DFS(tiles,ret,vis,curStr+tiles[i]);
                vis[i]=true;
            }
        }


    }

public:
    int numTilePossibilities(string tiles) {
         unordered_set<string> ret;
         vector<bool> vis(tiles.size(),true);

         DFS(tiles,ret,vis,"");
         
         return ret.size();
    }
};

N皇后

在这里插入图片描述
在这里插入图片描述

class Solution {
    void DFS(vector<vector<pair<int,int>>>& ret,vector<pair<int,int>>& temp,int curRow,int n)
    {
        if(curRow==n)
        {
            //临时数组坐标全部符合,curRow才能加到n,才能入结果数组
            ret.push_back(temp);
        }

        for(int col=0;col<n;col++)
        {
            //坐标有效才插入临时数组
            //临时数组:(保护当前一种方法的所有皇后坐标)
            if(isValid(temp,curRow,col))
            {
            temp.push_back(make_pair(curRow,col));
            DFS(ret,temp,curRow+1,n);
            temp.pop_back();
            }
        }
    }
    //皇后坐标是否有效
    bool isValid(vector<pair<int,int>>& temp,int curRow,int col)
    {
        for(auto& e:temp)
        {
            if(e.second==col||e.first+e.second==curRow+col||e.first-e.second==curRow-col)
               return false;
        }
        return true;
    }
    //输出有点奇怪,所以要转换
    vector<vector<string>> transRet(vector<vector<pair<int,int>>> ret,int n)
    {
        vector<vector<string>> vv;
        for(auto& v:ret)
        {
           //观察输出,二维数组,每个一维数组由4个string组成,每个string都有四个'.'
           vector<string> vs(n,string(n,'.'));
           for(auto& e:v)
           {
               vs[e.first][e.second]='Q';
           }
           vv.push_back(vs);
        }
        return vv;
    }
public:
    vector<vector<string>> solveNQueens(int n) {
     vector<vector<pair<int,int>>> ret;
     vector<pair<int,int>> temp;
     DFS(ret,temp,0,n);

     vector<vector<string>> vv=transRet(ret,n);

     return vv;
    }
};

N皇后||

我们用一维临时数组来存储当前方法的皇后坐标。有几种方法就插入几次二维数组。最后二维数组的size就是方法数量

class Solution {
private:
    void DFS(vector<vector<pair<int,int>>>& ret,vector<pair<int,int>>& temp,int curRow,int n)
    {
        if(curRow==n)
        {
            ret.push_back(temp);
            return;
        }
        for(int col=0;col<n;col++)
        {
            if(isVaild(temp,curRow,col))
            {
                temp.push_back(make_pair(curRow,col));
                DFS(ret,temp,curRow+1,n);
                temp.pop_back();
            }
        }
    }
    bool isVaild(vector<pair<int,int>> temp,int curRow,int col)
    {
               for(auto&e :temp)
               {
                   if(col==e.second||curRow+col==e.first+e.second||curRow-col==e.first-e.second)
                     return false;
               }
               return true;        
    }
public:
    int totalNQueens(int n) {
           vector<vector<pair<int,int>>> ret;
           vector<pair<int,int>> temp;

           DFS(ret,temp,0,n);

           return ret.size();
    }
};

以上是关于回溯(Flash back)学习的主要内容,如果未能解决你的问题,请参考以下文章

从片段返回到上一个活动 onclick back button

Gremlin 图遍历回溯

哪个 Javascript 历史回溯实现是最好的?

Android - 弹出两个或更多片段但不顶部

使用带有actionscript 3 Flash cs6的矩形或线条创建2D重复模式

addToBackStack 没有导航回正确的片段