动态规划--矩阵最小的路径和

来源:互联网 发布:js 截取数组 编辑:程序博客网 时间:2024/04/24 14:41

题目描述:给定一个 N*M 的矩阵arr,从左上角开始每次只能向下或者向右走,最后到达右下角。路径上所有点的数字和为 路径和,求最小的路径和。

典型的动态规划。状态方程为: dp[i][j] = getMin( dp[i - 1][j] ,dp[i][j - 1] ) + arr[i][i] 。dp[i][j] 表示 达到点 arr[i][j] 是的最小路径和,因为每次只能向下或者向右,所以要达到 arr[i][j] 必须先经过 arr[i - 1][j - 1] 或者 arr[i][j - 1] 其中一个点,找出路径最小的即可。

因为每次只能向下和向右,所以第一行只能从左一直往右走,而第一列只能从上一直往下走,并且将经过的点累加起来。

所以我们可以先对第一行、第一列的 dp[i][j] 进行初始化。

具体代码:

import java.util.Scanner;/** * 输入一个 N*M 的矩阵,从左上角开始,每次只能向下或者向右走,最后到达右下角的位置 * 将路径上所以数字加起来就是路径和,求最小路径和 *   * @author luzi * */public class minPathSumOfArr {public static void main(String args[]){Scanner scan = new Scanner(System.in);while(scan.hasNext()){int n = scan.nextInt();//行数int m = scan.nextInt();//列数int[][] arr = new int[n][m];for(int i = 0; i < n; i++){for(int j = 0; j < m; j++){arr[i][j] = scan.nextInt();}}System.out.println(minPath(arr,n,m));}}public static int minPath(int[][] arr,int n,int m){if(arr == null)return 0;int[][] dp = new int[n][m];dp[0][0] = arr[0][0];for(int i = 1; i < n;i++){dp[i][0] = dp[i - 1][0] + arr[i][0];}for(int i = 1; i < m; i++){dp[0][i] = dp[0][i - 1] + arr[0][i];}for(int i = 1; i < n; i++){for(int j = 1; j < m; j++){dp[i][j] = Math.min(dp[i - 1][j], dp[i][j - 1]) + arr[i][j];}}return dp[n - 1][m - 1];}}



/*******************2017-5-31 修改********************/

上面的方法中,时间复杂度为 O(n*m), 但是辅助空间也为 O(n*m)。通过压缩空间的方法来减小辅助空间,使得辅助空间只需要 O(min(n,m)),

状态转移方程为: dp[ i ] = min ( dp[ i - 1]  , dp[ i ] ) + arr[ i ] [ j ] .


我们用一个实际的例子来分析说明,比如现在有一个 5*4 的矩阵: 

1  1  1  1
2  3  5  4
1  7  2  3
1  3  3  4
2  3  4  1

明显 n > m,我们设置一个辅助的数组 int[ ] dp = new int [ m ]。dp[ 0 ] = arr[ 0 ][ 0 ];

我们从第一行开始(如果是 m > n,那么就从 第一列开始),因为第一行只能从左到右(第一列则只能从上到下),

所以我们初始化 dp[ 1 ] = 2,dp[ 2 ] = 3, dp[ 3 ] = 4。接下来看第二行,因为第二行的第一个只能是从上到下,所以此时应该为 1 + 2 = 3,即 dp[ 0 ] =dp[ 0 ] + arr[ 1 ][ 0 ] = 3, 而第二行的第二个,有两种方法可以到达,一种是从左边的2 到达,一种是从上面的 1 到达,我们要的是更小的路径,所以我们选择左边跟上边中更小的那个即:

 min( dp[ i - 1],dp[ i ])  。式子中红色 的 dp[ i ] ,表示的是我们能到达上一行的 第 i 个数的 最小路径值,也就是我们当前这一行的第 i 的数上面数值,跟当前行左边的数值进行比较取更小的 。最终我们遍历到最后一行最后一个数字,得出的dp [ m - 1 ] 就是最小的路径。

具体的代码如下:


//压缩空间的方法,只需要额外的 O(min(n,m)) 空间 ,代码过于冗余,精益求精!再优化public static int minPath2(int[][] arr,int n,int m){if(arr == null)return 0;if(n <= m){int[] dp = new int[n];dp[0] = arr[0][0];for(int i = 1; i < n; i++){dp[i] = dp[i - 1] + arr[i][0];}for(int i = 1; i < m; i++){dp[0] = dp[0] + arr[0][i];for(int j = 1; j < n; j++){dp[j] = Math.min(dp[j], dp[j - 1]) + arr[j][i];}}return dp[n - 1];}else{int[] dp = new int[m];dp[0] = arr[0][0];for(int i = 1; i < m; i++){dp[i] = dp[i - 1] + arr[0][i];}for(int i = 1; i < n; i++){dp[0] = dp[0] + arr[i][0];for(int j = 1; j < m; j++){dp[j] = Math.min(dp[j], dp[j - 1]) + arr[i][j];}}return dp[m - 1];}}//根据上面的 minPath2 方法进行优化,参考了《程序员代码面试指南》public static int minPath3(int[][] arr,int n,int m){if(arr == null)return 0;//比较行数 和  列数大小int more = Math.max(n, m);int less = Math.min(n, m);boolean rowmore = (more == n);//标志变量,行数是否大于列数int[] dp = new int[less];dp[0] = arr[0][0];for(int i = 1; i < less; i++){dp[i] = dp[i - 1] + (rowmore ? arr[0][i] : arr[i][0]);//如果行数大于列数,则加上arr[0][i]}for(int i = 1; i < more; i++){dp[0] = dp[0] + (rowmore ? arr[i][0] : arr[0][i]);for(int j = 1; j < less; j++){dp[j] = Math.min(dp[j - 1], dp[j]) + (rowmore ? arr[i][j] : arr[j][i]);}}return dp[less - 1];}


minPath2 的方法容易理解,但是代码太冗余

minPath3 方法的精髓在于设置了一个标志标量 rowmore ,用于选择我们应该先遍历每一行还是先遍历每一列,细细体会。

原创粉丝点击