/**
* 功能:有个机器人坐在X*Y网格的左上角,只能向右、向下移动。机器人从(0,0)到(X,Y)有多少种走法。
* 进阶:假设有些点为“禁区”,机器人不能踏足。找出一条路径,让机器人从左上角移动到右下角。
*/
解答:
排列组合问题:共有(X+Y)!/X!Y!
进阶问题有两种方法:
方法一:
//递归法
/**
* 思路:自上而下
* 从终点往回走,试着找出到其相邻点的路径。
*/
public static boolean getPath(int x,int y,ArrayList path){
Point p=new Point(x,y);
path.add(p);
if(x==0&&y==0)
return true;//找到路径
boolean success=false;
if(x>=1&&isFree(x-1,y))//向左走
success=getPath(x-1,y, path);
if(y>=1&&isFree(x,y-1))
success=getPath(x,y-1,path);
if(!success)
path.add(p);//错误路径
return success;
}
方法二:
//动态规划
/**
* 思路:缓存先前访问过的点
* @param y
* @param path
* @param cache
* @return
*/
public static boolean getPathDP(int x,int y,ArrayList path,HashMap cache){
Point p=new Point(x,y);
if(x==0&y==0)
return true;
path.add(p);
if(cache.containsKey(p))
return cache.get(p);
boolean success=false;
if(x>=1&&isFree(x-1,y))
success=getPathDP(x-1, y, path, cache);
if(y>=1&&isFree(x,y-1))
success=getPathDP(x, y-1, path, cache);
if(!success)
path.add(p);//错误路径的点
cache.put(p, success);//缓存结果
return success;
}
}
class Point{
int x;
int y;
boolean isFree;
public Point(){}
public Point(int x,int y){
this.x=x;
this.y=y;
}
}
一、无障碍的网格
问题描述:
有一个XxY的网格,一个机器人只能走格点且只能向右或向下走,要从左上角走到右下角。请设计一个算法,计算机器人有多少种走法。给定两个正整数intx,inty表示网格的大小,计算机器人的走法数目。
求解思路:
对于本题,我们依然运用动态规划的思想。对于网格中的每一个格子,若该格子位于第一行,则只能由左边的格子到达;若格子位于第一列,只 能由上面的格子到达;网格中的其他格子可以由左边的格子到达,也可以由上面的格子到达。因此到达每一个格子的方法数都由左边的或者上面的格子所决定。我们 依次从网格的左上角遍历到右下角,则到达右下角格子的方法数便是最终的结果了。
代码实现:
import java.util.Scanner;
import java.util.Stack;
public class Main {
public static void main(String[] args) {
Scanner sc=new Scanner(System.in);
int x=sc.nextInt();
int y=sc.nextInt();
if(x==0||y==0)
System.out.println(0);
else if(x==1||y==1)
System.out.println(1);
else{
int [][]f=new int[x][y];
for(int j=0;j
f[0][j]=1;
}
for(int i=0;i
f[i][0]=1;
}
for(int i=1;i
for(int j=1;j
f[i][j]=f[i][j-1]+f[i-1][j];
}
}
System.out.println(f[x-1][y-1]);
}
}
}
二、有障碍的网格
问题描述:
题目与上述类似,只是注意这次的网格中有些障碍点是不能走的,给定一个int[][]map表示网格图,若map[i][j]为0则说明该点不是障碍点,否则为障碍点。计算机器人从(0,0)走到(x - 1,y - 1)的走法数。
求解思路:
求解思路也与上题类似,只是此时网格中有障碍点,我们可以这样考虑:
(1)当格子是障碍点时,机器人不能达到此格点,方法数为0;
(2)当格子不是障碍点时,则说明机器人向右走或向下走能到达此格子,因此到达此格子的方法数为到达其上方格子和左边格子的方法数之和。
代码实现:
public int countWays(int[][] map, int x, int y) {
int step[][]=new int[x][y];
if(map[0][0]==0) step[0][0]=1;
else step[0][0]=0;
for(int j=1;j
if(map[0][j]==0)
step[0][j]=step[0][j-1];
else
step[0][j]=0;
}
for(int i=1;i
if(map[i][0]==0)
step[i][0]=step[i-1][0];
else
step[i][0]=0;
}
for(int i=1;i
for(int j=1;j
if(map[i][j]==0)
step[i][j]=step[i][j-1]+step[i-1][j];
else
step[i][j]=0;
}
}
return step[x-1][y-1];
}
在一个N*N矩阵的左上角坐着一个机器人,它只能向右运动或向下运动。那么, 机器人运动到右下角一共有多少种可能的路径?
进一步地,
如果对于其中的一些格子,机器人是不能踏上去的。设计一种算法来获得所有可能的路径。
解答
为了一般化这个问题,我们假设这个矩阵是m*n的,左上角的格子是(1, 1), 右下角的坐标是(m, n)。
解法一
这个题目可以递归来解,如何递归呢?首先,我们需要一个递推公式, 对于矩阵中的格子(i, j),假设从(1, 1)到它的路径数量为path(i, j), 那么,有:
path(i, j) = path(i-1, j) + path(i, j-1)
很好理解,因为机器人只能向右或向下运动,因此它只能是从(i-1, j)或(i, j-1) 运动到(i, j)的,所以路径数量也就是到达这两个格子的路径数量之和。然后, 我们需要一个初始条件,也就是递归终止条件,是什么呢?可以发现, 当机器人在第一行时,不论它在第一行哪个位置,从(1, 1)到达那个位置都只有一条路径, 那就是一路向右;同理,当机器人在第一列时,也只有一条路径到达它所在位置。 有了初始条件和递推公式,我们就可以写代码了,如下:
ll path(ll m, ll n){
if(m == 1 || n == 1) return 1;
else return path(m-1, n) + path(m, n-1);
}
ll是数据类型long long。
解法二
如果用纯数学的方法来解这道题目,大概也就是个高中排列组合简单题吧。 机器人从(1, 1)走到(m, n)一定要向下走m-1次,向右走n-1次,不管这过程中是怎么走的。 因此,一共可能的路径数量就是从总的步数(m-1+n-1)里取出(m-1)步,作为向下走的步子, 剩余的(n-1)步作为向右走的步子。
C(m-1+n-1, m-1)=(m-1+n-1)! / ( (m-1)! * (n-1)! )
代码如下:
ll fact(ll n){
if(n == 0) return 1;
else return n*fact(n-1);
}
ll path1(ll m, ll n){
return fact(m-1+n-1)/(fact(m-1)*fact(n-1));
}
对于第二问,如果有一些格子,机器人是不能踏上去的(比如说放了地雷XD), 那么,我们如何输出它所有可能的路径呢?
让我们先来考虑简单一点的问题,如果我们只要输出它其中一条可行的路径即可, 那么我们可以从终点(m, n)开始回溯,遇到可走的格子就入栈, 如果没有格子能到达当前格子,当前格子则出栈。最后到达(1, 1)时, 栈中正好保存了一条可行路径。代码如下:
bool get_path(int m, int n){
point p; p.x=n; p.y=m;
sp.push(p);
if(n==1 && m==1) return true;
bool suc = false;
if(m>1 && g[m-1][n])
suc = get_path(m-1, n);
if(!suc && n>1 && g[m][n-1])
suc = get_path(m, n-1);
if(!suc) sp.pop();
return suc;
}
其中二维数组g表示的是M*N的矩阵,元素为1表示该位置可以走,为0表示该位置不可走。 这个只能得到其中一条可行路径,但题目是要求我们找到所有可行路径,并输出。 这样的话,又该怎么办呢?我们从(1, 1)开始,如果某个格子可以走, 我们就将它保存到路径数组中;如果不能走,则回溯到上一个格子, 继续选择向右或者向下走。当机器人走到右下角的格子(M, N)时,即可输出一条路径。 然后程序会退出递归,回到上一个格子,找寻下一条可行路径。代码如下:
void print_paths(int m, int n, int M, int N, int len){
if(g[m][n] == 0) return;
point p; p.x=n; p.y=m;
vp[len++] = p;
if(m == M && n == N){
for(int i=0; i
cout<
cout<
}
else{
print_paths(m, n+1, M, N, len);
print_paths(m+1, n, M, N, len);
}
}
程序使用的输入样例8.2.in如下:
3 4
1 1 1 0
0 1 1 1
1 1 1 1
输出路径如下:
one of the paths:
(1, 1) (1, 2) (1, 3) (2, 3) (2, 4) (3, 4)
all paths:
(1, 1) (1, 2) (1, 3) (2, 3) (2, 4) (3, 4)
(1, 1) (1, 2) (1, 3) (2, 3) (3, 3) (3, 4)
(1, 1) (1, 2) (2, 2) (2, 3) (2, 4) (3, 4)
(1, 1) (1, 2) (2, 2) (2, 3) (3, 3) (3, 4)
(1, 1) (1, 2) (2, 2) (3, 2) (3, 3) (3, 4)