递归和回溯:
17 电话号码
class Solution {
public:
vector letterCombinations(string digits) {
string letters[10] = { "", "",
"abc",
"def",
"ghi",
"jkl",
"mno",
"pqrs",
"tuv",
"wxyz" };
ret.clear();
if (digits.size() == 0)
return ret;
getLetters(digits, 0, "", letters);
return ret;
}
private:
vector ret;
void getLetters(string &digits, int index, const string temp, string letters[]){
if (index == digits.size()){
cout << "push: " << temp << endl;
ret.push_back(temp);
return;
}
int dig = digits[index] - '0';
string letter = letters[dig];
int n = letter.size();
for (int i = 0; i
思路:
运用递归去实现回溯算法的思想。
回溯算法本质上是一种暴力搜索,通过递归调用去实现,回溯思想与深度搜索思想一致。
对于本题,解法设计思路:
首先定义一个查找表去映射数字和字母之间的关系。
之后运用回溯的算法思想,通过递归去设计,解题思路可以通过画一个树形图来梳理,每次取出digits中的一个元素,然后分别讨论每一种选择(for循环选中每一次选择),然后递归。
与广度优先搜索的区别是深搜是一条路跑到黑的,除非找到最后,否则回一直找下去。
最后添加一个if是为了处理取数字1的时候字符串为空的情况,不实现这部分leetcode上也可以通过,但是输入“12”的话不会返回“abc”,因为此时没有进入递归
另外就是cpp不支持类中的变量初始化定义,所以把letters其做成了一个参数
练习:93 131
46 全排列
class Solution {
public:
vector> permute(vector& nums) {
int n=nums.size();
ret.clear();
if(n==0)
return ret;
used=vector(n+1,false);
vector temp;
getPermute(nums,used,temp);
return ret;
};
private:
vector> ret;
vector used;
void getPermute(vector &nums,vector &used,vector &temp){
if(temp.size()==nums.size()){
ret.push_back(temp);
return ;
}
int n=nums.size();
for(int i=0;i
思路:
利用回溯法:
先画一个树形图,来判断需要用到哪些状态变量去控制。
通过画树形图,可以知道,首先我们要判断当前选择的元素之前有没有使用过,因此需要used集合来记录状态。每一次选择后再之后便不再选择,因此在递归调用的时候需要使用if语句进行判断。
练习:47
77 组合
class Solution {
public:
vector> combine(int n, int k) {
ret.clear();
if(n==0)
return ret;
vector temp;
getCombine(n,1,k,temp);
return ret;
};
private:
vector> ret;
void getCombine(int n,int index,int k,vector &temp){
//找到了一个组合,将其记录下来,退出递归
if(temp.size()==k){
ret.push_back(temp);
return;
}
//递归已经达到了边界,依然没有找到组合,直接退出递归,其实不写也没事
if(index==n+1)
return;
//每一层的选项,从index开始依次往后选,index前面的不用再考虑了,否则会有重复,另外i<=n可以优化为n-i+1>=k-temp.size(),进行剪枝操作,提高算法效率
for(int i=index;i<=n;i++){
temp.push_back(i);
getCombine(n,i+1,k,temp);
temp.pop_back();
}
}
};
思路:
利用回溯法:
- 画树形图,找出需要辅助实现算法的变量,每一层的选项,递归关系,终止条件
- 关键点已在代码中进行了注释
练习:39 40 216 78 90 401
二维平面上的回溯法:
79 搜索单词
错误代码,没有检查出哪里错误,会超时
class Solution {
public:
bool exist(vector>& board, string word) {
row=board.size();
if(row==0)
return false;
col=board[0].size();
if(col==0)
return false;
used=vector>(row,vector(col,false));
for(int i=0;i> used;
bool inArea(int x,int y){
return x>=0 && x<=row-1 && y>=0 && y<=col-1;
}
bool getRet(vector> &board,string &word,int index,int x,int y){
if(index==word.size()-1)
{
cout<<"return final: "<
正确代码
class Solution {
public:
bool exist(vector>& board, string word) {
m=board.size();
if(m==0)
return false;
n=board[0].size();
used=vector>(m,vector(n,false));
for(int i=0;i> used;
bool inArea(int x,int y){
return x>=0 && x=0 && y> &board,string &word,int index,int posX,int posY){
if(index+1==word.size())
return board[posX][posY]==word[index];
if(board[posX][posY]==word[index]){
used[posX][posY]=true;
for(int i=0;i<4;++i){
int newX=posX+d[i][0];
int newY=posY+d[i][1];
if(inArea(newX,newY)==false || used[newX][newY]==true)
continue;
if(search(board,word,index+1,newX,newY))
return true;
}
used[posX][posY]=false;
}
return false;
}
};
思路:
二维平面采用回溯算法利用一个二维数组即可,一般来讲需要判断边界
本题解题思路:
先设计一个函数getWord,用来判断board从x,y开始,是否有匹配word从index开始算起的单词。
之后通过双重循环去遍历二维平面,考虑每一个点作为起点的情况。
getWord的设计思路:
采用递归来设计,或者说回溯的算法思想,回溯的算法思想可以分为2部分,1部分是递归,1部分是复原状态遍历,因为回溯就是回滚状态,每深搜完一个路径往回回溯的时候状态也要回滚。
递归的话要有终止条件和递归过程。
首先我们可以画一个树形图,去整体上查看递归的调用链。
通过画图可以明确,我们需要判断边界,需要记录要探索的点是否已被访问过,需要判断当前点是否符合要求,终止条件是index达到最后word的最大下标的时候。
getWord流程:
先判是不是达到终止条件,是则返回
再判当前点是否符合要求,不符合则返回
当前点符合要求,那么记录访问状态,开始递归相邻点(共4种情况,需要考虑边界约束和访问约束,对符合条件的相邻点进行深搜递归,),若有1个符合要求,即可返回true
遍历完所有相邻节点后若都不符合,则回复访问状态,返回false(进行回溯)
200 岛屿数量
思路:
- dfs
- 回溯法,利用深度优先搜索进行遍历并标记,最后统计
class Solution {
public:
int numIslands(vector>& grid) {
row=grid.size();
if(row==0)
return 0;
col=grid[0].size();
if(col==0)
return 0;
int ret=0;
vector> used=vector>(row,vector(col,false));
for(int i=0;i> used;
bool isContain(int x,int y){
return x>=0 && x=0 && y > &grid,int x,int y,vector> &used){
used[x][y]=true;
int d[4][2]={
{0,1},
{0,-1},
{1,0},
{-1,0}
};
for(int i=0;i<4;i++){
int newX=d[i][0];
int newY=d[i][1];
newX+=x;
newY+=y;
if(isContain(newX,newY) && used[newX][newY]==false && grid[newX][newY]=='1')
dfs(grid,newX,newY,used);
}
};
};
代码解释:
- 首先主函数通过调用dfs来标记岛屿
- 写了2个子函数,isContain用来判断是否坐标越界,dfs用来使用深度优先搜索遍历整片岛屿并标记
- 主函数开始遍历整个grid,一旦发现代表岛屿的格子‘1’,就利用dfs遍历整片岛屿并标记(遍历条件,未越界,之前未访问过,‘1’,),然后计数(ret++)
经验:
- 编写算法可以主函数、子函数交替进行,这样逻辑更加清晰。
- 注意数据类型,比如本题grid存的是char,若写代码时忘记加单引号,会出bug,得不到正确的结果
练习 130 417
51 八皇后
思路:
- 找到判断条件(同行、同列,主对角线,副对角线的判定方式)
- 回溯法(dfs,去遍历各种组合)
class Solution {
public:
vector> solveNQueens(int n) {
ret.clear();
div=vector(2*n,false);
ndiv=vector(2*n,false);
col=vector(n,false);
m=n;
vector temp;
string line;
for(int i=0;i> ret;
vector div,ndiv,col;
int m;
void pos(int row,vector &temp){
if(row==m){
ret.push_back(temp);
return;
}
for(int i=0;i
代码设计详解:
- 已经决定使用回溯法去解决该问题,回溯法其实就是dfs,分支是下一步的备选方案,每次会从根节点到叶子节点处递归一条路径
- 通过div,ndiv,col来判断当前位置是否与已放置棋子的地方有冲突(分别判断主对角线,副对角线,列),行不用判断是因为每次都放的是不同行,一定不会冲突。
- 注意递归调用下一步时进行状态更新,回溯时记得恢复状态。另外就是递归思路的设计,设计好退出条件。
练习 52 37