1. 程式人生 > >leetcode中的DP題目總結

leetcode中的DP題目總結

[leetcode 413]Arithmetic Slices

dp解釋

dp[i] 代表以 i 結尾的arithmetic slice的個數。因此有下列關係
dp[i]=dp[i1]+1,ifA[i]A[i1]=A[i1]A[i2]
dp[i]=0,ifA[i]A[i1]A[i1]A[i2]

注意

  • dp中經常會設計有以i結尾的**的個數

程式

public int numberOfArithmeticSlices(int[] A) {
        int len  = A.length;
        if(len < 3
) return 0; int result = 0; int[] dp = new int[len]; for(int i = 1; i < len-1; i++) { if(A[i+1] - A[i] == A[i] - A[i-1]) { dp[i+1] = dp[i] + 1; result += dp[i+1]; } else { dp[i+1] = 0; } } return
result; }

[leetcode 375] Guess Number Higher or Lower II

題目

給一個範圍n,對方心裡想了某個數字(範圍在[1,n],具體大小你並不知道)。你每次猜一個數字,對方會告訴你猜得大了還是猜的小了或者猜對了。如果這次沒有猜對,你需要支付你猜的這個數字的大小。問,給定一個範圍n時,最少支付多少才能保證一定猜到這個數字呢?

解釋

這個問題有點繞,然而卻是一個典型的minmax問題。可以先從簡單的例子來想。
在一個範圍[m,n]中猜數字,
1. 如果只有一個數,比如[5,5],那麼一次就會猜中,就不用支付,因此最少支付0。
2. 如果範圍裡有兩個數,比如[5,6],你猜5如果錯了,你就知道是6了,需要支付5;你猜6如果錯了,同樣一定會知道答案,但是需要支付6。相比之下,支付5就可以一定猜出來。
3. 如果範圍裡有3個數,比如[5,6,7],你猜5,那麼[6,7]中猜6一定知道答案,共支付11;你猜6,[5,5]和[7,7]不用支付就可以知道答案,共支付6;你猜7,那麼[5,6]需要支付5,共支付12;比較11,6,12可知,最少支付為6。
4. 一般的情況,我們不管範圍裡有幾個數,用d

p[i][j]來表示在[i,j]範圍內猜數字,最難猜到的數字讓我們支付了多少(最少支付了多少可以保證猜到這個範圍內的任何數字)。這相當於,如果我們猜任何一個k[i,j],一定會得到需要猜的數字的是在[i,k1]這個範圍還是在[k+1,j]這個範圍,相應再去找dp[i][k1],dp[k+1][j]來看這兩個範圍的保底花費。由於我們並不知道這個數字在哪個範圍,因此要取最大的來保證我們一定可以猜出來最難猜的那個數字。接下來就是dp的遞推式了

dp[i][j]=min(dp[i][j],max(dp[i][k1],dp[k+1][j])),k[i,j]

注意

大範圍需要先知道小範圍,用什麼順序來求dp陣列呢?如下圖:
這裡寫圖片描述

程式

    public int getMoneyAmount(int n) {
        int dp[][] = new int[n+1][n+1];

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

                dp[i][i+j] = Integer.MAX_VALUE;

                for(int k = i; k <= i+j; k++) {
                    dp[i][i+j] = Math.min(dp[i][i+j], k + Math.max(dp[i][k-1], (k+1 > i+j)?0:dp[k+1][i+j]));
                }
            }
        }
        return dp[1][n];

    }

[leetcode 115] Distinct Subsequences

題目

給定兩個字串S,T。S中有多少個不同的子序列恰好等於T
這個題目很容易理解錯,題目想問的並不是S和T有多少個不同的公共子序列。舉一個例子
S=”rabbbit”, T=”rabbit”,則返回3。原因是:
S的三個子序列rab-bit , ra-bbit,rabb-it恰好等於T,因此返回3。

DP解釋

i[1,S.len]j[1,T.len]分別代表當前指向S和T的位置。用dp[j][i]代表S的前i個字母組成的子串中有多少個子序列恰好等於T的前j個字母組成的子串。則有,
S[i]=T[j], dp[j][i]=dp[j1][i1]+dp[j][i1]
S[i]T[j], dp[j][i]=dp[j][i1]
以上的遞推式的意思是:
1. S增加一個字母,但是和T的當前字母不相等時,S不會增加新的子序列和T相等,因此dp[j][i]=dp[j][i1]
2. S增加一個字母,和T的當前字母相等時,S會增加dp[j1][i1]個子序列和T相等(任何一個相等的情況在末尾加上同一個字母依然符合條件),再計算上之前的序列得到dp[j][i]=dp[j1][i1]+dp[j][i1]

注意

  1. 計算的順序是一行一行算
  2. 初值的設計是dp[0][any]均為1,可以理解為S的空序列等於一個空串,因此為1
  3. 只需要計算j>=i的情況,因為若S比T短,那一定dp一定為0
  4. 思考dp遞推式時,定住T的長度,增加S的長度來思考會更容易想明白問題

程式

    public int numDistinct(String s, String t) {
        int lens = s.length();
        int lent = t.length();
        int[][] dp = new int[lent+1][lens+1];

        for(int i = 0; i <= lens; i++)
            dp[0][i] = 1;


        for(int j = 1; j <= lent; j++) {
            for(int i = j; i <= lens; i++) {
                if(s.charAt(i-1) == t.charAt(j-1))
                    dp[j][i] = dp[j-1][i-1]+dp[j][i-1];
                else
                    dp[j][i] = dp[j][i-1];
            }
        }

        return dp[lent][lens];

    }

[leetcode 120] Triangle

題目

一個三角形從頂向下尋找一條和最小的路徑

DP解釋

  1. 一個最普通的想法:自頂向下,dp[i][j]為走到第i層的第j個節點是的和最小路徑的和,那麼則有dp[i][j]=min(dp[i1][j1],dp[i1][j]),由於每層的資訊沒有必要都存下來,因此只用存當前層,則空間複雜度為O(n)。自頂向下的缺點是:需要特殊處理邊界情況,並且還需要求最底層的最小值。
  2. 更簡潔的方法:自底向上,考慮到從上向下走的最小值和從下向上走是沒有區別的。由於到第i層第j個節點只能走向第i+1層的第j個節點和第j+1個節點,因此從向上走到第i層第j個節點的最小路徑的總和表示為dp[i][j],則有dp[i][j]=min(dp[i+1][j],dp[i+1]