[LeetCode] 62. Unique Paths*

版权声明:如需转载请注明出处 https://blog.csdn.net/Bertram03/article/details/87884498

原题链接: https://leetcode.com/problems/unique-paths/

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).

How many possible unique paths are there?
Note: m and n will be at most 100.

有一个长为m, 宽为n的格子区域。n和m最大是100。
这个格子区域左上角有一个机器人,右下角有一个星星。机器人只能向右或者向下走,问机器人到达星星的不同路线有多少条?
在这里插入图片描述

Example 1:

Input: m = 3, n = 2
Output: 3
Explanation:
From the top-left corner, there are a total of 3 ways to reach the bottom-right corner:
1. Right -> Right -> Down
2. Right -> Down -> Right
3. Down -> Right -> Right

Example 2:

Input: m = 7, n = 3
Output: 28

2. 解题思路

2.1 动态规划

这个题是一个非常好的、用于理解和练习动态规划的题目。通过这道题既可以体会动态规划的思路,还可以学习二维数组变一维数组的优化方法。

观察这个格子区域,首先可以发现,星星上面和左面的格子到星星的路线只有1条。我们将路线数目存放在格子里,如下图所示:
在这里插入图片描述
在星星左上角的格子,它想要走到星星,必须到达星星上面的格子或者左面的格子,因此它到达星星的路线数就是右边格子的值+下面格子的值。1+1=2.
在这里插入图片描述
我们可以发现,由于机器人每一步只能向右或者向下走,因此某个位置到达星星的路线数,就是下面格子的路线数+右面格子的路线数。如果右边没有格子了,那就只加下边格子;如果下边没有格子了,那就只加右边格子。
在这里插入图片描述
每一个格子的值,都是右边格子和下边格子的和。下图是Example2 的求解过程。
在这里插入图片描述

因此,我们只需要构造一个二维的数组,采用倒序的遍历方式就可以得到可能的路线总数。倒序的遍历方式是指:在同一行中,从右向左遍历;在同一列中,自下向上遍历。

动态规划的精髓就在于利用前一步的计算结果去计算当前的值。本题的状态转移方程为:
d p [ i ] [ j ] = d p [ i ] [ j + 1 ] + d p [ i + 1 ] [ j ] dp[i][j] = dp[i][j+1] + dp[i+1][j]
在具体实现时,星星上边和左边的两个位置需要赋初值1,然后其他的位置都由他们递推计算而来。

实现代码

class Solution {
    public int uniquePaths(int m, int n) {
		
		if(m==1 || n ==1) {
			return 1;
		}
		
        int[][] dp = new int[m+1][n+1];
        dp[m-1][n-2] = 1;
        dp[m-2][n-1] = 1;
        
        for(int i = m-1 ; i>=0 ; i--) {
        	for(int j = n-1 ; j>=0 ; j--) {
        		if(dp[i][j] != 0) {
        			continue;
        		}
        		dp[i][j] = dp[i][j+1] + dp[i+1][j];
        	}
        }
        return dp[0][0];
    }
}

2.2 进一步改进:二维数组变一维

上述方法中,采用的是二维数组,需要遍历整个二维数组。空间复杂度为O(n*m),但是在每一次更新dp[ i ][ j ]时,只用到了dp[ i ][ j+1 ] 和 dp[ i+1 ][ j ]。所以可以采用一维数组代替二维数组,并且使用倒序的方法更新一维数组。
这样的空间复杂度为O(m)或者O(n).

实际实现时,可以取m和n中的最小的一个,这样空间复杂度为O(min(m,n))。长度宽度对调并不会对结果产生影响,所以我们会选取m、n中较小的那个作为一维数组的长度。

具体实现代码如下:

class Solution {
    public int uniquePaths(int m, int n) {
		
		if(m==1 || n ==1) {
			return 1;
		}
		
		int length = ( m>n ? m : n);
		int width =  ( m>n ? n : m);

		int [] dp = new int [width+1];
        dp[width-1] = 1;
        
        for(int i = length-1 ; i>=0 ; i--) {
        	for(int j = width-1 ; j>=0 ; j--) {	
        		dp[j] += dp[j+1] ;
           	}
        }
        
        return dp[0];
    }
}

2.3 排列组合法

注: 2.3排列组合法的思路和代码来源于 https://blog.csdn.net/linhuanmars/article/details/22126357
原文写的非常好,分析也很清楚,所以我直接复制在下面:


如果我们仔细的看这个问题背后的数学模型,其实就是机器人总共走m+n-2步,其中m-1步往下走,n-1步往右走,本质上就是一个组合问题,也就是从m+n-2个不同元素中每次取出m-1个元素的组合数。根据组合的计算公式,我们可以写代码来求解即可。代码如下:

public int uniquePaths(int m, int n) {
    double dom = 1;
    double dedom = 1;
    int small = m<n? m-1:n-1;
    int big = m<n? n-1:m-1;
    for(int i=1;i<=small;i++)
    {
        dedom *= i;
        dom *= small+big+1-i;
    }
    return (int)(dom/dedom);
}

上面的代码求解了组合的结果,只需要做一次行或者列的扫描,所以时间复杂度是O(min(m,n)),而空间复杂度是O(1)。比起上面的两种解法更优。不过这里有个弊端,就是如果代码中的dom和dedom如果不是double,而是用int,那么会很容易越界,因为这是一个阶乘,所以大家在面试中讨论这种方法要注意和提及越界的问题。

猜你喜欢

转载自blog.csdn.net/Bertram03/article/details/87884498