LeetCode動態規劃題總結【持續更新】

如下題號均爲LeetCode題號,便於查看原題。node

10. Regular Expression Matching算法

題意:實現字符串的正則匹配,包含'.' 和 '*'。'.' 匹配任意一個字符,"*" 匹配 '*' 以前的0個或多個字符。express

example:數組

isMatch("aa","a") → false
isMatch("aa","aa") → true
isMatch("aaa","aa") → false
isMatch("aa", "a*") → true
isMatch("aa", ".*") → true
isMatch("ab", ".*") → true
isMatch("aab", "c*a*b") → true
View Code

思路:輸入字符串 s[0...m] 和 p[0...n]curl

f[i][j] 表示 s[0..i-1] 和 p[0..j-1] 匹配,咱們須要判斷 s 和 p 是否匹配,就是求 f[m][n] 的值是否爲true,因此要日後更新 f[i][j] 的值。ide

更新思路以下:函數

一、if p[j-1]!='*', f[i][j] = f[i-1][j-1] & (s[i-1]==p[j-1] || p[j-1]=='.')優化

二、if p[j-1]=='*', 看 '*' 匹配多少個字符,即匹配多少個p[j-2]。ui

若是 '*' 匹配0個字符,此時,p[0...j-1]==p[0...j-3],f[i][j]=f[i][j-2];url

若是 '*' 匹配1個字符,此時,p[0...j-1]==p[0...j-2],f[i][j]=f[i][j-1];

若是 '*' 匹配多個字符,此時,p[0...j-1]=={ p[0: j-2], p[j-2], ... , p[j-2] },f[i][j]=(s[i-1]==p[j-2] || p[j-2]=='.') & f[i-1][j]

public boolean isMatch(String s, String p)
    {
        int m = s.length();
        int n = p.length();
        boolean[][] f = new boolean[m+1][n+1];
        f[0][0] = true;
        
        for (int i = 0; i <= m; i++)
        {
            for (int j = 1; j <= n; j++)
            {
                if(p.charAt(j-1)!='*')
                {
                    f[i][j] = i>0 && f[i-1][j-1] && (s.charAt(i-1)==p.charAt(j-1) || p.charAt(j-1)=='.');
                }
                else
                {
                    f[i][j] = (j>1&&f[i][j-2]) || (i>0&&(s.charAt(i-1)==p.charAt(j-2)||p.charAt(j-2)=='.')&&f[i-1][j]);
                }
            }
        }
        
        return f[m][n];
    }
View Code

32. Longest Valid Parentheses

題意:Given a string containing just the characters '(' and ')', find the length of the longest valid (well-formed) parentheses substring.

 example:

"(()" --> 2

")()())" --> 4

思路:dp[i] 表示在 i 處的匹配的最長子串的長度,維護一個全局max變量,記錄到 i 處的最長長度

一、若是s[i]=='(',dp[i]=0

二、若是s[i]==')',若是s[i-1]=='(',dp[i]=dp[i-2]+2;若是s[i-1]==')' 而且 s[i-dp[i-1]-1]=='(',dp[i]=dp[i-1]+2+dp[i-dp[i-1]-2]

def longestValidParentheses(self, s):
        """
        :type s: str
        :rtype: int
        """
        cmax = 0
        dp = [0 for x in range(len(s))]
        for i in range(1, len(s)):
            if s[i] == ")":
                if s[i - 1] == "(":
                    if i - 2 >= 0:
                        dp[i] = dp[i - 2] + 2
                    else:
                        dp[i] = 2
                    cmax = max(cmax, dp[i])
                else:
                    if i - dp[i - 1] - 1 >= 0 and s[i - dp[i - 1] - 1] == "(":
                        if i - dp[i - 1] - 2 >= 0:
                            dp[i] = dp[i - 1] + 2 + dp[i - dp[i - 1] - 2]
                        else:
                            dp[i] = dp[i - 1] + 2
                        cmax = max(cmax, dp[i])
        return cmax
View Code

 44. Wildcard Matching

思路:解法和10.很是像,輸入字符串 s[0...m] 和 p[0...n]

f[i][j] 表示 s[0..i-1] 和 p[0..j-1] 匹配,咱們須要判斷 s 和 p 是否匹配,就是求 f[m][n] 的值是否爲true,因此要日後更新 f[i][j] 的值。

example:

isMatch("aa","a") → false
isMatch("aa","aa") → true
isMatch("aaa","aa") → false
isMatch("aa", "*") → true
isMatch("aa", "a*") → true
isMatch("ab", "?*") → true
isMatch("aab", "c*a*b") → false
View Code

一、若是p[j-1]!='*',f[i][j]=f[i-1][j-1] & (s[i-1]==p[j-1] || p[j-1]=='?')

二、若是p[j-1]=='*',f[i][j]=f[i][j-1] || f[i-1][j]

Equation 1). means that if p[j-1] is not *, f(i,j) is determined by if s[0:i-2] matches p[0:j-2] and if (s[i-1]==p[j-1] or p[j-1]=='?'). 

Equation 2). means that if p[j-1] is *, f(i,j) is true if either f(i,j-1) is true: s[0:i-1] matches p[0:j-2] and * is not used here; or f(i-1,j) is true: s[0:i-2] matches p[0:j-1] and * is used to match s[i-1].

public boolean isMatch(String s, String p) {
        int sl = s.length();
        int pl = p.length();
        
        boolean[][] a = new boolean[sl+1][pl+1];
        a[0][0] = true;
        for (int i = 1; i <= pl; i++) {
            a[0][i] = p.charAt(i-1) == '*' ? a[0][i-1] : false;
        }
        
        for (int i = 1; i <= sl; i++) {
            for (int j = 1; j <= pl; j++) {
                if (p.charAt(j-1) != '*') {
                    a[i][j] = a[i-1][j-1] & (s.charAt(i-1) == p.charAt(j-1) || p.charAt(j-1) == '?');
                }
                else
                {
                    a[i][j] = a[i][j-1] || a[i-1][j];
                }
            }
        }
        return a[sl][pl];
    }
View Code

 53. Maximum Subarray

題意:求最大子串和

最優化問題,能夠用DP解決,而後考慮構造子問題,能夠考慮maxSubArray(int A[], int i, int j),可是遞歸方程不太好想到,因此考慮

maxSubArray(int A[], int i),表示以A[i]結尾的子串的最大和。

因此maxSubArray(A, i) = (maxSubArray(A, i - 1) > 0 ? maxSubArray(A, i - 1) : 0 ) + A[i];

public int maxSubArray(int[] nums) {
        int length = nums.length;
        if (length < 1)
            return 0;
        
//         int[] dp = new int[length];
//         dp[0] = nums[0];
//         int max = dp[0];
        int curMax = nums[0];
        int max = curMax;
        for (int i = 1; i < length; i++) 
        {
          //  dp[i] = Math.max(dp[i-1]+nums[i], nums[i]);
          //  max = Math.max(max, dp[i]);
            curMax = Math.max(curMax+nums[i], nums[i]);
            max = Math.max(max, curMax);
        }
        return max;
}
View Code

 62. Unique Paths

題意:求從左上到右下有多少不一樣的路徑

p[i][j]表示到(i,j)的路徑數,能夠觀察到,機器人到達(i,j),要麼從左邊,要麼從上邊,因此遞歸方程爲:

p[i][j]=p[i-1][j]+p[i][j-1]

public int uniquePaths(int m, int n) {
        int[][] p = new int[m][n];
        for(int i = 0; i<m;i++){
            p[i][0] = 1;
        }
        for(int j= 0;j<n;j++){
            p[0][j]=1;
        }
        for(int i = 1;i<m;i++){
            for(int j = 1;j<n;j++){
                p[i][j] = p[i-1][j]+p[i][j-1];
            }
        }
        return p[m-1][n-1];
}
View Code

 63. Unique Paths II

題意:在62. 基礎上加入障礙,求如今有多少路徑。

相似p[i][j]表示到(i,j)的路徑數,若是(i,j)=1,則p[i][j]=0,若是(i,j)=0,p[i][j]=p[i-1][j]+p[i][j-1]

須要注意的是邊緣狀況,即p[0][j]和p[i][0]的值

public int uniquePathsWithObstacles(int[][] obstacleGrid) {
        int n = obstacleGrid.length;
        int m = obstacleGrid[0].length;
        int[][] p = new int[n][m];
        
        for (int i = 0; i < m; i++){
            p[0][i] = 1 - obstacleGrid[0][i];
            if (p[0][i] == 0)   break;
        }
        for (int j = 0; j < n; j++){
            p[j][0] = 1 - obstacleGrid[j][0];
            if (p[j][0] == 0) break;
        }
        for ( int i = 1; i < n; i++){
            for (int j = 1; j < m; j++) {
                if(obstacleGrid[i][j] == 1)
                    p[i][j] = 0;
                else p[i][j] = p[i-1][j] + p[i][j-1];
            }
        }
        return p[n-1][m-1];
    }
View Code

64. Minimum Path Sum

題意:找到一條從左上到右下的路徑,要使其和最小。

p[i][j]表示到(i,j)的路徑和,遞歸方程爲:p[i][j]=min(p[i-1][j],p[i][j-1])+(i,j)

注意邊界條件便可

public int minPathSum(int[][] grid) {
        int m = grid.length;
        int n = grid[0].length;
        return minPathSum(grid, m, n);
    }
public int minPathSum(int[][] grid, int m, int n) {
        int[][] p = new int[m][n];
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (i == 0 && j == 0)
                    p[i][j] = grid[i][j];
                else if (i == 0 && j > 0)
                    p[i][j] = p[i][j-1] + grid[i][j];
                else if (i > 0 && j == 0)
                    p[i][j] = p[i-1][j] + grid[i][j];
                else 
                    p[i][j] = Math.min(p[i-1][j], p[i][j-1]) + grid[i][j];
            }
        }
        return p[m-1][n-1];
}
View Code

70. Climbing Stairs

題意:爬n節樓梯,每步能爬1或2步,求有多少種不一樣的方法爬到頂端。

p[i]表示到i的路徑數,則遞歸方程爲:p[i]=p[i-1]+p[i-2]

public int climbStairs(int n) {
        int[] p = new int[n+1];
        p[0] = 1;
        p[1] = 1;
        for (int i = 2; i <= n; i++)
        {
            p[i] = p[i-1] + p[i-2];
        }
        return p[n];
}
View Code

 72. Edit Distance

題意:這題就是求編輯距離。

p[i][j]表示word1[0...i-1]和word2[0...j-1]的編輯距離

一、若是word1[i-1]==word2[j-1],則p[i][j]=p[i-1][j-1]

二、若是word1[i-1]!=word2[j-1],則p[i][j]=min(p[i-1][j-1]+1,p[i-1][j]+1,p[i][j-1]+1)

第1個條件好理解,主要說說第2個條件

  1. Replace word1[i - 1] by word2[j - 1] (dp[i][j] = dp[i - 1][j - 1] + 1 (for replacement));
  2. Delete word1[i - 1] and word1[0..i - 2] = word2[0..j - 1] (dp[i][j] = dp[i - 1][j] + 1 (for deletion));
  3. Insert word2[j - 1] to word1[0..i - 1] and word1[0..i - 1] + word2[j - 1] = word2[0..j - 1] (dp[i][j] = dp[i][j - 1] + 1 (for insertion)).

好比p[i][j]=p[i-1][j]+1,將word1[i-1]刪掉(一次操做),而後就是看word1[0...i-2]和word2[0...j-1]的編輯距離,即p[i-1][j]

public int minDistance(String word1, String word2) {
        int len1 = word1.length();
        int len2 = word2.length();
        int[][] p = new int[len1+1][len2+1];
        for (int i = 0; i <= len1; i++) {
            p[i][0] = i;
        }
        for (int i = 0; i <= len2; i++) {
            p[0][i] = i;
        }
        for (int i = 1; i <= len1; i++) {
            for (int j = 1; j <= len2; j++) {
                if (word1.charAt(i-1) == word2.charAt(j-1)) {
                    p[i][j] = p[i-1][j-1];
                }else {
                    p[i][j] = min(p[i-1][j]+1, p[i][j-1]+1, p[i-1][j-1]+1);
                }
            }
        }
        return editDst[len1][len2];
    }
View Code

 85. Maximal Rectangle

題意:在0,1填充的矩形中找出全是1的最大的矩形。

以(i,j)爲右下頂點的矩形的最大的面積爲[right(i,j)-left(i,j)]*height(i,j)

因此咱們維護3個變量,left(i,j),right(i,j)和height(i,j)

一、left(i,j)記錄的是左邊界,left(i,j)=max(left(i-1,j), curleft)

二、right(i,j)記錄的是右邊界,right(i,j)=min(right(i-1,j), curright)

三、height(i,j)記錄的是上邊界,若是matrix[i][j]=='1',則height(i,j)=height(i-1,j)+1,不然 height(i,j)=0

    public int maximalRectangleUsingDP(char[][] matrix)
    {
        int m = matrix.length;
        int n = matrix[0].length;
        int maxA = 0;
        int[] left = new int[n];
        int[] right = new int[n];
        for (int i = 0; i < n; i++)
        { right[i]=n; }
        int[] height = new int[n];
        for (int i = 0; i < m; i++)
        {
            for (int j = 0; j < n; j++)
                height[j] = matrix[i][j]=='1'?height[j]+1:0;
            int preleft = 0;
            for (int j = 0; j < n; j++)
            {
                if (matrix[i][j] == '1')
                    left[j] = Math.max(left[j], preleft);
                else{left[j]=0;preleft=j+1;}
            }
            int preright = n;
            for (int j = n-1; j >= 0; j--)
            {
                if (matrix[i][j] == '1')
                    right[j] = Math.min(right[j], preright);
                else{right[j]=n;preright=j;}
            }
            for (int j = 0; j < n; j++)
                maxA = Math.max((right[j]-left[j])*height[j], maxA);
        }
        return maxA;
    }
View Code

 87. Scramble String

題意:一個字符串有不少種二叉表示法,判斷兩個字符串s1,s2是否能夠作這樣的交換。

使用一個3維數組res[len][len][len+1],其中第一維爲s1的起始索引,第二維爲s2的起始索引,第三維爲子串的長度。

res[i][j][k]表示的是以i和j分別爲s1和s2起點的長度爲k的字符串是否是互爲scramble。

咱們首先是把當前s1[i...i+len-1]字符串劈一刀分紅兩部分,而後分兩種狀況:第一種是左邊和s2[j...j+len-1]左邊部分是否是scramble,以及右邊和s2[j...j+len-1]右邊部分是否是scramble;第二種狀況是左邊和s2[j...j+len-1]右邊部分是否是scramble,以及右邊和s2[j...j+len-1]左邊部分是否是scramble。若是以上兩種狀況有一種成立,說明s1[i...i+len-1]和s2[j...j+len-1]是scramble的。

上面說的是劈一刀的狀況,對於s1[i...i+len-1]咱們有len-1種劈法,在這些劈法中只要有一種成立,那麼兩個串就是scramble的。

總結起來遞推式是res[i][j][len] = || (res[i][j][k]&&res[i+k][j+k][len-k] || res[i][j+len-k][k]&&res[i+k][j][len-k]) 對於全部1<=k<len,也就是對於全部len-1種劈法的結果求或運算。由於信息都是計算過的,對於每種劈法只須要常量操做便可完成,所以求解遞推式是須要O(len)(由於len-1種劈法)。
如此總時間複雜度由於是三維動態規劃,須要三層循環,加上每一步須要線行時間求解遞推式,因此是O(n^4)。雖然已經比較高了,可是至少不是指數量級的,動態規劃仍是有很大有事的,空間複雜度是O(n^3)。

    public boolean isScramble(String s1, String s2) {
        int n1 = s1.length();
        int n2 = s2.length();
        if(n1 != n2)
            return false;
        if(n1 == 0)
            return true;
        boolean[][][] res = new boolean[n1][n2][n1+1];
        for(int i = 0; i<n1; i++){
            for(int j=0;j<n2;j++){
                res[i][j][1] = (s1.charAt(i)==s2.charAt(j));
            }
        }
        for(int len=2; len <= n1; len++){
            for(int i=0; i<= n1-len; i++){
                for(int j=0; j<=n2-len; j++){
                    for(int k = 1; k<len; k++){
                        res[i][j][len] |= res[i][j][k]&res[i+k][j+k][len-k] || res[i][j+len-k][k]&res[i+k][j][len-k];
                    }
                }
            }
        }
        return res[0][0][n1];
    }
View Code

 91. Decode Ways

題意:求解有多少種不一樣的解碼方式。

res[i]表示s[0...i-1]的不一樣的解碼方式,如今要更新到res[n]。

考慮s(i-2,i-1)的取值範圍

一、00,30~90     ---> res[i]=0

二、01-09,27~29,31~99    ---> res[i]=res[i-1]

三、10,20       ---> res[i]=res[i-2]

四、11~19,21~26   ---> res[i]=res[i-1]+res[i-2]

public int numDecodings(String s) {
        if (s==null || s.length()==0 || s.charAt(0)=='0'){
            return 0;
        }
        int n = s.length();
        int[] res = new int[n+1];
        res[0]=1;
        res[1]=1;
        for(int i=1; i<n; i++){
            char ch = s.charAt(i); 
            if(ch == '0'){
                if(s.charAt(i-1)=='1' || s.charAt(i-1)=='2'){    //10, 20
                    res[i+1]=res[i-1];
                }else{    // 00, 30,40,50,60,70,80,90
                    return 0;
                }
            }
            else{
                if(s.charAt(i-1)=='0' || s.charAt(i-1)>'2'){    // 01-09, 31-99
                    res[i+1]=res[i];
                }else if(s.charAt(i-1)=='2' && ch<='6' || s.charAt(i-1)=='1'){    //21-26, 11-19
                    res[i+1]=res[i]+res[i-1];
                }else{                // 27-29
                    res[i+1]=res[i];
                }
            }
        }
        return res[n];
    }
View Code

 96. Unique Binary Search Trees

題意:判斷有多少種不一樣的二叉搜索樹結構

能夠用動態規劃解決。維護兩個變量:

G(n):長度爲n的不一樣的二叉搜索樹的數量

F(i,n):以i爲跟結點的長度爲n的不一樣的二叉搜索樹的數量

則G(n) = F(1, n) + F(2, n) + ... + F(n, n),其中G(0)=1,G(1)=1

考慮[1, 2, 3, 4, 5, 6, 7],跟結點爲3,則左子樹[1,2],右子樹[4,5,6,7]

F(3,7)=G(2)*G(4),因此有F(i, n) = G(i-1) * G(n-i)

得出:G(n) = G(0) * G(n-1) + G(1) * G(n-2) + … + G(n-1) * G(0)

 1 public int numTrees(int n) {
 2     int [] G = new int[n+1];
 3     G[0] = G[1] = 1;
 4 
 5     for(int i=2; i<=n; ++i) {
 6         for(int j=1; j<=i; ++j) {
 7             G[i] += G[j-1] * G[i-j];
 8         }
 9     }
10 
11     return G[n];
12 }
View Code

 95. Unique Binary Search Trees II

這道題是求解全部可行的二叉查找樹,從Unique Binary Search Trees中咱們已經知道,可行的二叉查找樹的數量是相應的卡特蘭數,不是一個多項式時間的數量級,因此咱們要求解全部的樹,天然是不能多項式時間內完成的了。算法上仍是用求解NP問題的方法來求解,也就是N-Queens中介紹的在循環中調用遞歸函數求解子問題。思路是每次一次選取一個結點爲根,而後遞歸求解左右子樹的全部結果,最後根據左右子樹的返回的全部子樹,依次選取而後接上(每一個左邊的子樹跟全部右邊的子樹匹配,而每一個右邊的子樹也要跟全部的左邊子樹匹配,總共有左右子樹數量的乘積種狀況),構造好以後做爲當前樹的結果返回。

 1 public List<TreeNode> generateTrees(int n) {
 2     List<TreeNode> res = new ArrayList<>();
 3     if (n < 1)
 4         return res;
 5     return generateT(1, n);
 6 }
 7 private List<TreeNode> generateT(int left, int right){
 8     List<TreeNode> res = new ArrayList<>();
 9     if (left > right){
10         res.add(null);
11         return res;
12     }
13     if (left == right){
14         res.add(new TreeNode(right));
15         return res;
16     }
17     
18     for (int i=left; i<=right; i++){
19         List<TreeNode> leftList = generateT(left, i-1);
20         List<TreeNode> rightList = generateT(i+1, right);
21         
22         for (TreeNode lnode : leftList){
23             for (TreeNode rnode : rightList){
24                 TreeNode root = new TreeNode(i);
25                 root.left = lnode;
26                 root.right = rnode;
27                 res.add(root);
28             }
29         }
30     }
31     return res;
32 }
View Code

121. Best Time to Buy and Sell Stock

題意:給一數組,表示天天的股票的價格,求最多進行一次交易,最大化收益。

最大化第 i 天的最大化收益就是第 i 天的價格 - 之前的最低價格。因此咱們維護一個變量min[i],表示前 i-1 天的最低價格。則:

min[i] = min(min[i-1], prices[i-1])
maxProfit = max(maxProfit, prices[i]-min[i])

 1 public int maxProfit(int[] prices) {
 2     int len = prices.length;
 3     if (len <= 1)
 4         return 0;
 5     
 6     int[] min = new int[len];
 7     min[0] = prices[0];
 8     int maxProfit = 0;
 9     for (int i = 1; i < len; i++)
10     {
11         min[i] = Math.min(min[i-1], prices[i-1]);
12         maxProfit = Math.max(maxProfit, prices[i]-min[i]);
13     }
14     return maxProfit;
15 }
View Code

123. Best Time to Buy and Sell Stock III

題意:給一數組,表示天天的股票的價格,求最多進行兩次交易,最大化收益。

能夠在整個區間的每一點切開,而後分別計算左子區間和右子區間的最大值,而後再用O(n)時間找到整個區間的最大值。

O(n^2)的算法很容易想到:

找尋一個點j,將原來的price[0..n-1]分割爲price[0..j]和price[j..n-1],分別求兩段的最大收益。

進行優化:

對於點j+1,求price[0..j+1]的最大收益時,不少工做是重複的,在求price[0..j]的最大收益中已經作過了。

相似於上題,能夠在O(1)的時間從price[0..j]推出price[0..j+1]的最大收益。

可是如何從price[j..n-1]推出price[j+1..n-1]?反過來思考,咱們能夠用O(1)的時間由price[j+1..n-1]推出price[j..n-1]。

最終算法:

數組l[i]記錄了price[0..i]的最大收益,

數組r[i]記錄了price[i..n]的最大收益。

已知l[i],求l[i+1]是簡單的,一樣已知r[i],求r[i-1]也很容易。

最後,咱們再用O(n)的時間找出最大的l[i]+r[i],即爲題目所求。

 1 public int maxProfit(int[] prices) {
 2     int len = prices.length;
 3     if (len <= 1)
 4         return 0;
 5     
 6     int[] left = new int[len];
 7     left[0] = 0;
 8     // 從前日後
 9     int minPrice = prices[0];
10     for (int i = 1; i < len; i++)
11     {
12         minPrice = Math.min(prices[i-1], minPrice);
13         left[i] = Math.max(prices[i]-minPrice, left[i-1]);
14     }
15     // 從後往前
16     int max = left[len-1];
17     int maxPrice = prices[len-1];
18     int right = 0;
19     for (int i = len-2; i >= 0; i--)
20     {
21         maxPrice = Math.max(maxPrice, prices[i+1]);
22         right = Math.max(right, maxPrice-prices[i]);
23         max = Math.max(max, right+left[i]);
24     }
25     return max;
26 }
View Code

188. Best Time to Buy and Sell Stock IV

題意:在最多進行k次交易的前提下,求最大收益。

咱們仍是使用「局部最優和全局最優解法」。咱們維護兩種量,一個是當前到達第i天能夠最多進行j次交易,最好的利潤是多少(global[i][j]),另外一個是當前到達第i天,最多可進行j次交易,而且最後一次交易在當天賣出的最好的利潤是多少(local[i][j])。下面咱們來看遞推式,全局的比較簡單,

global[i][j]=max(local[i][j],global[i-1][j]),

也就是去當前局部最好的,和過往全局最好的中大的那個(由於最後一次交易若是包含當前天必定在局部最好的裏面,不然必定在過往全局最優的裏面)。

全局(到達第i天進行j次交易的最大收益) = max{局部(在第i天交易後,剛好知足j次交易),全局(到達第i-1天時已經知足j次交易)}

對於局部變量的維護,遞推式是

local[i][j]=max(global[i-1][j-1]+max(diff,0),local[i-1][j]+diff),

也就是看兩個量,第一個是全局到i-1天進行j-1次交易,而後加上今天的交易,若是今天是賺錢的話(也就是前面只要j-1次交易,最後一次交易取當前天),第二個量則是取local第i-1天j次交易,而後加上今天的差值(這裏由於local[i-1][j]好比包含第i-1天賣出的交易,因此如今變成第i天賣出,並不會增長交易次數,並且這裏不管diff是否是大於0都必定要加上,由於不然就不知足local[i][j]必須在最後一天賣出的條件了)。

局部(在第i天交易後,總共交易了j次) =  max{狀況2,狀況1}

狀況1:在第i-1天時,剛好已經交易了j次(local[i-1][j]),那麼若是i-1天到i天再交易一次:即在第i-1天買入,第i天賣出(diff),則這不併不會增長交易次數!【例如我在第一天買入,次日賣出;而後次日又買入,第三天再賣出的行爲  和   第一天買入,第三天賣出  的效果是同樣的,其實只進行了一次交易!由於有連續性

狀況2:第i-1天后,共交易了j-1次(global[i-1][j-1]),所以爲了知足「第i天事後共進行了j次交易,且第i天必須進行交易」的條件:咱們能夠選擇1:在第i-1天買入,而後再第i天賣出(diff),或者選擇在第i天買入,而後一樣在第i天賣出(收益爲0)。

 1 public int maxProfit(int k, int[] prices) {
 2     int len = prices.length;
 3     if (len == 0)   return 0;
 4     if (k >= len / 2) return quickSolve(prices);
 5     int [][] local = new int[len][k+1];
 6     int [][] global = new int[len][k+1];
 7     
 8     for (int i = 1; i < len; i++)
 9     {
10         int diff = prices[i]-prices[i-1];
11         for (int j = 1; j <= k; j++)
12         {
13             local[i][j] = Math.max(global[i-1][j-1]+Math.max(0,diff), local[i-1][j]+diff);
14             global[i][j] = Math.max(global[i-1][j], local[i][j]);
15         }
16     }
17     return global[len-1][k];
18 }
19 private int quickSolve(int[] prices) {
20     int len = prices.length, profit = 0;
21     for (int i = 1; i < len; i++)
22         // as long as there is a price gap, we gain a profit.
23         if (prices[i] > prices[i - 1]) profit += prices[i] - prices[i - 1];
24     return profit;
25 }
View Code

264. Ugly Number II

題意:求第n個ugly數。ugly數定義爲因子只有2,3,5的數,如:1,2,3,4,5,6,8,9,10,12等,定義1爲第一個ugly數。

k[n-1]爲第n個ugly數,則k[0]=1

k[1]=min(k[0]*2,k[0]*3,k[0]*5)  ->  k[0]*2

k[2]=min(k[1]*2,k[0]*3,k[0]*5)  ->  k[0]*3

k[3]=min(k[1]*2,k[1]*3,k[0]*5)  ->  k[1]*2

 1 public int nthUglyNumber(int n) 
 2 {
 3     int[] k = new int[n];
 4     k[0] = 1;
 5     int t1 = 0, t2 = 0, t3 = 0;
 6     for (int i = 1; i < n; i++)
 7     {
 8         k[i] = min(k[t1]*2, k[t2]*3, k[t3]*5);
 9         if (k[i]==k[t1]*2)  t1 += 1;
10         if (k[i]==k[t2]*3)  t2 += 1;
11         if (k[i]==k[t3]*5)  t3 += 1;
12     }
13     return k[n-1];
14 }
15 private int min(int a, int b, int c)
16 {
17     a = a < b ? a : b;
18     return a < c ? a : c;
19 }
View Code

 279. Perfect Squares

題意:求數n是最少多少個平方數的和。

p[i]表示由p[i]個平方數構成數i,其中p[i]是最小的值,則p[i]=min(p[k]+p[i-k]),k>=1且k<=i.

咱們能夠再優化下k的取值,將k取值爲平方數,則p[i]=min(1+p[i-k]),k=j*j且k<=i

 1 public int numSquares(int n) 
 2 {
 3     int[] p = new int[n+1];
 4     p[0] = 0;
 5     for (int i = 1; i <= n; i++)
 6     {
 7         p[i] = n;
 8         
 9         for (int j = 1; j*j <= i; j++)
10             p[i] = Math.min(p[i], p[i-j*j]+1);
11     }
12     return p[n];
13 }
View Code

 300. Longest Increasing Subsequence

題意:找到數組nums最長遞增子序列,求出長度。

p[i]爲包含索引 i 的最長遞增子序列的長度,則更新p[i]=max(p[j]+1, p[i]),nums[i]>nums[j],j from 0 to i-1

 1 public int lengthOfLIS(int[] nums)
 2     {
 3         if (nums == null || nums.length == 0)
 4             return 0;
 5         int max = 0;
 6         int[] p = new int[nums.length];
 7         for (int i = 0; i < nums.length; i++)
 8         {
 9             p[i] = 1;
10             for (int j = 0; j < i; j++)
11             {
12                 if (nums[i] > nums[j] && p[j] + 1 > p[i])
13                 {
14                     p[i] = p[j] + 1;
15                 }
16             }
17             max = Math.max(max, p[i]);
18         }
19         return max;
20     }
View Code

309. Best Time to Buy and Sell Stock with Cooldown

題意:

給定一個數組,第i個元素表明某隻股票在第i天的價格。

設計一個算法計算最大收益。你能夠完成屢次交易(亦即,屢次買入、賣出同一只股票),須要知足下列限制:

  • 你不能夠在同一時間參與多個交易(亦即,在買入股票以前必須賣出)。
  • 在賣出股票以後,你不能夠在次日立刻買入。

這道題比較麻煩的是有個cooldown的限制,其實本質也就是買與賣之間的限制。對於某一天,股票有三種狀態: buy, sell, cooldown, sell與cooldown咱們能夠合併成一種狀態,由於手裏最終都沒股票,最終須要的結果是sell,即手裏股票賣了得到最大利潤。因此咱們能夠用兩個DP數組分別記錄當前持股跟未持股的狀態。

對於當天最終未持股的狀態,最終最大利潤有兩種可能,一是今天沒動做跟昨天未持股狀態同樣,二是昨天持股了,今天賣了。因此咱們只要取這二者之間最大值便可,表達式:sells[i] = max(sells[i-1], buys[i-1]+price[i])

對於當天最終持股的狀態,最終最大利潤有兩種可能,一是今天沒動做跟昨天持股狀態同樣,二是前天還沒持股,今天買了股票,這裏是由於cooldown的緣由,因此今天買股要追溯到前天的狀態。咱們只要取這二者之間最大值便可,表達式:buys[i] = max(buys[i-1], sells[i-2]-price[i])

第二種思路:

/*
     * 每一天有3種狀態,buy,sell,rest
   * buy[i]表示第i天買入後的最大收益,sell[i]表示第i天賣出後的最大收益,rest[i]表示第i天不作操做的最大收益 * buy[i] = max(buy[i-1]-price[i], sell[i-1]-price[i], rest[i-1]-price[i], buy[i-1]) * sell[i] = max(buy[i-1]+price[i], sell[i-1]+price[i], rest[i-1]+price[i], sell[i-1]) * rest[i] = max(buy[i-1], sell[i-1], rest[i-1]) * 而後去除上述不合理的狀況 * buy[i]<=sell[i],rest[i]<=sell[i],因此rest[i] = sell[i-1] * buy[i] = max(buy[i-1]-price[i], sell[i-1]-price[i], sell[i-2]-price[i], buy[i-1]) * sell[i] = max(buy[i-1]+price[i], sell[i-1]+price[i], sell[i-2]+price[i], sell[i-1]) * 由於 buy[i-1]-price[i] <= buy[i-1], 昨天賣了,今天不可能再買 * 因此 buy[i] = max(sell[i-2]-price[i], buy[i-1]) * 由於 昨天和前天賣了,今天不可能再賣 * 因此 sell[i] = max(buy[i-1]+price[i], sell[i-1]) * * 因此有遞推式 * buy[i] = max(sell[i-2]-price[i], buy[i-1]) * sell[i] = max(buy[i-1]+price[i], sell[i-1]) *
*/
 1 /*
 2  * buy[i] = max(sell[i-2]-price, buy[i-1])
 3  * sell[i] = max(buy[i-1]+price, sell[i-1])
 4  * */
 5 public int maxProfit(int[] prices)
 6 {
 7     int sell = 0, prev_sell = 0, buy = Integer.MIN_VALUE, prev_buy;
 8     for (int i = 0; i < prices.length; ++i)
 9     {
10         prev_buy = buy;
11         buy = Math.max(prev_sell-prices[i], prev_buy);
12         prev_sell =sell;
13         sell = Math.max(prev_buy+prices[i], prev_sell);
14     }
15     return sell;
16 }
View Code

338. Counting Bits

題意:計算二進制表示中有多少個1.

先寫寫看,找找規律。0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4,發現 0,1,1,2,1,2,2,3, | 1,2,2,3,2,3,3,4  後半部分正好是前半部分加1。相似,0,1,1,2,|    1,2,2,3 也是這種規律。因此,二進制每多一位就把結果數組中全部的結果都加一,再放回結果數組中。

 1 public int[] countBits(int num) 
 2     {
 3         if (num == 0)   return new int[]{0};
 4         int[] res = new int[num+1];
 5         int count = 0;
 6         while (true)
 7         {
 8             int len = count+1;
 9             for (int j = 0; j < len; j++)
10             {
11                 res[++count] = res[j]+1;
12                 if (count >= num)   return res;
13             }
14         }
15     }
View Code
相關文章
相關標籤/搜索