回溯法
回溯法可以看成暴力法的升级,它从解决问题每一步的所有可能选项里系统地选择出一个可行的解决方案。回溯法非常适合由多个步骤组成的问题,并且每个步骤都有多个选项。当我们在某一步选择了其中一个选项时,就进入下一步,然后又面临新的选项。我们就这样重复选择,直到到达最终的状态。
用回溯法解决的问题的所有选项可以形象地用树状结构表示。在某一步有 n n n个可能的选项,那么该步骤可以看成是树状结构中的一个节点,每个选项看成树中节点连接线,经过这些连接线到达该节点的 n n n个子节点。树的叶节点对应着终结状态。如果在叶节点的状态满足该题目的约束条件,那么我们找到了一个可行的解决方案。
如果在叶节点的状态不满足约束条件,那么只好回溯到它的上一个节点再尝试其他的选项。如果上一个节点所有可能的选项都已经尝试过,并且不能到达满足约束条件的终结状态,则再次回溯到上一个节点。如果所有节点的所有选项都已经尝试过仍然不能到达满足约束条件的终结状态,则该问题无解。
12 矩阵中的路径
自己没写出来,放弃,直接看题解。
题解思路:
是典型的矩阵搜索问题,可以使用深度优先搜索(DFS)+ 剪枝解决。
- 深度优先搜索:可以理解为暴力法遍历矩阵中所有字符串可能性。DFS通过递归,先朝一个方向搜到底,再回溯到上一个节点,沿另一个方向搜索,以此类推。
- 剪枝:在搜索中,遇到这条路不可能和目标字符串匹配成功的情况,则应立即返回,称之为可行性剪枝。
class Solution {
public:
bool exist(vector<vector<char>>& board, string word) {
int row = board.size();
int column = board[0].size();
for(int i=0;i<row;i++){
for(int j=0;j<=column;j++){
if(find(board,i,j,0,word)){
return true;
}
}
}
return false;
}
bool find(vector<vector<char>>& board,int i,int j,
int pos,string word){//要确保传过来的newWord不是空的
int row = board.size();
int column = board[0].size();
if(i<0||i>=row||j<0||j>=column||board[i][j]!=word[pos]){
return false;
}
if(pos==word.size()-1){
return true;
}
board[i][j]=' ';
bool res=find(board,i,j-1,pos+1,word)||find(board,i,j+1,pos+1,word)||
find(board,i-1,j,pos+1,word)||find(board,i+1,j,pos+1,word);
//这里从第一步来考虑,如果上下左右都不符合,应该恢复board,返回false
//如果有符合的,最终也要恢复board,但是就返回true了
//一步一步考虑
board[i][j]=word[pos];
return res;
}
};
时间复杂度
O
(
3
K
M
N
)
O(3^KMN)
O(3KMN):最差情况下,需要遍历矩阵中长度为
K
K
K的字符串的所有方案,时间复杂度为
O
(
3
K
)
O(3^K)
O(3K);矩阵中共有MN个起点,时间复杂度为
O
(
M
N
)
O(MN)
O(MN)。
方案数计算:设字符长度为 K K K,搜索中每个字符有上、下、左、右四个方向可以选择,舍弃回头(上个字符)的方向,剩下3中选择,因此方案数的复杂度是 O ( 3 K ) O(3^K) O(3K)。
空间复杂度 O ( K ) O(K) O(K) : 搜索过程中的递归深度不超过 K K K ,因此系统因函数调用累计使用的栈空间占用 O ( K ) O(K) O(K)(因为函数返回后,系统调用的栈空间会释放)。最坏情况下 K = M N K=MN K=MN ,递归深度为 M N MN MN ,此时系统栈使用 O ( M N ) O(MN) O(MN) 的额外空间。
13 机器人的运动范围
广度优先(使用队列)
class Solution {
int getSum(int x){
int res=0;
while(x){
res += x%10;
x /= 10;
}
return res;
}
public:
int movingCount(int m, int n, int k) {
if(m<0||n<0){
return -1;
}
if(k==0){
return 1;
}
queue<pair<int,int> > Q;
//向右和向下的数组
int dx[2]={0,1};
int dy[2]={1,0};
vector<vector<int> > vis(m,vector<int>(n,0));
int ans=1;
Q.push(make_pair(0,0));
vis[0][0]=1;
while(!Q.empty()){
auto [x,y] = Q.front();
Q.pop();
for(int i=0;i<2;i++){
int tx = x+dx[i];
int ty = y+dy[i];
//不符合条件
if(tx<0||tx>=m||ty<0||ty>=n||vis[tx][ty]||getSum(tx)+getSum(ty)>k){
//vis这个数组要在判断完tx,ty没有越界之后再判断!!!
continue;
}
ans++;
Q.push(make_pair(tx,ty));
vis[tx][ty]=1;
}
}
return ans;
}
};
深度优先(使用栈)
class Solution {
int getSum(int x){
int res=0;
while(x){
res += x%10;
x /= 10;
}
return res;
}
public:
int movingCount(int m, int n, int k) {
int tx;
int ty;
if(m<0||n<0){
return -1;
}
if(k==0){
return 1;
}
stack<pair<int,int> > S;
//向右和向下的数组
int dx[2]={0,1};
int dy[2]={1,0};
vector<vector<int> > vis(m,vector<int>(n,0));
int ans=1;
S.push(make_pair(0,0));
vis[0][0]=1;
while(!S.empty()){
auto [x,y] = S.top();
S.pop();
for(int i=0;i<2;i++){
tx = x+dx[i];
ty = y+dy[i];
//不符合条件
if(tx<0||tx>=m||ty<0||ty>=n||vis[tx][ty]||getSum(tx)+getSum(ty)>k){
//vis这个数组要在判断完tx,ty没有越界之后再判断!!!
continue;
}
ans++;
S.push(make_pair(tx,ty));
vis[tx][ty]=1;
}
}
return ans;
}
};
时间复杂度
O
(
m
n
)
O(mn)
O(mn):考虑所有格子都能进入,那么搜索的时候一个格子最多会被访问的次数是常数。
空间复杂度 O ( m n ) O(mn) O(mn):搜索的时候需要一个大小为 m n mn mn的数组来表示该格子是否被访问过。标记数组。