目录
一:求解
对于这种回溯求解值的可能问题,画出递归树是关键
如图:与排列问题不同,每个分支结束了,下一次的递归调用不要把这个数包含。
也就是:考虑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; }