1. 程式人生 > >最大子段-n上找m個子段的和為最大-動態規劃-二維dp+滾動陣列dp優化

最大子段-n上找m個子段的和為最大-動態規劃-二維dp+滾動陣列dp優化

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子段-二維dp
int 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

這題讓我領悟到了:最對的思維+最強的技巧=最優的效能