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

题目描述:给定一个 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 ,用于选择我们应该先遍历每一行还是先遍历每一列,细细体会。

你可能感兴趣的:(动态规划)