面试题12:矩阵中的路径
问题描述:
请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。如果一条路径经过了矩阵中的某一个格子,则该路径不能再进入该格子。 例如 a b c e s f c s a d e e 矩阵中包含一条字符串”bcced”的路径,但是矩阵中不包含”abcb”路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入该格子。
分析:
这是一个可以用回朔法解决的典型题。
1、首先,在矩阵中任选一个格子作为路径的起点。如果路径上的第i个字符不是ch,那么这个格子不可能处在路径上的第i个位置。如果路径上的第i个字符正好是ch,那么往相邻的格子寻找路径上的第i+1个字符。除在矩阵边界上的格子之外,其他格子都有4个相邻的格子。
2、重复这个过程直到路径上的所有字符都在矩阵中找到相应的位置。
由于回朔法的递归特性,路径可以被看成一个栈。当在矩阵中定位了路径中前n个字符的位置之后,在与第n个字符对应的格子的周围都没有找到第n+1个字符,这个时候只要在路径上回到第n-1个字符,重新定位第n个字符。
由于路径不能重复进入矩阵的格子,还需要定义和字符矩阵大小一样的布尔值矩阵,用来标识路径是否已经进入每个格子。
易错误之处:
最开始在走过错路之后,将之前的位置锁住
如下矩阵,如果要得到路径a,b,c,f,e,b,但如果开始走的是(1,0),则到e开始回退时,由于flag矩阵变成了右边矩阵,则无法再找到新路径。
在第一步撤销时,保留锁定坐标,如果找到新路径,则撤除锁定;或者要再一步回退时,撤销原有锁定,更新锁定位置
a | b | h | 1 | 0 | 0 | ||
b | c | i | 1 | 1 | 0 | ||
e | f | g | 1 | 1 | 0 |
o(╯□╰)o不过感觉这么写好像有点复杂。。。应该有好多其他更好的解法
public class Q12 {
public static void main(String[] args) {
char[][] A = new char[][] {{'a','b','h'},{'b','c','i'},{'e','f','g'}};
String s = "abcfeb";
int[][] flag = new int[A.length][A[0].length];
int[][] path = new int[s.length()][2];
findPath(A, flag, path, s);
}
public static void findPath(char[][] A,int[][] flag,int[][] path,String s) {
int step=0;
int state=0;
int tempx=-1;
int tempy=-1;
if(s.length()==0) {
System.out.println("wrong input");
}else {
while(step>=0 && step<=s.length()-1) {
System.out.printf("step:%d\n",step);
// 如果是字符才开始,则从全局寻找
if(step==0) {
state = findOne(path, A, flag, s.charAt(step), step);
if(state==0) {
System.out.printf("no such char:%c\n", s.charAt(step));
step--;
}
else {
System.out.printf("[%c] at (%d,%d)\n",s.charAt(step),path[step][0],path[step][1]);
step++;
}
}
// 如果不是查找的开始,则应从上次位置开始查找
else if (step>0) {
state = findNext(path, A, flag, s.charAt(step), step);
// 如果找到,则在path中存储位置,并下一次查找
if(state==1) {
System.out.printf("[%c]at (%d,%d)\n",s.charAt(step),path[step][0],path[step][1]);
step++;
// 用于消除对之前锁定位置的锁定
if(tempx!=-1) {
flag[tempx][tempy] = 0;
}
// 如果没有找到,则将path的当前位置抹去,退回上一步,flag隔步回退或找到下一步后回退
}else {
System.out.printf("no %c around %c, back\n", s.charAt(step),s.charAt(step-1));
step--;
if(tempx!=-1) {
flag[tempx][tempy] = 0;
}
tempx = path[step][0];
tempy = path[step][1];
path[step][0] = -1;
path[step][1] = -1;
}
}
}
//如果是因为查找完成跳出,则找到路径
//如果是因为step=-1退出,则没有找到路径
System.out.printf("final step:%d\n", step);
if(step == s.length()) {
System.out.printf("find a path\n");
showPath(path);
}else {
System.out.println("there is no path");
}
}
}
// 从整个数组A查找第一个字符
public static int findOne(int[][] path,char[][] A,int[][] flag,char c,int loc){
for(int i=0;i<A.length;i++) {
for(int j=0;j<A[0].length;j++) {
if(A[i][j]==c && flag[i][j]==0) {
path[loc][0] = i;
path[loc][1] = j;
flag[i][j]=1;
return 1;
}
}
}
return 0;
}
// 通过当前的位置,查找下一个字符位置
public static int findNext(int[][] path,char[][] A,int[][] flag,char c,int loc) {
int row = A.length; //行数
int cols = A[0].length; // 列数
int x = path[loc-1][0]; // 上个位置的行
int y = path[loc-1][1]; // 上个位置的列
System.out.printf("To find [%c] in (%d,%d)'s next[%c]\n",A[x][y],x,y,c);
if(hasPath(x-1, y, row, cols, A, flag, path, loc, c)==1) { return 1; } // 上
if(hasPath(x+1, y, row, cols, A, flag, path, loc, c)==1) { return 1; } // 下
if(hasPath(x, y+1, row, cols, A, flag, path, loc, c)==1) { return 1; } // 左
if(hasPath(x, y-1, row, cols, A, flag, path, loc, c)==1) { return 1; } // 右
return -1;
}
// 检查当前位置和c是否相同
public static int hasPath(int x,int y,int row,int cols,char[][] a,int[][] flag,int[][] path,int loc,char c) {
if(x>=0 && x<row && y>=0 && y<cols) {
if(a[x][y]==c && flag[x][y]==0) {
path[loc][0] = x;
path[loc][1] = y;
flag[x][y]= 1;
return 1;
}
}
return -1;
}
// 按path顺序打印路径
public static void showPath(int[][] path) {
for(int i=0;i<path.length;i++) {
if(i!=path.length-1) {
System.out.printf("(%d,%d)→",path[i][0],path[i][1]);
}else {
System.out.printf("(%d,%d)",path[i][0],path[i][1]);
}
}
}
}