最大子段-n上找m个子段的和为最大-动态规划-二维dp+滚动数组dp优化

来源:互联网 发布:pdf.js 打开 word文档 编辑:程序博客网 时间:2024/06/18 12:22

1.二维dp

dp[i][j]代表的是j长度上找到i段,使得i段和最大。(其中最后一段的最后一位一定要是a[j],这句话不理解的可以看看http://blog.csdn.net/qq_36523667/article/details/78598426)


这时最后一段分为两种情况:

1.a[j]是最后一段的一部分

2.a[j]自己成为最后一段


用算式表示为

1.dp[i][j - 1] + a[j]意思就是从j-1中重新选i段,再把j添加到最后一段的最后

2.max(dp[i - 1][k]) + a[j],i - 1 <= k < j,意思就是从i-1到j中重新选i段取最大值,再加上最后一段组成i段


代码

// TODO: 2017/11/22 动态规划m子段-二维dpint dynamicM(int[] a, int m) {    int n = a.length;    int[][] dp = new int[100][100];    for (int i = 1; i <= m; i++) {        for (int j = i; j < n; j++) {            int max = 0;            //在当前j找?段的最优解(?是因为找几段并没有体现出来)            for (int k = i - 1; k < j; k++) {                max = Math.max(max, dp[i - 1][k]);            }            dp[i][j] = Math.max(dp[i][j - 1] + a[j], max + a[j]);        }    }    return dp[m][n - 1];}

2.滚动数组优化

由于上面的时间复杂度到达了n^3,所以不优化会爆掉的。

仔细看一下上面的代码,虽然思考模式是j中找i个,但是实际上i根本就没有用到,所以我们先把二维dp变成一维的。


此外

for (int k = i - 1; k < j; k++) {

                max = Math.max(max, dp[i - 1][k]);

            }

只需要记录下这个j位上的最大值即可。下一次循环的时候,再去使用记录下来的最大值。


// TODO: 2017/11/22 动态规划m子段-滚动数组int dynamicM2(int[] a, int m) {    int n = a.length;    int[] max = new int[100];    int[] dp = new int[100];    int mMax = Integer.MIN_VALUE;    for (int i = 1; i <= m; i++) {        mMax = Integer.MIN_VALUE;        for (int j = i; j < n; j++) {            dp[j] = Math.max(dp[j - 1] + a[j], max[j - 1] + a[j]);            max[j - 1] = mMax;            mMax = Math.max(mMax, dp[j]);        }    }    return mMax;}


综上所述,第一份代码写出来,和二维dp的思维有关,是有迹可循的。但是第二份代码写出来,完全是因为,发现了,第一份代码虽然思维上是找在不同段上找不同i的最优解,但是i的作用没有体现出来,所以可以察觉到可以把二维降成一维,并且记录下max就可以消除一个循环的复杂度。第二份代码与技术有关。

当然了,第二份代码想直接写出来,需要另一种思维。


第二份代码到底是怎样的一种思维?

(前方高能,灵魂升华!)


先重新讲一下有关于这一题的dp:

n中找m段使之和最大,最后一段的最后一位有可能在各个地方,可能正好就是在n上,也可能是n-1上。。。

所以这里就把状态抽象出来了:j上找i个使之和最大,但是多了一个条件,a[j]必须在最后一段的最后一位上。

所以我们的j是可以在不同的位置上

for (int j = i; j < n; j++) {
以此实现了遍历每一种情况。


所以我们的思维就是这样的,在不同的j上找m段使之最大。这些不同的结果我们取一个最大值就是我们的结果了。


实在不懂我来个图。


你最后一段的最后一个位置可以在任何一个地方。所以我们的j循环就是针对这个而设置的。那么你的dp[j]就代表了最后一段的最后一个数在所有不同位置上的找m段使之和最大的情况。


------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

好吧,我前面还是讲的太乱了。最后简单浓缩的讲一下。

还是看这个图。你的第一段的第一个数,和你最后一段的最后一个数,有好多好多个可能。我们用

for (int i = 1; i <= m; i++) {    for (int j = i; j < n; j++) {
这两个for循环的目的就是把你的第一个点(后面称之为左节点)和你最后一个点(后面称之为右节点)的所有情况都列举出来(这里i=1是因为我们传进来的数组是从1开始赋值的),然后,再在这一段区间中去找m段使之和最大。(这就是下面这个公式中dp[j]的含义)

dp[j] = Math.max(dp[j - 1] + a[j], max[j - 1] + a[j]);
所以这个公式的可以这样理解:

如果你的a[j]是最后一段的一部分,你就在你的左节点相同,右节点-1的这种情况找到m个,a[j]添加到最后一段中去。

如果你的a[j]自成一段,你就需要在你的左节点相同,右节点范围左节点到原右节点之间找到m-1个。

max[j - 1] = mMax;mMax = Math.max(mMax, dp[j]);

mMax就是记录了你当前位之前最大的dp


这题让我领悟到了:最对的思维+最强的技巧=最优的性能

原创粉丝点击