剑指offer第二版——面试题12(java)

面试题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矩阵变成了右边矩阵,则无法再找到新路径。

在第一步撤销时,保留锁定坐标,如果找到新路径,则撤除锁定;或者要再一步回退时,撤销原有锁定,更新锁定位置

abh  100
bc

i

  110
efg  110

 

 

 

 

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]);
			}
		}
	}
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值