面试题12 13是接连的两道回溯法的题。
先上定义:
回溯法按深度优先策略搜索问题的解空间树。首先从根节点出发搜索解空间树,当算法搜索至解空间树的某一节点时,先利用剪枝函数判断该节点是否可行(即能得到问题的解)。如果不可行,则跳过对该节点为根的子树的搜索,逐层向其祖先节点回溯;否则,进入该子树,继续按深度优先策略搜索。
回溯法的基本行为是搜索,搜索过程使用剪枝函数来为了避免无效的搜索。剪枝函数包括两类:1. 使用约束函数,剪去不满足约束条件的路径;2.使用限界函数,剪去不能得到最优解的路径。
回溯法说白了就是递归,递归法算法简洁且执行效率高,但是与之相应的就是递归法一般都很抽象,很难想。
回溯法的经验小结:
- 出口: 出口就是判定条件,一般放在代码第一行,这样执行到终止条件的递归函数可以优先判定,避免有一些条件,内容的更改。
- 递归函数的参数设置: 这个参数是随着每一次的递归操作而发生改变的。而回溯法很关键的一点就是:如果当前操作行不通,如何回溯到上一步操作。所以不要改变参数,如下不可
for index in range(nums):
Flag = conflict(queen_str, index)
# 如果当前位置的皇后是否与之前所有位置的皇后没有冲突,则执行下述代码
if Flag is False:
queen_str = queen_str+str(index)
back(queen_str )
如下可:
for index in range(nums):
Flag = conflict(queen_str, index)
# 如果当前位置的皇后是否与之前所有位置的皇后没有冲突,则执行下述代码
if Flag is False:
back(queen_str+str(index))
典型例题:
剑指offer上的面试题12:请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一格开始,每一步可以在矩阵中向左、右、上、下移动一格。如果一条路径经过了矩阵的某一格,那么该路径不能再次进入该格子。例如,在下面的3×4的矩阵中包含一条字符串“bfce”的路径(路径中的字母用加粗标出)。
[["a","b","c","e"],
["s","f","c","s"],
["a","d","e","e"]]
但矩阵中不包含字符串“abfb”的路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入这个格子。
算法设计很考究。
class Solution {
public:
bool exist(vector<vector<char>>& board, string word) {
auto s=word.begin();
for(int i=0;i!=board.size();++i){
for(int j=0;j!=board[i].size();++j){
if(dfs(i,j,board,word,0)) return true;//找到起始位置开始迭代
}
}
return false;
}
bool dfs(int i,int j,vector<vector<char>>& board,string word,int k){
if(i<0||i>=board.size()||j<0||j>=board[0].size()||board[i][j]!=word[k])
return false; //判断句放前,false放在true前面,否则不对
if(k==word.size()-1)return true; //唯一正确判定方式
char tmp=board[i][j];
board[i][j]='/'; //如果过了false判定,就说明可以递归,先更改值,再下面改回来
bool res=dfs(i-1,j,board,word,k+1)||dfs(i+1,j,board,word,k+1)||dfs(i,j- 1,board,word,k+1)||dfs(i,j+1,board,word,k+1);
board[i][j]=tmp;
return res;
}
};