DFS深度优先

岛屿最大面积

image-20220316151223454

深度优先算法的重要思想就是借助栈每次访问栈顶的元素

递归就无需我们手动压入栈当中了,我们需要做的是判断栈顶元素是否已经访问,并且每次递归调用(压入栈)的应该是栈顶元素的邻接节点

这道题非常的特殊,0表示没有岛屿,1表示是岛屿,那么每次压入栈的邻接节点应该值应是1的,并且可以通过将已经访问的元素置为0,表示栈顶元素已经访问以避免重复访问。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class Solution {
int des[4][2] = { {1,0},{-1,0},{0,1},{0,-1} };
int m,n;
public:
int maxAreaOfIsland(vector<vector<int>>& grid) {
int maxs = 0;
m = grid.size();
n = grid[0].size();
for(int i = 0;i < m;i++){
for(int j = 0;j < n;j++){
int s = 0;
if(grid[i][j] == 1){
dfs(i,j,grid,s);
maxs = max(maxs,s);
}
}
}
return maxs;
}
void dfs(int i,int j,vector<vector<int>>& grid,int &s){
grid[i][j] = 0;
s++;
for (int k = 0; k < 4; k++) {
int tmpi = i + des[k][0];
int tmpj = j + des[k][1];
if (tmpi < 0 || tmpi >= m || tmpj < 0 || tmpj >= n || grid[tmpi][tmpj] == 0)continue;
dfs(tmpi, tmpj, grid,s);
}
}
};

省份数量

image-20220316153543490

记住dfs的本质:借助栈来遍历,每次访问栈顶的元素,并且将栈顶元素的邻接节点加入栈中,注意每次访问的时候要判断栈顶元素是否已经访问过了。

而我们借助递归,无需我们手动构建栈了,但是一定要注意,递归参数就是邻接节点,

dfs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Solution {
int m, n;
vector<bool>visited;
public:
int findCircleNum(vector<vector<int>>& isConnected) {
m = isConnected.size();
n = isConnected[0].size();
visited = vector<bool>(m,false);
int sum = 0;
for(int i = 0;i < m;i++){
if(!visited[i]){
sum++;
dfs(isConnected,i);
}
}
return sum;
}
void dfs(vector<vector<int>>& isConnected,int cur){
visited[cur] = true;
for(int i = 0;i < n;i++){
if(visited[i] == true || isConnected[cur][i] == 0)continue;
dfs(isConnected,i);
}
}
};

回溯感觉讲究的是所有可能的方向都枚举出来,然后对部分枚举结果进行剪枝操作(即做一定的选择)。而且后面一定要撤销之前的选择。

1
2
3
4
5
6
7
8
9
10
result = []
def backtrack(路径, 选择列表):
if 满足结束条件:
result.add(路径)
return

for 选择 in 选择列表:
做选择
backtrack(路径, 选择列表)
撤销选择

这一题关键在于for循环对所有的数组所有的值进行递归操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Solution {
public:
vector<vector<int>>ans;
vector<vector<int>> permute(vector<int>& nums) {
vector<int>res;
vector<bool> visited(nums.size(),false);
dfs(nums,0,res,visited);
return ans;
}
void dfs(vector<int>& nums,int s,vector<int>&res,vector<bool>&visited){
if(s == nums.size()){
ans.push_back(res);
return;
}
for(int i = 0;i < nums.size();i++){
if(!visited[i]){
visited[i] = true;
res.push_back(nums[i]);
dfs(nums,s+1,res,visited);
res.pop_back();
visited[i] = false;
}
}
}
};

130. 被围绕的区域

image-20220417114113198

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class Solution {
vector<vector<bool>>visited;
int m, n;
int des[4][2] = { {1,0},{-1,0},{0,1},{0,-1} };
public:
void solve(vector<vector<char>>& board) {
m = board.size();
n = board[0].size();
visited = vector<vector<bool>>(m,vector<bool>(n,false));
for(int i = 0;i < m;i++){
for(int j = 0;j < n;j++){
if((i == 0 || i == m-1 || j==0 || j == n-1 )&& (board[i][j] == 'O'))//边缘且为'O'
dfs(board,i,j);
}
}

for(int i = 1;i < m - 1;i++){//非边缘节点
for(int j = 1;j < n - 1;j++){
if(board[i][j] == 'O' && visited[i][j] == false)board[i][j] = 'X';
}
}
return;
}
void dfs(vector<vector<char>>& graph, int i, int j) {
if (i < 0 || i >= m || j < 0 || j >= n || visited[i][j] || graph[i][j] == 'X')return;
visited[i][j] = true;
for (int k = 0; k < 4; k++) {
int tmpi = i + des[k][0];
int tmpj = j + des[k][1];
dfs(graph, tmpi, tmpj);
}
}
};

417. 太平洋大西洋水流问题

image-20220427155214165

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class Solution {
public:
vector<vector<bool>>visited1;
vector<vector<bool>>visited2;
int des[4][2] = {{0,1},{1,0},{0,-1},{-1,0}};
int m,n;
vector<vector<int>> pacificAtlantic(vector<vector<int>>& heights) {
m = heights.size();n = heights[0].size();
visited1 = vector<vector<bool>>(m,vector<bool>(n,false));
visited2 = vector<vector<bool>>(m,vector<bool>(n,false));
for(int i = 0;i < m;i++)dfs(heights,visited1,i,0);
for(int i = 0;i < n;i++)dfs(heights,visited1,0,i);
for(int i = 0;i < m;i++)dfs(heights,visited2,i,n-1);
for(int i = 0;i < n;i++)dfs(heights,visited2,m-1,i);
vector<vector<int>>results;
for(int i = 0;i < m;i++){
for(int j = 0;j < n;j++){
if(visited1[i][j] && visited2[i][j]){
results.push_back(vector<int>{i,j});
}
}
}
return results;
}
void dfs(vector<vector<int>>& heights,vector<vector<bool>>&visited,int i,int j){
visited[i][j] = true;
for(int k = 0;k < 4;k++){
int tmpi = i + des[k][0];
int tmpj = j + des[k][1];
if(tmpi < 0 || tmpj < 0 || tmpi >= m || tmpj >= n
|| visited[tmpi][tmpj] || heights[tmpi][tmpj] < heights[i][j])continue;
dfs(heights,visited,tmpi,tmpj);
}
}
};

回溯与DFS区别:

例如对于一个遍历矩阵

第一次遍历如下

image-20220427104604739

如果是回溯,那么下一次遍历还可能访问C和D

image-20220427104656120

如果是图visited[i] = true放在for前面,如果是数组选择的那么visited[i] = true放在for后面

回溯

回溯法解决的问题都可以抽象为树形结构,因为回溯法解决的都是在集合中递归查找子集,集合的大小就构成了树的宽度,递归的深度,都构成的树的深度

递归树上的每一个节点包括1.子集(当前遍历路径上的所有元素构成子集)和2.剩余集合(剩下可以选择的元素构成集合)

1
2
3
4
5
6
7
8
9
10
11
12
void backtracking(参数) {
if (终止条件) {
存放结果;
return;
}

for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
处理节点;不符合条件的continue
backtracking(路径,选择列表); // 递归
回溯,撤销处理结果
}
}
  • 1.递归树

  • 2.终止条件:

  • 3.选择列表

  • 4.判断是否需要剪枝

  • 5.做出选择

  • 6.撤销选择

全排列

image-20220324155557517

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Solution {
public:
vector<vector<int>>ans;
vector<int>res;
vector<bool>visited;
vector<vector<int>> permute(vector<int>& nums) {
vector<bool>v(nums.size(),false);
visited = v;
dfs(nums,0);
return ans;
}
void dfs(vector<int>& nums,int s){
if(s == nums.size()){//终止条件
ans.push_back(res);
return;
}
for(int i = 0;i < nums.size();i++){//所有元素都要来一次选择
if(visited[i])continue;//重复元素,要删减枝叶

else{
visited[i] = true;
res.push_back(nums[i]);
dfs(nums,s+1);
res.pop_back();
visited[i] = false;
}
}
}
};

全排列2

image-20220324162916881

我们需要将其重新排序,并且仅仅需要在多加一个判断
else if(i > 0 && nums[i]==nums[i-1] && !visited[i-1])continue;//前面重复的1被选择了但被撤销了应该删除
为什么是这样一个判断,因为这个判断是用于删除同一层的分支的,避免删除同一列的分支情况

image-20220324162846017

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class Solution {
public:
vector<vector<int>>ans;
vector<int>res;
vector<bool>visited;
vector<vector<int>> permuteUnique(vector<int>& nums) {
visited = vector<bool>(nums.size(),false);

sort(nums.begin(),nums.end());
dfs(nums,0);
return ans;
}
void dfs(vector<int>& nums,int s){
if(s == nums.size()){//终止条件
ans.push_back(res);
return;
}
for(int i = 0;i < nums.size();i++){//所有元素都要来一次选择
if(visited[i])continue;//重复元素,要删减枝叶
else if(i > 0 && nums[i]==nums[i-1] && !visited[i-1])continue;//前面重复的1被选择了但被撤销了应该删除
else{
visited[i] = true;
res.push_back(nums[i]);
dfs(nums,s+1);
res.pop_back();
visited[i] = false;
}
}
}
};

78. 子集(元素互不相同)

image-20220324170820601

谨记回溯模板

1
2
3
4
5
6
7
8
9
10
11
12
void backtracking(参数) {
if (终止条件) {
存放结果;
return;
}

for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
处理节点;不符合条件的continue
backtracking(路径,选择列表); // 递归
回溯,撤销处理结果
}
}

子集问题稍有变换,本质还是这个模板,终止条件存放结果,未终止的话就处理节点递归

image-20220324171048866

子集对于每一个中间过程都应该存入,因此没有终止条件,每一个过程节点都应存放结果

对于本层集合中元素,是上一个元素的后一位置开始选择而不是从0开始

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
public:
vector<vector<int>>res;
vector<int>r;

vector<vector<int>> subsets(vector<int>& nums) {
dfs(nums,0);
return res;
}
void dfs(vector<int>&nums,int start){
res.push_back(r);
for(int j = start;j < nums.size();j++){
r.push_back(nums[j]);
dfs(nums,j+1);
r.pop_back();
}
}
};

90. 子集 II(含相同元素)

image-20220324171946727

类似的像全排列2那样新加判断即可

if(i > start&&nums[i] == nums[i-1])continue;这个判断是用于删除同一层的分支的,避免删除同一列的分支情况

image-20220324172533013

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution {
public:
vector<vector<int>>res;
vector<int>r;

vector<vector<int>> subsetsWithDup(vector<int>& nums) {
sort(nums.begin(),nums.end());
dfs(nums,0);
return res;
}

void dfs(vector<int>&nums,int start){
res.push_back(r);
for(int i = start;i < nums.size();i++){
if(i > start&&nums[i] == nums[i-1])continue;
r.push_back(nums[i]);
dfs(nums, i + 1);
r.pop_back();
}
}
};

77. 组合

给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。

你可以按 任何顺序 返回答案。

示例 1:

1
2
3
4
5
6
7
8
9
10
输入:n = 4, k = 2
输出:
[
[2,4],
[3,4],
[2,3],
[1,2],
[1,3],
[1,4],
]

示例 2:

1
2
输入:n = 1, k = 1
输出:[[1]]

提示:

  • 1 <= n <= 20
  • 1 <= k <= n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class Solution {
void printvector(vector<int>path,string l,string r){
cout << l;
for(auto v : path){
cout << v << ", ";
}
cout <<r<<endl;
}
vector<vector<int>>results;
vector<int>path;
vector<bool>visited;
int K;
void dfs(int n,int h, int start){
if(h == K){//终止条件
results.push_back(path);
return;
}
for(int i = start;i <= n ;i++){//所有元素都要来一次选择
if( (K - (int)path.size()) + i - 1 > n)break;//剪枝
path.push_back(i);
//printvector(path,"递归之前:[","]");
dfs(n,h+1,i+1);
path.pop_back();
//printvector(path,"递归之后:[","]");
}
}
public:
vector<vector<int>> combine(int n, int k) {
K = k;
visited = vector<bool>(n,false);
dfs(n, 0, 1);
return results;
}
};

组合总和(数字可重复选取)

image-20220324173941314

  • 1.递归树:

image-20220324174131273

  • 2.终止条件:

当路径总和等于 target 时候,就应该把路径加入结果集,并 return
路径和大于target时候,直接return

  • 3.选择列表

for(int i=start;i<nums.size();i++)

  • 4.判断是否需要剪枝

sum>target的时候可以剪枝,但是放到终止那里也可以

  • 5.做出选择

题中说数可以无限次被选择,那么 i 就不用 +1 。即下一层的选择列表,从自身开始。并且要更新当前状态的sum

  • 6.撤销选择

pop_back()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Solution {
public:
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
dfs(candidates,target,0,0);
return res;
}

vector<vector<int>>res;
vector<int>r;
void dfs(vector<int>& candidates, int target,int sum,int start){
if(sum == target){
res.push_back(r);
return;
}
else if(sum > target){
return;
}
else{
for(int i = start;i < candidates.size();i++){
r.push_back(candidates[i]);
dfs(candidates,target,sum+candidates[i],i);//i无需加1是因为数字可以重复选取
r.pop_back();
}
}
}
};

组合总数2(数字不可重复选取)

image-20220324174913403

不可重复选取的问题都要先进行排列,然后对于在同一层的进行判断是否要进行剪枝操作。if(i > start && candidates[i] == candidates[i-1])continue;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Solution {
public:
vector<vector<int>>res;
vector<int>r;
vector<bool>visited;
vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {

sort(candidates.begin(),candidates.end());
dfs(candidates,target,0,0);
return res;
}

void dfs(vector<int>& candidates, int target,int sum,int start){
if(target == sum){
res.push_back(r);
return;
}
else if(sum > target){
return;
}
else{
for(int i = start;i < candidates.size();i++){
if(i > start && candidates[i] == candidates[i-1])continue;

r.push_back(candidates[i]);
dfs(candidates,target,sum+candidates[i],i+1);
r.pop_back();
}
}
}
};

216. 组合总和 III

找出所有相加之和为 n 的 k 个数的组合,且满足下列条件:

  • 只使用数字1到9

  • 每个数字 最多使用一次

返回 所有可能的有效组合的列表 。该列表不能包含相同的组合两次,组合可以以任何顺序返回。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Solution {

vector<vector<int>>results;
vector<int>path;
vector<bool>visited;
int target;
int k;
void dfs(int h, int start, int curSum){
if(h == k){//终止条件
if(curSum == target)results.push_back(path);
return;
}
for(int i = start;i <= 9;i++){//所有元素都要来一次选择
if(curSum + i > target)continue;//剪枝
path.push_back(i);
//printvector(path,"递归之前:[","]");
dfs(h+1,i+1, curSum + i);
path.pop_back();
//printvector(path,"递归之后:[","]");
}
}
public:
vector<vector<int>> combinationSum3(int k, int n) {
this->k = k;
target = n;
dfs(0, 1, 0);
return results;
}
};

复原IP地址

  • 递归树

image-20220324181002298

  • 2.终止条件:

已经切分成了四段

  • 3.选择列表

[start,start+3)

  • 4.判断是否需要剪枝

if( start + j + (3 - strs.size() )*3 < s.size())continue;的时候可以剪枝,(即ip长度无法覆盖整个字符串)

if((tmp.size() == 3 && tmp > "255")|| (start+j>s.size())||(tmp.size()>1&&tmp[0] == '0'))continue;地址不合法的时候可以剪枝

  • 5.做出选择

下一层的选择列表,从这层开始地址+的这层剪的长度开始start+j。并且要更新当前状态的strs

  • 6.撤销选择

pop_back()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class Solution {
public:
vector<string> restoreIpAddresses(string s) {
vector<string>strs;
dfs(s,strs,0);
return ans;
}
vector<string>ans;
void dfs(string s,vector<string>&strs,int start){
if(strs.size() == 4){
string tmp;
for(auto e : strs)
tmp = tmp + "." + e;

ans.push_back(tmp.substr(1));
return;
}
else{
for(int j = 1;j <= 3;j++){
string tmp = s.substr(start,j);

if( start + j + (3 - strs.size() )*3 < s.size())continue;

if((tmp.size() == 3 && tmp > "255")||
(start+j>s.size())||(tmp.size()>1&&tmp[0] == '0'))continue;

strs.push_back(s.substr(start,j));
dfs(s,strs,start+j);
strs.pop_back();
}

}
}
};

分割回文串

image-20220328100405547

实在是感慨回溯的强大之处啊!!刚开始没有意识到使用回溯算法,更没有意识到如何切割。

  • 回溯递归树

image-20220328100638969

  • 2.终止条件:

切取的指针已经到达了字符串末尾,无法继续切取

  • 3.选择列表

[start,start+1)~~[start,s.length()-1)若干区间都可选取

  • 4.判断是否需要剪枝

选取的区间不满足回文串的要求,那么就continue

  • 5.做出选择

下一层的选择列表,从这层开始地址+的这层剪的长度开始start+i。并且要更新当前状态的strs

  • 6.撤销选择

pop_back()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class Solution {
public:
vector<vector<string>>results;
vector<string>r;
vector<vector<string>> partition(string s) {
dfs(s,0);
return results;
}
void dfs(string s,int start){
if(start == s.length()){
results.push_back(r);
return;
}
for(int i = 1;i <= s.length() - start;i++){
string tmp = s.substr(start,i);
if(!isHuiWen(tmp))continue;
else{
r.push_back(tmp);
dfs(s,start+i);
r.pop_back();
}
}
}
bool isHuiWen(string s){
for (int i = 0, j = s.length() - 1; i < j; i++, j--) {
if (s[i] != s[j]) {
return false;
}
}
return true;
}
};

51. N 皇后

image-20220325152041441

想明白递归树每一层应该存放什么,可以先暴力的思考,在稍微进行剪枝。这里每一层存放的就是这一行的皇后摆放的位置

  • 1.递归树

image-20220325150703585

  • 2.终止条件:

棋盘的n行都已经填好了queen

  • 3.选择列表

[0,N)每一行的每一个位置都可以填

  • 4.判断是否需要剪枝

判断新填入的皇后是否会互相攻击,如果会互相攻击就剪掉

  • 5.做出选择

修改棋盘的当前行

  • 6.撤销选择

将棋盘的当前行删除

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
class Solution {
int N;
vector<vector<string>>results;
vector<string>path;
bool isValid(int m,int n){
bool ret = false;
for(int i = m - 1,j = n - 1;i >= 0&&j >= 0;i--,j--){
if(path[i][j] == 'Q')ret = true;
}

for(int i = m - 1,j = n;i >= 0;i--){
if(path[i][j] == 'Q')ret = true;
}

for(int i = m - 1,j = n + 1;i >= 0&&j < N;i--,j++){
if(path[i][j] == 'Q')ret = true;
}
return ret;
}
void dfs(int h){
if(h == N){
results.push_back(path);
return;
}
for(int i = 0;i < N;i++){
if(isValid(h,i))continue;
string cur(N,'.');
cur[i] = 'Q';
path.push_back(cur);
dfs(h + 1);
path.pop_back();
}
}

public:
vector<vector<string>> solveNQueens(int n) {
N = n;
dfs(0);
return results;
}
};

37. 解数独

  • 1.递归树

这个递归树非常的简单

  • 2.终止条件:

9*9数独全部都填满了

  • 3.选择列表

[1,9] 9个数字任选

  • 4.判断是否需要剪枝

判断新填入的数字是否会造成横竖斜存在相同值

  • 5.做出选择

修改数独的当前节点值

  • 6.撤销选择

将棋盘的当前节点值删除

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
class Solution {
bool isUsed(vector<vector<char>>& board,char num,int m,int n){
for(int i = 0;i < 9;i++){
if(board[m][i] == num)return true;
}
for(int i = 0;i < 9;i++){
if(board[i][n] == num)return true;
}
for(int i = (m/3) * 3;i < (m/3) * 3+3;i++){
for(int j = (n/3) * 3;j < (n/3) * 3+3;j++){
if(board[i][j] == num)return true;
}
}
return false;
}
bool find = false;
vector<vector<char>>results;
void getNextMN(vector<vector<char>>&board,int& m,int& n){
while(m < 9 && board[m][n] != '.' ){
if(n == 8){
m++;
n = 0;
}
else n++;
}
}
void dfs(vector<vector<char>>& board,int m, int n){
getNextMN(board,m,n);
if(m == 9){
find = true;
return;
}
for(int i = 1;i <= 9;i++){
if(isUsed(board, '0' + i, m, n))continue;
board[m][n] = '0' + i;
dfs(board, m, n);
if(find)return;
board[m][n] = '.';
}
}
public:
void solveSudoku(vector<vector<char>>& board) {
dfs(board, 0, 0);
return;
}
};

79. 单词搜索

image-20220404180243052

此题的一个特殊点在于除了剪枝,对当前节点应该有判断,因为dfs遍历是将栈顶元素的周边符合条件的元素也要压入栈,因此最开始的栈顶元素一定要符合标准,要避免存在第一个节点就不满足条件但是进入dfs遍历的情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
int m;int n;
int des[4][2] = {{0,1},{1,0},{0,-1},{-1,0}};
bool find = false;
bool exist(vector<vector<char>>& board, string word) {
m = board.size();n = board[0].size();
for(int i = 0;i < m;i++){
for(int j = 0;j < n;j++){
if(word[0] == board[i][j])dfs(board,word,i,j,0);//这里很关键,做选择的时候才会判断
if(find)return true;
}
}
return false;
}

void dfs(vector<vector<char>>& board, string word,int i,int j,int curi){
//当前节点判断
if(curi == word.length()-1){
find = true;
return;
}
char tmp = board[i][j];
board[i][j] = '#';//已经访问过做标记
for(int k = 0;k < 4;k++){
int ti = i+des[k][0];
int tj = j+des[k][1];
if(ti < 0 || ti >= m || tj < 0 || tj >= n || board[ti][tj] == '#' || find || word[curi+1] != board[ti][tj]){//剪枝
continue;
}
dfs(board,word,ti,tj,curi+1);
}
board[i][j] = tmp;
return;
}