这周我们重温一下经典的格子世界。
A robot is located at the top-left corner of a m x n grid (marked ‘Start’ in the diagram below).
The robot can only move either down or right at any point in time. The robot is trying to reach the bottom-right corner of the grid (marked ‘Finish’ in the diagram below).
How many possible unique paths are there?
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。
问总共有多少条不同的路径?
Example
输入: m = 3, n = 2
输出: 3
解释:
从左上角开始,总共有 3 条路径可以到达右下角。
1.向右 -> 向右 -> 向下
2.向右 -> 向下 -> 向右
3.向下 -> 向右 -> 向右
输入: m = 7, n = 3
输出: 28
这是非常简单的一道题,最早出现于高中的排列与组合中。即若有一个 m x n 的网格,从对角线的一端到达另一端的路径有多少种?根据高中的知识可得,只需要选取(m-1)+(n-1)步数中的(m-1)个步骤为竖着即可。
class Solution {
public int uniquePaths(int m, int n) {
if (m>n){
return uniquePaths(n, m);
}
return (int) Combination(m-1, m+n-2);
}
public long factorial(int k){
long sum=1;
for(int i=2;i<=k;i++){
sum*=i;
}
return sum;
}
public long Combination(int m, int n){
//m在上,n在下
return m <= n ? factorial(n) / (factorial(m) * factorial((n - m))) : 0;
}
}
对于较小的数是没有问题的(在leetcode中通过数目为37 / 62)。但这里有一个最大的问题,就是如果乘积过大是无法计算的,因为是int类型,尤其时n=100时,便已无法计算。那就只能使用递归方法求解了。
我们可以从1 X 1的格子开始递推,一直到m*n的格子,情况如下:
由以上得到推导公式,这就转换为一个动态规划问题,简单的递归调用即可解:
S ( m , n ) = S ( m , n − 1 ) + S ( m − 1 , n ) S(m,n)=S(m,n-1)+S(m-1,n) S(m,n)=S(m,n−1)+S(m−1,n)
class Solution {
public int uniquePaths(int m, int n) {
if (m == 1 && n == 1) {
return 1;
}
if (m == 1 && n > 1) {
return 1;
}
if (n == 1 && m > 1) {
return 1;
}
// 递归调用
return uniquePaths(m - 1, n) + uniquePaths(m, n - 1);
}
}
上述代码仍然存在问题,即递归调用不能够太深(在leetcode中通过数目为61 / 62),因此我们转为非递归的二维矩阵即可解,首先确定二维数组,然后确定边界条件,最后根据递推公式即可获得。
class Solution {
public int uniquePaths(int m, int n) {
if(m<=1 && n<=1){
return 1;
}
int[][] array=new int[m][n];
for(int rol=0;rol<m;rol++){
array[rol][0]=1;
}
for(int col=0;col<n;col++){
array[0][col]=1;
}
for(int rol=1;rol<m;rol++){
for(int col=1;col<n;col++){
array[rol][col]=array[rol-1][col]+array[rol][col-1];
}
}
return array[m-1][n-1];
}
}
二维矩阵可以进一步缩小其空间复杂度,转换为1维矩阵。
class Solution {
public int uniquePaths(int m, int n) {
int d[] = new int[n];
d[0] = 1;
for(int i = 0; i < m; i++){
for(int j = 1; j < n; j++) {
d[j] = d[j] + d[j - 1];
}
}
return d[n - 1];
}
}
A robot is located at the top-left corner of a m x n grid (marked ‘Start’ in the diagram below).
The robot can only move either down or right at any point in time. The robot is trying to reach the bottom-right corner of the grid (marked ‘Finish’ in the diagram below).
Now consider if some obstacles are added to the grids. How many unique paths would there be?
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。
现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径?
Example
Input:
[
[0,0,0],
[0,1,0],
[0,0,0]
]
Output: 2
Explanation:
There is one obstacle in the middle of the 3x3 grid above.
There are two ways to reach the bottom-right corner:
1.Right -> Right -> Down -> Down
2.Down -> Down -> Right -> Right
和第一题的不同之处在于有障碍物不允许我们走了,那么我们只需要在每一步递归的时候,条件判断一下即可。
class Solution {
public int uniquePathsWithObstacles(int[][] obstacleGrid) {
int m=obstacleGrid.length;
int n=obstacleGrid[0].length;
if (m<1 || n<1){
return 0;
}
if(m==1 && n==1){
return 1-obstacleGrid[0][0];
}
int[][] array=new int[m][n];
for(int rol=0;rol<m && obstacleGrid[rol][0]==0;rol++){
array[rol][0]=1;
}
for(int col=0;col<n&& obstacleGrid[0][col]==0;col++){
array[0][col]=1;
}
for(int rol=1;rol<m;rol++){
for(int col=1;col<n;col++){
//递归条件判断
if (obstacleGrid[rol][col]==0){
array[rol][col]=array[rol-1][col]+array[rol][col-1];
}
else{
array[rol][col]=0;
}
}
}
return array[m-1][n-1];
}
}
On a 2-dimensional grid, there are 4 types of squares:
1 represents the starting square. There is exactly one starting square.
2 represents the ending square. There is exactly one ending square.
0 represents empty squares we can walk over.
-1 represents obstacles that we cannot walk over.
Return the number of 4-directional walks from the starting square to the ending square, that walk over every non-obstacle square exactly once.
Example 1:
Input: [[1,0,0,0],[0,0,0,0],[0,0,2,-1]]
Output: 2
Explanation: We have the following two paths:
1.(0,0),(0,1),(0,2),(0,3),(1,3),(1,2),(1,1),(1,0),(2,0),(2,1),(2,2)
2.(0,0),(1,0),(2,0),(2,1),(1,1),(0,1),(0,2),(0,3),(1,3),(1,2),(2,2)
Example 2:
Input: [[1,0,0,0],[0,0,0,0],[0,0,0,2]]
Output: 4
Explanation: We have the following four paths:
1.(0,0),(0,1),(0,2),(0,3),(1,3),(1,2),(1,1),(1,0),(2,0),(2,1),(2,2),(2,3)
2.(0,0),(0,1),(1,1),(1,0),(2,0),(2,1),(2,2),(1,2),(0,2),(0,3),(1,3),(2,3)
3.(0,0),(1,0),(2,0),(2,1),(2,2),(1,2),(1,1),(0,1),(0,2),(0,3),(1,3),(2,3)
4.(0,0),(1,0),(2,0),(2,1),(1,1),(0,1),(0,2),(0,3),(1,3),(1,2),(2,2),(2,3)
Example 3:
Input: [[0,1],[2,0]]
Output: 0
Explanation:
There is no path that walks over every empty square exactly once.
Note that the starting and ending square can be anywhere in the grid.
这题是上一题的加强版,这里连起点和终点都是不确定的,并且,对于路径也增加了限制,即所有的能走的路径都只能走一次。这就是最普通的格子世界,这种小规模的场景,一般使用深度搜索进行求解,就像最笨的人类一样,把所有的路径都试一遍就到达了。而如果这个场景相当大的话,其搜索的复杂度相当大,这时候,一般都是使用强化学习进行求解。
首先要做的就是确定开始位置和我们要走的步数。然后,在每一步时都依次进行以下判断:
如果没有走到终点,则首先将当前位置置为不可达,其次减少一个要走的步数。然后递归,递归后再复原原有地图坐标,即可。
class Solution {
//X的操作
static int[] dx = new int[]{0, 1, -1, 0};
//Y的操作
static int[] dy = new int[]{1, 0, 0, -1};
//最终的结果
static int result = 0;
public static int uniquePathsIII2(int[][] grid) {
int n = grid.length;
int m = grid[0].length;
int x = -1;
int y = -1;
int t = 1;
//统计需要走的步数和开始位置。
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (grid[i][j] == 0) {
t++;
} else if (grid[i][j] == 1){
x = i;
y = j;
}
}
}
//深度优先搜索
dfs(grid, x, y, t);
return result;
}
public static void dfs(int[][] grid, int x, int y, int n) {
//如果越界了
if (x < 0 || y < 0 || x >= grid.length || y >= grid[0].length) {
return;
}
//如果是不能走的
if (grid[x][y] == -1) {
return ;
}
//如果是到达终点,如果是0则将结果++
if (grid[x][y] == 2) {
if (n == 0) {
result++;
}
return;
}
//如果没有到达终点,则将该点设置为不可走的位置。
grid[x][y] = -1;
for (int i = 0; i < 4; i++) {
int nx = x + dx[i];
int ny = y + dy[i];
dfs(grid, nx, ny, n - 1);
}
//再把当前值复原
grid[x][y] = 0;
}
}
本次主要对不同路径的3个题目进行了拓展,最主要的是我们复习了动态规划和代理探索的常规框架,为以后的学习打下基础。