递归与回溯2

目录

一:求解​

       回溯法的剪枝 

         二:二维平面上使用回溯法

1:二维平面找字母

      题目描述

      几点细节

     下面看一个简化问题

         三:岛屿数量

         四:N皇后问题

vector string特点


一:求解c_{n}^{k}


对于这种回溯求解值的可能问题,画出递归树是关键

 如图:与排列问题不同,每个分支结束了,下一次的递归调用不要把这个数包含。

也就是:考虑1+[2,...n]然后2+[3,...,n]....也就是起点也是个关键变量


代码:

class Solution {
private:
    vector<vector<int>> res;//返回结果
    //因为n,k在程序中始终为定值,不妨设成私有变量
    int n;
    int k;
    //递归函数
    void genCombine(int start,vector<int>& s){
          //start:记录1+[其他],2+[除1其他],3+[除1,2其他]
          //s记录符合的结果,要求s.size()==k
          if(s.size()==k){
              res.push_back(s);
              return;
          }
          for(int i=start;i<=n;i++){
              //范围:【strat,....,n】
              s.push_back(i);
              genCombine(i+1,s);
              //注意回溯
              s.pop_back();
          }
    }
public:
    vector<vector<int>> combine(int n, int k) {
        this->n=n;
        this->k=k;
        //n必须大于k
        if(n<k) return res;
        //传入一个记录单个符合要求的参数
        vector<int> s;
        genCombine(1,s);
        return res;
    }
};

每次只能从当前传入的start后开始找。

回溯法的剪枝 

剪枝-----减少无用的枝节

比如:【1,2,3,4】取两个,那么从4开始的完全没必要递归,因为已经没元素了

class Solution {
private:
    vector<vector<int>> res;//返回
    int n;
    int k;
    //递归函数
    void genCombine(int start,vector<int>& s){
          if(s.size()==k){
              res.push_back(s);
              return;
          }
          //优化关键
          //每次循环:还有k-s.size()的空位,也就是还需要找的个数
          //i是起点,[i,...,n]至少要有k-s.size()
          //(n-i)+1>=k-size()
          for(int i=start;i<=n-(k-s.size())+1;i++){
              s.push_back(i);
              genCombine(i+1,s);
              s.pop_back();
          }
    }
public:
    vector<vector<int>> combine(int n, int k) {
        this->n=n;
        this->k=k;
        if(n<k) return res;
        vector<int> s;
        genCombine(1,s);
        return res;
    }
};

关键:要保证[start,...n]至少有k个元素,那么每次执行到i时候,说明还需要k-s.size()

[i,。。。,n]必须有k-s.size()空位-----n-i+1>=k-s.size()

 二:二维平面上使用回溯法

1:二维平面找字母

先来回顾一下:vector<vector 类型>> 变量 的遍历

int main(){
     vector<vector<char>> board={
        {'a','b','c','e'},//borad[0]
        {'f','s','c','s','m'},//board[1]
        {'d','e','e'}//board[3]
    };
    //遍历1:下标法
    for(int i=0;i<board.size();i++){
        for(int j=0;j<board[i].size();j++){
            cout<<board[i][j]<<" ";
        }
        cout<<endl;
    }
    cout<<"****"<<endl;
    //遍历2:迭代
    vector<vector<char>>::iterator it;
    for(it=board.begin();it!=board.end();it++){
        vector<char>::iterator it1;
        for(it1=(*it).begin();it1!=(*it).end();it1++)
             cout<<(*it1)<<" ";
        cout<<endl;
     }
    return 0;
}

注意:

迭代器it指向外层,it1指向(*it)即it当前的内容,最后用(*it1)可输出单个字符

迭代器类型可用“auto”关键字自动获得

for(auto it=board.begin();it!=board.end();it++){
        for(auto it1=(*it).begin();it1!=(*it).end();it1++)
             cout<<(*it1)<<" ";
        cout<<endl;
     }

 题目描述:

给定一个 m x n 二维字符网格 board 和一个字符串单词 word 

如果 word 存在于网格中,返回 true ;否则,返回 false 。

单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。

几点细节

本题的递归回溯查找需要注意几个细节

  • 每个位置只能用一次,所以需要建立数组标记访问标志
  • 由于给出的位置可能错误,所以下一次查找必须还原访问标志
  • 本题实际限定了上下左右四个方向搜索方向,更新位置需要注意

下面看一个简化问题

给你一个棋盘和一个初始位置,要求你从该位置沿着→↑←↓四个方向输出s

一:由于要判断是否越界,需要棋盘的长和宽

    vector<vector<char>> board;
    //棋盘的长和宽
    int m;
    int n;
    //函数判断字符是否越界
    bool isInArea(int i,int j) {return i>=0&&i<m&&j>=0&&j<n;}

二:由于要判定是否越界,定义访问数组

vector<vector<bool>> visited;

三:四个搜索方向用数组d[4][2]表示

 //定义四个方向
    int d[4][2]{
        {0,1},//右
        {-1,0},//上
        {0,-1},//左
        {1,0}//下
    };

初始化:

          m=board.size();
          n=board[0].size();
          visited= vector<vector<bool>>(m,vector<bool>(n,false));

核心代码----递归过程

void searchWord(int startX,int startY,string newword){
             visited[startX][startY]=true;
             word=newword+board[startX][startY];
            //四个方向搜索---前提:该字符当前位置符合要求
             for(int i=0;i<4;i++){
            //递归赋值,传入新变量
               int newX=startX+d[i][0];
               int newY=startY+d[i][1];
               if(isInArea(newX,newY)&&!visited[newX][newY]){
                  searchWord(newX,newY,word);
                }
             }//for
            return;
    }

私有变量---word记录返回结果,每次调用表明可访问,标记--true

沿着四个方向搜索,由于要递归,而且赋值,所以把更新值用新的变量记录

最后:递归要符合条件,也暗含了递归结束条件------在区域内+没访问

if(isInArea(newX,newY)&&!visited[newX][newY]){
                  searchWord(newX,newY,word);
                }

↑这个语句起始蕴含了递归结束的语句

完整程序:

#include<iostream>
#include<vector>
#include<string>
using namespace std;
class Solution {
private:
    //把棋盘设为私有变量
    vector<vector<char>> board;
    //棋盘的长和宽
    int m;
    int n;
    //函数判断字符是否越界
    bool isInArea(int i,int j) {return i>=0&&i<m&&j>=0&&j<n;}
    //定义棋盘元素是否访问
    vector<vector<bool>> visited;
    //定义四个方向
    int d[4][2]{
        {0,1},//右
        {-1,0},//上
        {0,-1},//左
        {1,0}//下
    };
    //定义字符
    string word;
    //递归函数实现
    void searchWord(int startX,int startY,string newword){
             visited[startX][startY]=true;
             word=newword+board[startX][startY];
            //四个方向搜索---前提:该字符当前位置符合要求
             for(int i=0;i<4;i++){
            //递归赋值,传入新变量
               int newX=startX+d[i][0];
               int newY=startY+d[i][1];
               if(isInArea(newX,newY)&&!visited[newX][newY]){
                  searchWord(newX,newY,word);
                }
             }//for
            return;
    }
public:
    void exist(vector<vector<char>>& board) {
          //为私有变量赋值
          this->board=board;
          m=board.size();
          n=board[0].size();
          visited= vector<vector<bool>>(m,vector<bool>(n,false));
          searchWord(2,0,"");
          cout<<word;
        
    }
};
int main(){
    vector<vector<char>> board={
        {'J','G','E','K','W'},
        {'T','R','Q','O','M'},
        {'U','S','N','P','L'}
    };
    Solution().exist(board);
    return 0;
}

代码实现 

class Solution {
private:
    int m,n;
    vector<vector<char>> board;
    string word;
    vector<vector<bool>> visited;
    int dire[4][2]{
        {-1,0},//上
        {0,1},//右
        {1,0},//下
        {0,-1}//左
    };
    bool isInArea(int i,int j)  {return i>=0&&i<m&&j>=0&&j<n;}
    bool searchWordHere(int startX,int startY,int index){
         //递归结束
         if(index==word.size()-1) return board[startX][startY]==word[index];
         //寻找
         if(board[startX][startY]==word[index]){
             //从四个方向寻找
             visited[startX][startY]=true;
             for(int i=0;i<4;i++){
                 int newX=startX+dire[i][0];
                 int newY=startY+dire[i][1];
                 if(isInArea(newX,newY)&&!visited[newX][newY]){
                     if(searchWordHere(newX,newY,index+1)) return true;
                 }
             }
             visited[startX][startY]=false;//这句话的位置--
         }
         return false;
    }
public:
    bool exist(vector<vector<char>>& board, string word) {
       //为私有变量传参
       this->board=board;
       this->word=word;
       m=board.size();
       n=board[0].size();
       visited=vector<vector<bool>>(m,vector<bool>(n,false));
       //从board任何一个位置开始找
       for(int i=0;i<m;i++)
         for(int j=0;j<n;j++) 
             if(searchWordHere(i,j,0)) return true;//一个找到了就结束
       return false;
    }
};

注意范围:

bool isInArea(int i,int j) {return i>=0&&i<m&&j>=0&&j<n;}---没有等号


if(当前字符符合了要求){

标记该字符已访问;

去四个方向寻找:{

if(更新的x,y位置在棋盘内而且没标记过“访问”)----递归条件

       进行递归;

}

查找失败还原该字符;

}

三:岛屿数量


class Solution {
private:
    int m,n;//grid的长和宽
    //定义已访问数组
    vector<vector<bool>> visited;
    //定义正确范围
    bool isInArea(int x,int y){ return x>=0&&x<m&&y>=0&&y<n;}
    //定义四个方向(随便)
    int d[4][2]={{1,0},{-1,0},{0,1},{0,-1}};
    //dfs---标记周围岛屿
    void dfs(vector<vector<char>>& grid,int x,int y){
       visited[x][y]=true;
       //四个方向搜索
       for(int i=0;i<4;i++){
           int newx=x+d[i][0];
           int newy=y+d[i][1];
           if(isInArea(newx,newy)&&!visited[newx][newy]&&grid[newx][newy]=='1')
           dfs(grid,newx,newy);
       }
       return;
    }
public:
    int numIslands(vector<vector<char>>& grid) {
        m=grid.size();
        if(m==0) return 0;
        n=grid[0].size();
        if(n==0) return 0;
        visited=vector<vector<bool>>(m,vector<bool>(n,false));
        int res=0;
        //从头到尾一次查
        for(int i=0;i<m;i++)
            for(int j=0;j<n;j++)
                if(!visited[i][j]&&grid[i][j]=='1'){
                     res++;
                     dfs(grid,i,j);
                }
        return res;
    }
};

和上面没啥区别-----就是需要注意值为1才继续

思路不难

依次遍历棋盘各个位置,只要没访问而且为1(岛屿),结果+1,然后dfs把其周围四个方向的标记已访问即可。

递归函数的判断条件是三个----在区域内+未访问+是岛屿

四:N皇后问题

 输入输出示范:

先简化问题:只要求不同行不同列即可 

class Solution {
private:
    //冲突数组
    vector<bool> col;//列是否冲突
    vector<vector<string>> res;//返回结果
    vector<string> generateBoard(int n,vector<int>& row){
        vector<string>board(n,string(n,'.'));
        for(int i=0;i<n;i++)
             board[i][row[i]]='Q';
        return board;
    }
    void putQueen(int n,int index,vector<int>& row){
    /*参数:
    n---n×n棋盘
    index---当前排列的是第几行
    row---存储结果,row[index]=k---当前行棋子应该排在第k列
    */
    if(index==n){//递归结束
        res.push_back(generateBoard(n,row));//把int转换成string
        return;
    }
    for(int i=0;i<n;i++){//挨个尝试能否放在第i列
        /*解决冲突*/
        if(!col[i]){
            row.push_back(i);
            col[i]=true;
            putQueen(n,index+1,row);
            col[i]=false;
            row.pop_back();
        }
    }
    return;
    }
public:
    vector<vector<string>> solveNQueens(int n) {
          res.clear();//清空一下
          col=vector<bool>(n,false);
          vector<int> row;
          putQueen(n,0,row);
          return res;
    }
};

void putQueen(int n,int index,vector<int>& row)

这个函数记录每一列要存放的位置,采用int存储,然后利用函数把string每行这个位置改成棋子即可。其中index记录当前行,如果和棋盘一致时,递归结束。

#include<iostream>
#include<string>
#include<vector>
using namespace std;
class Solution {
private:
    //私有函数--棋盘n*n以及返回结果res
    int n;
    vector<vector<string>> res;
    vector<bool> row;
    //函数---把棋子位置做改变
    vector<string> getOneRes(vector<int>& p){
        vector<string> board(n,string(n,'.'));//borad全是.
        for(int i=0;i<n;i++){
            board[i][p[i]]='Q';//p[i]--当前列的正确位置
        }
        return board;
    }
    void putCorrectPos(int index,vector<int>& p){
        if(index==n){
            //把一个正确结果存放到res中
            res.push_back(getOneRes(p));
            return;
        }
        for(int i=0;i<n;i++){
        if(!row[i]){//不冲突
            p.push_back(i);//取当前i
            row[i]=true;
            putCorrectPos(index+1,p);//递归
            //递归结束,还原标志
            row[i]=false;
            p.pop_back();
        }
        }
        return;
    }
public:
    vector<vector<string>> solveNQueens(int n) {
       this->n=n;
       if(n<=0) return res;
       row=vector<bool>(n,false);
       vector<int> p;//存放每一行需要放置的列的标号
       putCorrectPos(0,p);//index记录当前行号,用于结束递归
       return res;
    }
};
int main(){
    int n=3;
    vector<vector<string>> aa=Solution().solveNQueens(n);
    for(int i=0;i<n;i++){
        for(int j=0;j<n;j++)
          cout<<aa[i][j]<<" ";
        cout<<"***"<<endl;
    }
    return 0;
}

vector<String>特点

比如:

 string aa[4]={"aaa","ced","n","121"};
    vector<string> mm(aa,aa+4);
    for(int i=0;i<mm.size();i++){
           cout<<mm[i];
           cout<<endl;
    }

string每一个元素都是string,所以想访问具体元素,或者修改需要二重循环


 

 

对角线特点 

 

 发现----丿-相加为定值;捺-相减为定值(为了从0开始,避免负数,加上数组长度)

n×n的棋盘有2n-1条对角线个数

很简单---1+2+..+n+n-1+.....1

 完整代码:

#include<iostream>
#include<string>
#include<vector>
using namespace std;
class Solution {
private:
    //私有函数--棋盘n*n以及返回结果res
    int n;
    vector<vector<string>> res;
    vector<bool> row;
    //对角线
    vector<bool> dig1;
    vector<bool> dig2;
    //函数---把棋子位置做改变
    vector<string> getOneRes(vector<int>& p){
        vector<string> board(n,string(n,'.'));//borad全是.
        for(int i=0;i<n;i++){
            board[i][p[i]]='Q';//p[i]--当前列的正确位置
        }
        return board;
    }
    void putCorrectPos(int index,vector<int>& p){
        if(index==n){
            //把一个正确结果存放到res中
            res.push_back(getOneRes(p));
            return;
        }
        for(int i=0;i<n;i++){
        if(!row[i]&&!dig1[index+i]&&!dig2[index-i+n-1]){//不冲突
            p.push_back(i);//取当前i
            row[i]=true;
            dig1[index+i]=true;
            dig2[index-i+n-1]=true;
            putCorrectPos(index+1,p);//递归
            //递归结束,还原标志
            row[i]=false;
            dig1[index+i]=false;
            dig2[index-i+n-1]=false;
            p.pop_back();
        }
        }
        return;
    }
public:
    vector<vector<string>> solveNQueens(int n) {
       this->n=n;
       if(n<=0) return res;
       row=vector<bool>(n,false);
       dig1=vector<bool>(2*n-1,false);
       dig2=vector<bool>(2*n-1,false);
       vector<int> p;//存放每一行需要放置的列的标号
       putCorrectPos(0,p);//index记录当前行号,用于结束递归
       return res;
    }
};
int main(){
    int n=5;
    vector<vector<string>> aa=Solution().solveNQueens(n);
    for(auto mm=aa.begin();mm!=aa.end();mm++){
       for(auto nn=(*mm).begin();nn!=(*mm).end();nn++)
       cout<<(*nn)<<endl;
       cout<<"******"<<endl;
    }
    return 0;
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值