题目大意:
给出一个二维数组,再给出一个字符串,试问该字符串是否存在于这个二维数组中?
题目说明:
所谓“字符串存在于二维数组中”,即从二维数组的任一元素出发,从上下左右四个方向“前进”,直到找到整条字符串。如下:
输入:[[‘A’,’B’,’C’,’E’], [‘S’,’F’,’C’,’S’], [‘A’,’D’,’E’,’E’]],构成的二维数组可以写成矩阵形式:
如果此时给定的字符串是”ABCCED”或”SEE”,则应当输出”true”。若给定字符串是”ABCB”,应当输出”false”,因为在这个二维数组中找不到连续的’A’、’B’、’C’和’B’组成的字符串。
要说明的是,二维数组中同一位置的字符不能重复使用两次。
解题思路
刚看到这道题,就能明确这是一道用BFS解决的题目。解题思路也很清晰:遍历二维数组,从当前点出发,比较所在点的字符与给定字符串当前位置的字符是否相等,若相等,则向四个方向前进,并把字符串的“当前位置”向后推移一位,直到达到字符串末尾。具体操作起来,在“前进”过程中,维护合法字符串长的最大值,一旦它与给定字符串的长度相等,则说明存在。
为保证同一个字符不会被使用两次,用数组flag记录每一个位置上字符的使用情况。具体实现如下:
class Solution {
public:
int row,column;
int flag[1000][1000];
int comp(int a,int b,int c,int d)
{
int k;
k=(a>=b?a:b);
k=(k>=c?k:c);
k=(k>=d?k:d);
return k;
}
int check(int i,int j, int seq, vector< vector<char> >& board, string word)
{
if(i>=0&&i<row&&j>=0&&j<column&&flag[i][j]==0&&seq<word.size()) //未超边界,未访问过且字符相等
{
char c;
c=board[i][j]; //取i,j处的值
if(c==word.at(seq))
{
flag[i][j]=1;
return comp(check(i+1,j,seq+1,board,word)+1,check(i,j+1,seq+1,board,word)+1,check(i-1,j,seq+1,board,word)+1,check(i,j-1,seq+1,board,word)+1);
}
else
return 0;
}
else
return 0;
}
bool exist(vector<vector<char>>& board, string word) {
int number,num;
column=board[0].size(); //列数
row=board.size(); //行数
num=word.size();
for(int i=0;i<row;i++) //逐个遍历
for(int j=0;j<column;j++)
{
memset(flag,0,sizeof(flag)); //将标志数组初始化
number=check(i,j,0,board,word);
if(number==num) return true;
}
return false;
}
};
不幸的是,上传代码后只得到了WA。仔细检查,发现问题就出在flag数组的更新上。以如下的二维数组为例:
给定字符串是”ABCESEEEFS”。可以发现,在程序按A(1,1)-B-C-E(2,3)-S-E(3,4)-E(3,3)的路线走后,发现再也没有与给定字符串匹配的路线。此时,由于(2,3)和(3,3)处的flag值被设为1,程序再也返回不到(1,3)处的C,从而使得结果输出错误。
要解决这一问题,必须在“此路不通”的情况下,修改flag的值,使得程序能返回到“岔路口”。参考了discuss中的结果,修改程序如下:
class Solution {
public:
int row,column;
int flag[1000][1000];
bool check(int i,int j, int seq, vector< vector<char> >& board, string word)
{
if(i<0||i>=row||j<0||j>=column||flag[i][j]==1||board[i][j]!=word.at(seq)) //超边界,访问过或者不相等
return false;
char c;
if(seq==word.size()-1) return true; //如果是最后一个字符,则直接返回true
c=board[i][j]; //否则,取(i,j)处的值
flag[i][j]=1;
if(check(i+1,j,seq+1,board,word)||check(i,j+1,seq+1,board,word)||check(i-1,j,seq+1,board,word)||check(i,j-1,seq+1,board,word)) //只要有一项成立即可,否则,需将标志改回来,再返回false,表示此路不通
return true;
flag[i][j]=0;
return false;
}
bool exist(vector<vector<char>>& board, string word) {
bool jud;
column=board[0].size(); //列数
row=board.size(); //行数
for(int i=0;i<row;i++) //逐个遍历
for(int j=0;j<column;j++)
{
memset(flag,0,sizeof(flag)); //将标志数组初始化
jud=check(i,j,0,board,word);
if(jud) return true;
}
return false;
}
};
这次不用长度作为判断是否存在标志,而是直接将搜索结果返回。但运行的结果却是超时。仔细对比了修改的程序与discuss中给出的程序,发现差别主要在于:自己写的程序中用到了新建的数组flag,每遍历一个位置的字符时就要将其重新初始化为0,而discuss中的程序则是将修改flag值的操作改成修改board数组相应位置元素为’/0’的操作,这样就少了将flag整体赋为0的步骤,整体降低了时间复杂度。
需要说明的是,虽然引用传递会改变实参(本问题中即原二维数组board)的值,但在程序中已经做了相应的处理:对每条走不通路径上的值都将其还原。这样,就不用担心每遍历一次,原数组的值修改一次的问题了。代码如下:
class Solution {
public:
bool exist(vector<vector<char> > &board, string word) {
m=board.size();
n=board[0].size();
for(int x=0;x<m;x++)
for(int y=0;y<n;y++)
{
if(isFound(board,word.c_str(),x,y))
return true;
}
return false;
}
private:
int m;
int n;
bool isFound(vector<vector<char> > &board, const char* w, int x, int y)
{
if(x<0||y<0||x>=m||y>=n||board[x][y]=='\0'||*w!=board[x][y])
return false;
if(*(w+1)=='\0')
return true;
char t=board[x][y];
board[x][y]='\0';
if(isFound(board,w+1,x-1,y)||isFound(board,w+1,x+1,y)||isFound(board,w+1,x,y-1)||isFound(board,w+1,x,y+1))
return true;
board[x][y]=t; //将值还原
return false;
}
};
这道题目虽然只是BFS的一道简单应用题,但背后需要注意的细节其实很多。这提醒我在编写程序的时候一定要提前把所有要注意到的方面想清楚,这样才能事半功倍。