- 写于2019年5月28日
62. 不同路径
① 题目描述
- 一个机器人位于一个
m
×
n
m×n
m×n网格的左上角 (起始点在下图中标记为
“Start”
)。 - 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为
“Finish”
)。 - 问总共有多少条不同的路径?
- 例如,下图是一个
7
×
3
7×3
7×3的网格。有多少可能的路径?
- 说明:m 和 n 的值均不超过 100。
- 示例 1:
输入: m = 3, n = 2
输出: 3
解释: 从左上角开始,总共有 3 条路径可以到达右下角。
- 向右 -> 向右 -> 向下
- 向右 -> 向下 -> 向右
- 向下 -> 向右 -> 向右
- 示例 2:
输入: m = 7, n = 3
输出: 28
② 使用带重复数字的全排列( Time Limit Exceeded
)
- 通过观察发现:在
m x n
的方格中,从左上角到右下角,无论哪条路径都需要向右走m-1
步,向下走n-1
步。 - 用0表示向右,1表示向下,问题就成了带重复数字的全排列问题。
- 于是初始化一个
(m-1)+(n-1)
的数组,用于存储向右和向下的0和1,实现对该数组的全排列,记录排列的种数。 - 可惜!
Time Limit Exceeded
。 - 代码如下:
public int uniquePaths(int m, int n) {
int[] steps = new int[m + n - 2];
int[] result = {0};
for (int i = m - 1; i < steps.length; i++) {
steps[i] = 1;
}
backtrace(steps, 0, result);
return result[0];
}
public void backtrace(int[] nums, int cur, int[] result) {
if (cur == nums.length) {
result[0]++;
return;
}
for (int i = cur; i < nums.length; i++) {
if (isSwap(nums, cur, i)) {
swap(nums, i, cur);
backtrace(nums, cur + 1, result);
swap(nums, i, cur);
}
}
}
public void swap(int[] nums, int i, int j) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
// 能不能交换,就是说交换数字中间不能出现与后面交换的重复数字如:122那个1只能和第一个2交换却不能和第二个2交换
public boolean isSwap(int[] num, int i, int j) {
for (int index = i; index < j; index++) {
if (num[index] == num[j])
return false;
}
return true;
}
③ 递归(优化后,没有Time Limit Exceeded
)
- 求
( 0 , 0 )
点到( m - 1 , n - 1)
点的走法。
①(0,0)
点到(m - 1 , n - 1)
点的走法等于(0,0)
点右边的点(1,0)
到(m - 1 , n - 1)
的走法加上(0,0)
点下边的点(0,1)
到(m - 1 , n - 1)
的走法。
② 左边的点(1,0)
点到(m - 1 , n - 1)
点的走法等于(2,0)
点到(m - 1 , n - 1)
的走法加上(1,1)
点到(m - 1 , n - 1)
的走法。
③ 下边的点(0,1)
点到(m - 1 , n - 1)
点的走法等于(1,1)
点到(m - 1 , n - 1)
的走法加上(0,2)
点到(m - 1 , n - 1)
的走法。
④ 然后一直递归下去,直到(m - 1 , n - 1)
点到(m - 1 , n - 1)
,返回 1。 - 注意: 递归时,由于m和n不一定相等 ,所以需要判断递归时,right和down是否越界,如果越界则不能继续前进,设置其为0。
- 可惜! 竟然
Time Limit Exceeded
,比我自己想的方法还死得早。。。。于是需进行优化,优化后竟然只需要3ms
,神奇!! - 代码如下:
public int uniquePaths(int m, int n) {
HashMap<String, Integer> visited = new HashMap<>();
return stepSum(0, 0, m, n, visited);
}
public int stepSum(int right, int down, int m, int n, HashMap<String, Integer> visited) {
if (right == m - 1 && down == n - 1) {
return 1;
}
int right_sum = 0;
int down_sum = 0;
// 判断当前点是否已经求过了
String key = (right + 1) + ", " + down;
if (!visited.containsKey(key)) {
if (right + 1 < m) {
right_sum = stepSum(right + 1, down, m, n, visited);
}
} else {
right_sum = visited.get(key);
}
key = right + ", " + (down + 1);
if (!visited.containsKey(key)) {
if (down + 1 < n) {
down_sum = stepSum(right, down + 1, m, n, visited);
}
} else {
down_sum = visited.get(key);
}
// 将当前点加入 visited 中
visited.put(right + ", " + down, right_sum + down_sum);
return right_sum + down_sum;
}
④ 动态规划
- 第一行的格子、第一列的格子,只能通过向右、向下到达,设置为
dp[i][0]=1
和dp[0][i]=1;
- 其他位置的格子:
dp[i][j]=dp[i-1][j]+dp[i][j-1];
- 代码如下:
public int uniquePaths(int m, int n) {
int[][] dp=new int[m][n];
for(int i=0;i<m;i++){
dp[i][0]=1;
}
for(int i=0;i<n;i++){
dp[0][i]=1;
}
for(int i=1;i<m;i++){
for(int j=1;j<n;j++){
dp[i][j]=dp[i-1][j]+dp[i][j-1];
}
}
return dp[m-1][n-1];
}
64. 最小路径和
① 题目描述
- 给定一个包含非负整数的
m x n
网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。 - 说明:每次只能向下或者向右移动一步。
- 示例:
输入:
[
[1,3,1],
[1,5,1],
[4,2,1]
]
输出: 7
解释: 因为路径1→3→1→1→1
的总和最小。
② 动态规划
- 用
dp[i][j]
表示从(0,0)
到(i,j)
的最小路径总和,则有:
① 如果j - 1 >= 0
,表示可以从(i,j)
的左边(i,j - 1)
向右到达(i,j)
;否则,设置dp[i][j - 1] = -1
。注意: 不要设置为0,因为grid中的数字可能为0。
② 如果i - 1 >= 0
,表示可以从(i,j)
的上边(i -1 ,j )
向下到达(i,j)
;否则,设置dp[i - 1][j] = -1
。
③ 如果既能通过向下和向右到达(i,j)
,则取dp[i][j - 1]
和dp[i - 1][j]
的最小值。
public int minPathSum(int[][] grid) {
int m = grid.length;
int n = grid[0].length;
int[][] dp = new int[m][n];
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (i == 0 && j == 0) {
dp[0][0] = grid[0][0];
continue;
}
int right = (j - 1 >= 0) ? dp[i][j - 1] : -1;
int down = (i - 1 >= 0) ? dp[i - 1][j] : -1;
if (right == -1) {
dp[i][j] = dp[i - 1][j] + grid[i][j];
} else if (down == -1) {
dp[i][j] = dp[i][j - 1] + grid[i][j];
} else {
dp[i][j] = Math.min(dp[i - 1][j], dp[i][j - 1]) + grid[i][j];
}
}
}
return dp[m - 1][n - 1];
}
③ 递归
- 62题中求路径种数时,返回的是从
(right,down)
到(m - 1,n - 1 )
的路径总数。我们这里要求的是最小路径和,因此应该返回从(right,down)
到(m - 1,n - 1 )
的最小路径和。 - 注意更新
(right,down)
的最小路径和时,要加上自身的grid[right][down]
。
public int minPathSum(int[][] grid) {
HashMap<String, Integer> visited = new HashMap<>();
return minSum(0, 0, grid.length, grid[0].length, grid, visited);
}
public int minSum(int right, int down, int m, int n, int[][] grid, HashMap<String, Integer> visited) {
if (right == m - 1 && down == n - 1) {
return grid[m - 1][n - 1];
}
int right_sum = Integer.MAX_VALUE;
int down_sum = Integer.MAX_VALUE;
String key = (right + 1) + ", " + down;
if (!visited.containsKey(key)) {
if (right+1<m){
right_sum = minSum(right + 1, down, m, n, grid, visited);
}
} else {
right_sum = visited.get(key);
}
key = right + ", " + (down + 1);
if (!visited.containsKey(key)) {
if (down+1<n){
down_sum = minSum(right, down + 1, m, n, grid, visited);
}
} else {
down_sum = visited.get(key);
}
key = right + ", " + down;
visited.put(key, Math.min(right_sum, down_sum) + grid[right][down]);
return Math.min(right_sum, down_sum) + grid[right][down];
}