动态规划应用之一
2016-04-06 08:19
267 查看
笔者在前不久做了腾讯的在线笔试题,面试的移动客户端开发实习生岗位,其中有这样的一个题目:
有一个M行N列的矩阵,其中部分格子里面有一些有价值的物品。现在你从左上角出发,每次只能向右或者向下走,走到右下角的时候,你能获取的物品的总价值最大有多少?
输入数据:第一行有两个数字M N,表示这个矩阵有M行N列。然后从第二行开始,有M行整数,每行都有N个非负整数,表示这一格的物品的价值
输出数据:可以获取的最大的物品总价值
数据范围:0<M, N<=1000,矩阵中的数字不会超过1000
示例
输入:
4 5
0 0 8 0 0
0 0 0 9 0
0 7 0 0 0
0 0 6 0 0
输出:
17
看到这个题,最初的想法是,找出矩阵的最大值,然后以这个值为界将矩阵分成两个矩阵(左上矩阵:这个最大值为左上矩阵的右下角元素;右下矩阵:这个最大值为右下矩阵的左上角元素)。后来一细想,这样做是有问题的,因为这样就直接抛弃了左下和右上的矩阵,就丢失了很多信息。后来想到用动态规划的方法去解决就清晰多了。
明确一下,矩阵有M行和N列,因为数组序号是从0开始的,因此这里行号和列号我也从0开始,那么最后一行就是(M-1)行,最后一列就是(N-1)列。
动态规划有两大性质:最优子结构和重叠子问题。利用动态规划的思想对此问题进行简单的分析。
假设整体最优解路线必定经过格子[x][y](x,y在矩阵范围内),则整体的最优解必定是([0][0]->[x][y])和([x][y]->[M-1][N-1])这两个矩阵最优解的和,这就是最优子结构;如果基于这个思想去编程实现的话,我们需要对(x,y)在整个矩阵范围内进行扫描,并找到整体最优解的最大值。然而,这样实现就会显现出重叠子问题重复求解的问题。比如,对于([0][0]->[x][y]),他的最优解必定是【([0][0]->[x-1][y])的最优解加上(x,y)上的价值】与【([0][0]->[x][y-1])的最优解加上(x,y)上的价值】的较大者,而([0][0]->[x-1][y])的最优解或者([0][0]->[x][y-1])的最优解,我们可能会多次重复求解。
另一种思考方式是,
我们从某一端开始考虑,比如从[0][0]开始,他的下一步必定是[0][1]或者[1][0]之一的格子,因此,那么问题就变成了([0][0]->[0][1])和([0][1]->[M-1][N-1])的最优解或者([0][0]->[1][0])和([1][0]->[M-1][N-1])的最优解的较大者,那么这个和就是整体的最优解。然后对所有的格子进行这样的考虑。在实现过程中,我们并不知道后面那一部分的最优解,只是计算前面那一部分的最优解,这个计算是一步一步扩大的。需要明确的是,第一行的元素只能是从[0][0]一直往右走,而第一列的元素只能是从[0][0]往下走。
如图所示,matrix矩阵是输入的价值矩阵,result, x, y矩阵是计算过程中定义的矩阵。result[i][j]保存从[0][0]到[i][j]的最优解,x[i][j]和y[i][j]分别保存着(i, j)的最优下一步的行列标号。结合上图和下面的代码。
上面的介绍是从[0][0]开始考虑的,下面的代码实际是从[M-1][N-1]开始考虑的,具体如下:
上面代码中,第一个for循环用于处理输入,紧接着的两个for循环用于处理最有一行和最后一列的result值,因为那些格子上只能往一个方向走,向右或者向下,没有其他的方向。接下来的嵌套for循环用于扫描所有的格子,直到第一个为止。x和y矩阵记录了最优路径。result矩阵保存了最优解的值,他的另一个作用就是消除了重叠子问题,他保存了子问题的值,因此只需要一次运算。
代码整体上其实很简单,只要理解了实现过程的思想,那么就能容易的写出上述代码。
有一个M行N列的矩阵,其中部分格子里面有一些有价值的物品。现在你从左上角出发,每次只能向右或者向下走,走到右下角的时候,你能获取的物品的总价值最大有多少?
输入数据:第一行有两个数字M N,表示这个矩阵有M行N列。然后从第二行开始,有M行整数,每行都有N个非负整数,表示这一格的物品的价值
输出数据:可以获取的最大的物品总价值
数据范围:0<M, N<=1000,矩阵中的数字不会超过1000
示例
输入:
4 5
0 0 8 0 0
0 0 0 9 0
0 7 0 0 0
0 0 6 0 0
输出:
17
看到这个题,最初的想法是,找出矩阵的最大值,然后以这个值为界将矩阵分成两个矩阵(左上矩阵:这个最大值为左上矩阵的右下角元素;右下矩阵:这个最大值为右下矩阵的左上角元素)。后来一细想,这样做是有问题的,因为这样就直接抛弃了左下和右上的矩阵,就丢失了很多信息。后来想到用动态规划的方法去解决就清晰多了。
明确一下,矩阵有M行和N列,因为数组序号是从0开始的,因此这里行号和列号我也从0开始,那么最后一行就是(M-1)行,最后一列就是(N-1)列。
动态规划有两大性质:最优子结构和重叠子问题。利用动态规划的思想对此问题进行简单的分析。
假设整体最优解路线必定经过格子[x][y](x,y在矩阵范围内),则整体的最优解必定是([0][0]->[x][y])和([x][y]->[M-1][N-1])这两个矩阵最优解的和,这就是最优子结构;如果基于这个思想去编程实现的话,我们需要对(x,y)在整个矩阵范围内进行扫描,并找到整体最优解的最大值。然而,这样实现就会显现出重叠子问题重复求解的问题。比如,对于([0][0]->[x][y]),他的最优解必定是【([0][0]->[x-1][y])的最优解加上(x,y)上的价值】与【([0][0]->[x][y-1])的最优解加上(x,y)上的价值】的较大者,而([0][0]->[x-1][y])的最优解或者([0][0]->[x][y-1])的最优解,我们可能会多次重复求解。
另一种思考方式是,
我们从某一端开始考虑,比如从[0][0]开始,他的下一步必定是[0][1]或者[1][0]之一的格子,因此,那么问题就变成了([0][0]->[0][1])和([0][1]->[M-1][N-1])的最优解或者([0][0]->[1][0])和([1][0]->[M-1][N-1])的最优解的较大者,那么这个和就是整体的最优解。然后对所有的格子进行这样的考虑。在实现过程中,我们并不知道后面那一部分的最优解,只是计算前面那一部分的最优解,这个计算是一步一步扩大的。需要明确的是,第一行的元素只能是从[0][0]一直往右走,而第一列的元素只能是从[0][0]往下走。
如图所示,matrix矩阵是输入的价值矩阵,result, x, y矩阵是计算过程中定义的矩阵。result[i][j]保存从[0][0]到[i][j]的最优解,x[i][j]和y[i][j]分别保存着(i, j)的最优下一步的行列标号。结合上图和下面的代码。
上面的介绍是从[0][0]开始考虑的,下面的代码实际是从[M-1][N-1]开始考虑的,具体如下:
package com.wl.test; import java.util.Scanner; public class Main { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int max; String line = ""; String[] parts; int lineCount = 0, rowCount = 0; int[][] matrix, result; int[][] x, y; int i, j; while(scanner.hasNextLine()) { line = scanner.nextLine(); parts = line.split(" "); rowCount = Integer.parseInt(parts[0]); lineCount = Integer.parseInt(parts[1]); matrix = new int[rowCount][lineCount]; result = new int[rowCount][lineCount]; x = new int[rowCount][lineCount]; y = new int[rowCount][lineCount]; for(i=0; i<rowCount; i++) { line = scanner.nextLine(); parts = line.split(" "); for(j=0; j<lineCount; j++) { matrix[i][j] = Integer.parseInt(parts[j]); } } result[rowCount-1][lineCount-1] = 0; for(i=rowCount-2; i>=0; i--) { result[i][lineCount-1] = matrix[i][lineCount-1] + result[i+1][lineCount-1]; x[i][lineCount-1] = i+1; y[i][lineCount-1] = lineCount-1; } for(j=lineCount-2; j>=0; j--) { result[rowCount-1][j] = matrix[rowCount-1][j] + result[rowCount-1][j+1]; x[rowCount-1][j] = rowCount-1; y[rowCount-1][j] = j+1; } for(i=rowCount-2; i>=0; i--) { for(j=lineCount-2; j>=0; j--) { if(result[i+1][j] > result[i][j+1]) { max = result[i+1][j]; x[i][j] = i+1; y[i][j] = j; } else { max = result[i][j+1]; x[i][j] = i; y[i][j] = j+1; } result[i][j] = matrix[i][j] + max; } } x[rowCount-1][lineCount-1] = rowCount-1; y[rowCount-1][lineCount-1] = lineCount-1; System.out.println(result[0][0]); printArray(result, rowCount, lineCount); printArray(x, rowCount, lineCount); printArray(y, rowCount, lineCount); printRoute(x, y, rowCount, lineCount); } scanner.close(); } private static void printArray(int[][] array, int m, int n) { for(int i=0; i<m; i++) { for(int j=0; j<n; j++) { System.out.print(array[i][j] + "\t"); } System.out.println(); } System.out.println(); } private static void printRoute(int[][] x, int[][] y, int rowCount, int lineCount) { for(int i=0, j=0, m=0, n=0; i!=(rowCount-1)||j!=(lineCount-1); i=x[m] , j=y[m] ) { m = i; n = j; System.out.print("[" + m + "," + n + "]->"); } System.out.println("[" + (rowCount-1) + "," + (lineCount-1) + "]"); } }
上面代码中,第一个for循环用于处理输入,紧接着的两个for循环用于处理最有一行和最后一列的result值,因为那些格子上只能往一个方向走,向右或者向下,没有其他的方向。接下来的嵌套for循环用于扫描所有的格子,直到第一个为止。x和y矩阵记录了最优路径。result矩阵保存了最优解的值,他的另一个作用就是消除了重叠子问题,他保存了子问题的值,因此只需要一次运算。
代码整体上其实很简单,只要理解了实现过程的思想,那么就能容易的写出上述代码。
相关文章推荐
- 书评:《算法之美( Algorithms to Live By )》
- 动易2006序列号破解算法公布
- Ruby实现的矩阵连乘算法
- C#插入法排序算法实例分析
- 超大数据量存储常用数据库分表分库算法总结
- C#数据结构与算法揭秘二
- C#冒泡法排序算法实例分析
- 算法练习之从String.indexOf的模拟实现开始
- C#算法之关于大牛生小牛的问题
- C#实现的算24点游戏算法实例分析
- c语言实现的带通配符匹配算法
- 浅析STL中的常用算法
- 算法之排列算法与组合算法详解
- C++实现一维向量旋转算法
- Ruby实现的合并排序算法
- C#折半插入排序算法实现方法
- C++动态规划之最长公子序列实例
- C++动态规划之背包问题解决方法
- 基于C++实现的各种内部排序算法汇总
- C++线性时间的排序算法分析