動態規劃問題(Java)

0. 動態規劃分析

0.1 動態規劃、遞歸和貪心算法的區別

動態規劃就是利用分治思想和解決冗余的辦法來處理問題,所以必然會有dp數組來實現記憶搜索,從而解決冗余,而分治思想就是遞歸的思想,總的問題可以分為若干相同的子問題,所有子問題的解合并即是該問題的解。

遞歸解決問題是一個自頂向下的思路,一直從最大的問題(頂)遞歸至小問題(下,底),只有小問題解決了,一層一層地返回,便可以得到最終的結果。

動態規劃解決問題是一個自底而上的思路,從小問題(下)開始,把小問題的計算結果保存至dp數組中,計算更大的問題時會用到小問題的結果,直接調用而不必重新計算,直到最大問題。

貪心算法就是動態規劃問題中的局部最優問題解,不要遍歷當前子問題中的所有情況,一般只取當前最優的情況。

0.2 dp公式

dp[i][j]公式是為了求出當前的狀態,需要再次搜索得到次解的索引,所以有索引變化,就有解的搜索。

所以,動態規劃問題就是需要分析出,當前狀態與前一個狀態的對應,即當前問題的子問題的組合

0.3 動態規劃問題的復雜度

由于動態規劃一般用兩個for循環實現,所以
時間復雜度是O(n^2)或O(mn)
空間復雜度是O(n^2)或O(n)
所以,一般都不是最好的算法,只是用來處理可以避免遺漏問題解

想要提高算法效率,繼續抓住問題的特點可以找到更多隱藏的信息,提供解題的切入點,得到的效果就會不一樣。

0.4 動態規劃問題與深度優先或廣度優先問題

動態規劃問題和BFS與DFS問題在分析子問題時,有時候會遇到相同的子問題是類似的情況,總問題是多個子問題的綜合,這是共同點

  1. 動態規劃是全面處理最優問題,時間和空間復雜度比較大,但是可以優化,這是一個覆蓋全部子問題的解決方法,重點是全面最優
  2. 深度/廣度優先遍歷是如何更加高效地處理這個問題,講到的是時間效率,降低時間復雜度,空間復雜度一般都是指數遞增的,重點在優先

動態規劃是有最優問題的,中間的子問題也有最優解,但是BFS與DFS是求解一個客觀存在的唯一問題。比如,求二叉樹的最長深度,這個是一個DFS問題,雖然有“最”字,但是這個客觀存儲的,不是最優問題。比如,求解二叉樹的最淺分支,這是一個BFS問題,也是客觀存在的,不是子問題有選擇的問題。

0.5 輔助數組的作用

一般情況下,我們都會利用與原問題矩陣行列大于1的新數組來存儲需要重復計算的數據,即原問題矩陣或數組分別為arr[n][m]和arr[n],而輔助數組為dp[n+1][m+1]和dp[n+1],目的是計算arr矩陣中的每一個位置的對應最優解,這就是動態規劃問題的最優子問題的數據存儲地方。

在此求解過程中,如果我們保持的數據只與前面幾個數據有關,例如跳一步和跳兩步這個問題,下一個問題只與之前的兩個數據有關,所以交替保存數據,只用O(1)空間復雜度便可以實現動態規劃問題。

1. (LeetCode) 單個幣種無限個數的硬幣找零

給你不同面值的硬幣數組coins和總金額amount。 編寫一個函數來計算您需要對amount金額的進行找零的最少數量的硬幣。 如果這些金額不能由硬幣的任何組合來找零,則返回'-1'。

例 1:
coins = [1, 2, 5], amount = 11
return 3 (11 = 5 + 5 + 1)

例 2:
coins = [2], amount = 3
return -1.

注意:假設每一個幣種的數量是無限的

題目分析

理論分析

理論分析是重復取所有coins,判斷當前數額n可以找零取錢最少
dp(n) = min{ dp(n - coins[i]) + 1} , i = 0, 1, 2,..., m.

編程實現分析

每一次取值與已經存在的最小F(n)比較,選擇最小的,因為我們只要知道最小的,中間的數據不必保存
dp(n) = min{ dp(n), dp(n - conins[i]) + 1}, i = 0, 1, 2,..., m.

問題剖析

由于本問題是一個單個幣種可以重復選擇的問題,所以這就意味著每取一個元素,后面可以取的情況與前一次是獨立的,沒有關系,獨立重復處理。而對于給定字符串的組合問題,則是取了元素之后,能取的元素就少了一個。字符串組合和給定全部一個的幣種的問題是一樣的。

當前問題與上一次遇到的情況是一樣的,如果把每一次取元素當作當前問題,下一次取值就是子問題,這種分析問題的方式比較容易理解

代碼實現

public int coinChange(int[] coins, int amount){
  if(coins.length < 1 || amount < 1) return -1;
  int[] dp = new int[amount + 1];
  for(int i = 1; i <= amount; ++i){
    dp[i] = amount;
    for(int j = 0; j < coins.length; ++j){
      if(coins[j] <= i){
        dp[i] = Math.min(dp[i], dp[i - coins[j]] + 1);
      }
    }
  }
  return dp[amount] > amount ? -1:dp[amount];
}

2.幣種無限個數的硬幣找零組合

給你不同面值的硬幣數組coins和總金額amount。 編寫一個函數來計算組成該amount的組合的數量。每種硬幣的個數是無限的。

注意:假設

  • 0 <= amount <= 5000
  • 1 <= coin <= 5000
  • the number of coins is less than 500
  • the answer is guaranteed to fit into signed 32-bit integer

例 1:
Input: amount = 5, coins = [1, 2, 5]
Output: 4
有四種組合方式:
5=5
5=2+2+1
5=2+1+1+1
5=1+1+1+1+1

例2:
Input: amount = 3, coins = [2]
Output: 0
Explanation: the amount of 3 cannot be made up just with coins of 2.

例 3:
Input: amount = 10, coins = [10]
Output: 1

代碼分析

求種類數,多少種方法,多少條路徑等這類問題
只要開頭走到結尾就算1種(1次),所以共有多少種方法,就是看分叉的次數,分叉次數就是總和。

代碼實現

public class Solution {  
    public int change(int amount, int[] coins) {  
        int[] dp=new int[amount+1];  
        dp[0]=1;  
        for(int i=0;i<coins.length;i++){  
            for(int j=0;j<amount+1;j++){  
                if(j-coins[i]>=0){  
                    dp[j]+=dp[j-coins[i]];  
                }  
            }  
        }  
        return dp[amount];  
    }  
}  

3.回文子串問題

3.1 Palindromic Substrings

題目描述

給定一個字符串,你的任務是計算這個字符串中有多少個回文子串。

具有不同起始索引或結束索引的子字符串即使由相同的字符組成,也會被計為不同的子字符串。
例1:

輸入: “abc”
輸出: 3

說明:三個回文串:“a”,“b”,“c”。
例2:

輸入: “aaa”
輸出: 6

說明:六個回文串:“a”,“a”,“a”,“aa”,“aa”,“aaa”。

代碼實現

public int countSubstrings(String s) {
    int n = s.length();
    int res = 0;
    boolean[][] dp = new boolean[n][n];
    for (int i = n - 1; i >= 0; i--) {
        for (int j = i; j < n; j++) {
            dp[i][j] = s.charAt(i) == s.charAt(j) && (j - i < 3 || dp[i + 1][j - 1]);
            if(dp[i][j]) ++res;
        }
    }
    return res;
}

復雜度更好的算法[1]

3.2 Longest Palindromic Substring

問題描述

給定一個字符串s,找到s中的最長回文子串。假設s的最大長度是1000。
例1:

輸入: “babad” 
輸出: “bab” 
注意: “aba”也是一個有效的答案。

例2:

輸入: “cbbd” 
輸出: “bb”

思路分析

dp[i][j] 的定義如下面的公式:



則遞歸公式:


編程實現過程
輸入:BCDFDECB


輸出:DFD

代碼實現

public String longestPalindrome(String s) {
  int n = s.length();
  String res = null;
    
  boolean[][] dp = new boolean[n][n];
  //依次從最后面進行迭代,前一輪迭代為可能的回文的第一個字符,然后依次進行比對是否與第一個字符相等,如果不等則直接為False,然后進行后續比對,如果找到相同的字符,則比對左斜下的子字符的回文信息,由于i+1,j-1,所以開始比對的是第i-1和第j-1字符是否相等,依次向里面靠攏,直到相遇。
  for (int i = n - 1; i >= 0; i--) {
    for (int j = i; j < n; j++) {  //dp[i+1][j-1]是一個左斜下的小回文
      dp[i][j] = s.charAt(i) == s.charAt(j) && (j - i < 3 || dp[i + 1][j - 1]);// j-i<3是在只有三個字符或四個字符為回文時的快速判斷,不需要獲取左斜下對角的值
            
      if (dp[i][j] && (res == null || j - i + 1 > res.length())) { //找出比之前更長的回文,則更新字符串
        res = s.substring(i, j + 1);
      }
    }
  }
    
  return res;
}

復雜度

Time complexity : O(n^2)
Space complexity : O(n^2)

復雜度更好的算法[2]

4.背包問題

4.1 背包問題的特點

給定了一個固定的容量結果數,計算最優解并且限制條件是這個固定容量

Max f(x)
s.t.  w <= W

4.2 問題描述

有N件物品,每件物品的體積為W1,W2……Wn(Wi為整數),與之相對應的價值為P1,P2……Pn(Pi為整數),現在從中取出若干件物品放入容量為W的背包里。求背包能夠裝下的最大價值。

4.3 題目分析

實現固定體積裝下最大價值的方法:
1.從物品索引開始,依次選擇取本物品或不取物品。
2.對待第一個是這樣,對待第二物品也是選擇或不選擇
3.選擇或不選擇,還有一個判斷條件,就是選擇的本物品的體積不能大于當前背包的剩余的空間

所以,本質上也是一個組合問題!從n個物品中選擇m(m的取值從0至n)個,使m個物品的價值之和最大,這個最大就是最優問題。

4.4 上述分析的編程結果分析

依次分析結果,W1,W1W2,W2,W1W2W3,W2W3,...,Wn。
但是在中間由于增加了一個最優解,所以利用O(1)的空間復雜度就可以保存之前遍歷的組合的最大價值。

4.5 代碼實現

import java.util.Scanner;
public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        int n = in.nextInt();
        int v = in.nextInt();
        int[] dp = new int[v + 1];
        int[] price = new int[n + 1];
        int[] weight = new int[n + 1];
        long max = 0;
        for (int i = 1; i < n + 1; i++) {
            weight[i] = in.nextInt();
            price[i] = in.nextInt();
        }
        for (int i = 1; i < n + 1; i++)
            for (int j = v; j > 0; j--)
                if (j - weight[i] >= 0)
                    dp[j] = Math.max(dp[j], dp[j - weight[i]] + price[i]);
                else
                    dp[j] = dp[j];
        for (int i = 0; i < v + 1; i++)
            max = max > dp[i] ? max : dp[i];
        System.out.println(max);
    }
}

5. Integer Break

本問題與背包問題也是一樣的,分裂固定的總數,使相乘結果最優。

題目描述

給定一個正整數n,將其分解為至少兩個正整數的和,并使這些整數的乘積最大化。返回您可以獲得的最大產品。
例如,給定n = 2,返回1(2 = 1 + 1); 給定n = 10,返回36(10 = 3 + 3 + 4)。
注意:你可以假設n不小于2且不大于58。

題目分析

數學的分析[1]:大于4的自然數,每次乘以3便可以,小于4的數枚舉便可。
例如:
dp[8] = dp[3] * dp[3] * dp[2]
dp[11] = dp[3] * dp[8]
dp[4] = dp[2] * dp[2]

所以取值問題是盡量讓被分的數離3接近,如果dp[2] * dp[2] > dp[3] * dp[1]

代碼實現

public int integerBreak(int n) {
       int[] dp = new int[n + 1];
       dp[1] = 1;
       for(int i = 2; i <= n; i ++) {
           for(int j = 1; j < i; j ++) {
               dp[i] = Math.max(dp[i], (Math.max(j,dp[j])) * (Math.max(i - j, dp[i - j])));
           }
       }
       return dp[n];
    }

5.Perfect Squares

問題描述

給定一個正整數n,找到與1, 4, 9, 16, ...相加的和為n的最小完美平方數。

例如,給定n = 12,返回3,因為12 = 4 + 4 + 4; 給n = 13,返回2,因為13 = 4 + 9

問題分析

這實際上是一個背包問題的改進,每次減去的是一個數的平方(j^2),然后計算最小的減法操作次數

背包問題與零錢找零問題也是類似的,所以統稱為“背包問題

代碼實現

public int numSquares(int n) {
    int[] dp = new int[n + 1];
    Arrays.fill(dp, Integer.MAX_VALUE);
    dp[0] = 0;
    for(int i = 1; i <= n; ++i) {
        int min = Integer.MAX_VALUE;
        int j = 1;
        while(i - j*j >= 0) {
            min = Math.min(min, dp[i - j*j] + 1);
            ++j;
        }
        dp[i] = min;
    }       
    return dp[n];
}

6. 字符串的距離和編輯問題

問題描述

對于序列S和T,它們之間距離定義為:
對二者其一進行幾次以下的操作
(1)刪去一個字符;
(2)插入一個字符;
(3)改變一個字符。
每進行上面任意一次操作,計數增加1。
將S和T變為同一個字符串的最小計數即為它們的距離(最優問題)

問題的遞歸思路分析

說明:dp[i][j] 表示截取字符S和T在第i和第j個字符之前的字符進行比對,這個相對于拿整個字符來處理,是子問題。
1.從兩個字符串尾字符開始建立索引并向前走,如果兩個字符相等則dp[i][j] = dp[i-1][j-1]
2.如果兩個字符不等,則從三種選擇中選擇一種操作進行本次更改(以下b和c中有多個重復的情況):
(a)修改S或T的這個字符,讓其等于另外一個字符,相等后就表示兩個字符相等了,也就是第一種情況,但操作了一回,所以為dp[i][j] = dp[i-1][j-1] + 1
(b)刪除S或T的這個字符,然后進行下一次比較,此時這兩個字符還是不等于,也就回到了下一次比較的情況,同時比較刪除S中這個不等的字符得到的結果與刪除T中的這個字符得到的結果,取小的,如dp[i][j] = min{ dp[i-1][j] + 1, dp[i][j-1] + 1 }
(c)在S或T中插入一個字符讓這兩個字符相等,同時比較插入到S后的結果與插入到T中的結果,取小的,如dp[i][j] = min{ dp[i][j-1] + 1, dp[i-1][j] + 1 }

3.第二步的三種不相等情況綜合結果,再取最小值就是本次處理不相等的情況,dp[i][j] = min{ dp[i-1][j-1] + 1, dp[i-1][j] + 1, dp[i][j-1] + 1 }

代碼實現

利用for循環把遞歸思路轉化為非遞歸思路,重點理解for循環迭代實現了遞歸中的思路,注意for循環中的全部步驟為什么可以覆蓋這個問題的全部情況

    public static int similarityString(String s, String t) {  
        if(sLen == 0)  return tLen;  
        if(tLen == 0) return sLen;  
        int sLen = s.length();  
        int tLen = t.length();  
        int i,j;    
        char ch1,ch2;   
        int cost;  
        int[][] dp = new int[sLen + 1][tLen + 1];  

        //下面兩個是邊界條件
        for(i = 0; i <= sLen; i++)  dp[i][0] = i; //這里是有意義的,就是當一個字符串長度為0,這就意味著另外一個字符串必須全部刪除
        for(i = 0; i <= tLen; i++)  dp[0][i] = i ; 

        for(i = 1; i < = sLen; i++) {  //第一個for循環表示第一個字符串取其前i個字符
            ch1 = s.charAt(i - 1);  
            for(j = 1; j <= tLen; j++) {  // 第二個for循環表示第二個字符串取其前j個字符
                ch2 = t.charAt(j - 1);  
                if(ch1 == ch2) cost = 0;  
                else  cost = 1;
                dp[i][j] = Math.min(Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1),dp[i - 1][j - 1] + cost);  
            }  
        }  
        return dp[sLen][tLen];  
    }  

7. 最長公共子序列(Longest Common Subsequence,LCS)

問題描述

對于序列S和T,求它們的最長公共子序列的長度。例如X={A,B,C,B,D,A,B},Y={B,D,C,A,B,A}則它們的lcs是{B,C,B,A}和{B,D,A,B},所以結果為4.

題目分析

dp[i][j] 表示取字符串S的第i個字符之前的序列和T的第j個字符之前的序列進行比對,求其最長子序列的長度。
1.從尾部字符開始取起,如果S和T字符串的字符相等,則dp[i][j] = dp[i-1][j-1] + 1.
2.如果S和T字符串的字符不相等,則比較以下兩種情況,取其中的最大值
(a)留下S的字符,去掉T的字符,利用S當前字符與T的第j-1字符進行比較,dp[i][j] = dp[i][j-1]
(b)去掉S的字符,留下T的字符,利用S的前一個字符i-1字符與T的當前j字符進行比較,dp[i][j] = dp[i-1][j]
3.每進行一次取元素的時候,遇到的問題又與上一次遇到的情況是一樣的,所以遞歸就可以實現。

代碼實現

  public static int compute(char[] str1, char[] str2){
        int substringLength1 = str1.length;
        int substringLength2 = str2.length;
 
        // 構造二維數組記錄子問題A[i]和B[j]的LCS的長度
        int[][] dp = new int[substringLength1 + 1][substringLength2 + 1];
 
        // 從后向前,動態規劃計算所有子問題。也可從前到后。
        for (int i = substringLength1 - 1; i >= 0; i--){
            for (int j = substringLength2 - 1; j >= 0; j--){
                if (str1[i] == str2[j])
                    dp[i][j] = dp[i + 1][j + 1] + 1;// 狀態轉移方程
                else
                    //索引加的不同,表示參考基準不同
                    dp[i][j] = Math.max(dp[i + 1][j], dp[i][j + 1]);// 狀態轉移方程
            }
        }
        System.out.println("substring1:" + new String(str1));
        System.out.println("substring2:" + new String(str2));
        System.out.print("LCS:");
 
        int i = 0, j = 0;
        while (i < substringLength1 && j < substringLength2){
            if (str1[i] == str2[j]){
                System.out.print(str1[i]);  //逐個輸出最長公共子串
                i++;  j++;
            }
            else if (dp[i + 1][j] >= dp[i][j + 1])  i++;
            else  j++;
        }
        System.out.println();
        return dp[0][0];  //最長公共子串的長度
    }

8. Maximal Square

題目描述

給定一個只包含0和1的矩陣,找到只包含1的最大方陣并返回其面積。

例如,給出以下矩陣:

1 0 1 0 0
1 0 1 1 1
1 1 1 1 1
1 0 0 1 0
返回4。

題目分析

初始化另一個矩陣(dp),其尺寸與初始化為0的所有矩陣相同。

dp(i,j)表示右下角是原始矩陣中索引為(i,j)的單元格的最大方格的邊長。

從索引(0,0)開始,對于在原始矩陣中找到的每個1元素,我們將當前元素的值更新為


代碼實現

public class Solution {
    public int maximalSquare(char[][] matrix) {
        int rows = matrix.length, cols = rows > 0 ? matrix[0].length : 0;
        int[][] dp = new int[rows + 1][cols + 1];
        int maxsqlen = 0;
        for (int i = 1; i <= rows; i++) {
            for (int j = 1; j <= cols; j++) {
                if (matrix[i-1][j-1] == '1'){
                    dp[i][j] = Math.min(Math.min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1;
                    maxsqlen = Math.max(maxsqlen, dp[i][j]);
                }
            }
        }
        return maxsqlen * maxsqlen;
    }
}

9.House Robber

問題描述

假如你是一名專業的強盜,計劃搶劫沿街的房屋。每間房屋都藏有一定數量的金錢,但是同一晚上有兩間相鄰的房屋被闖入,它將自動觸發警報。

輸入一個代表每個房屋的金額的非負整數列表,在沒有觸發警報的情況下,輸出你搶劫的最高金額。

代碼實現

class Solution {
    public int rob(int[] nums){
    if(nums.length == 0) return 0;
    int n = nums.length;
    int[] dp = new int[n];
    dp[0] = nums[0];
    dp[1] = Math.max(dp[0], nums[1]);
    if(nums.length < 2) return dp[nums.length -1];
    for(int i = 2; i < n; i++){
      dp[i] = Math.max(dp[i-2] + nums[i], dp[i-1]);
    }
    return dp[n];
  }
}

10.Maximum Subarray

問題描述

在數組中找到連續的子數組(至少包含一個數字),使這個子數組的總和最大。

例如,給定數組[-2,1,-3,4,-1,2,1,-5,4]
連續子數組[4,-1,2,1]的最大sum = 6.

代碼實現

public int maxSubArray(int[] A) {
        int n = A.length;
        int[] dp = new int[n];//dp[i] means the maximum subarray ending with A[i];
        dp[0] = A[0];
        int max = dp[0];
        
        for(int i = 1; i < n; i++){
            dp[i] = A[i] + (dp[i - 1] > 0 ? dp[i - 1] : 0);
            max = Math.max(max, dp[i]);
        }
        return max;
}

11.Range Sum Query - Immutable

問題描述

給定一個整數數組NUMS,找到索引(i,j)之間的元素的總和,包括端值。
例:

給定nums = [-2,0,3,-5,2,-1]

sumRange(0,2) - > 1
sumRange(2,5) - > -1
sumRange(0,5) - > -3

注意:

  1. 這個數組不能改變。
  2. 可能會有很多次sumRange函數調用。

代碼實現

public class NumArray {
    private int[] sums;  /dp數組

    public NumArray(int[] nums) {
        if(nums.length != 0){
            sums = new int[nums.length];
        
            sums[0] = nums[0];
            for(int i=1; i<nums.length; i++){
                sums[i] = nums[i] + sums[i-1];
            }
        }
    }

    public int sumRange(int i, int j) {
        return i==0 ? sums[j] : sums[j]-sums[i-1];
    }
}

12.Unique Substrings in Wraparound String

題目描述

將字符串s作為“abcdefghijklmnopqrstuvwxyz”的無限循環·字符串,因此s將如下所示:“... zabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcd ....”

現有另一個字符串p,請找出有多少個唯一的非空子串p存在于字符串s中。

注意: p只包含小寫英文字母,p的大小可能超過10000。

例1:
輸入: “a”
輸出: 1

說明:字符串“s”中只有字符串“a”的子串“s”。

例2:
輸入: “cac”
輸出: 2

說明:字符串s中有兩個字符串“cac”的子字符串“a”,“c”。

例3:
輸入: “zab”
輸出: 6

說明:字符串s中的字符串“zab”有六個子字符串“z”,“a”,“b”,“za”,“ab”,“zab”。

本例的特別說明

本例其實是順序組合問題,除了位置不變,還有不能任意取不相鄰的元素組合在一起,這種組合就是簡單的相加就可以實現總數了。

例如, 字符abcd的本例組合,a,b,c,d,ab,bc,cd,abc,bcd,abcd共10個,實際上sum = 1+2+3+4,有幾個就是幾個的加法運算。

代碼實現

public class Solution {
    public int findSubstringInWraproundString(String p) {
        // count[i] is the maximum unique substring end with ith letter.
        // 0 - 'a', 1 - 'b', ..., 25 - 'z'.
        int[] count = new int[26]; //容量只有26的散列表,用于dp輔助數組,實現最優子問題
        
        // store longest contiguous substring ends at current position.
        int maxLengthCur = 0; 

        for (int i = 0; i < p.length(); i++) {
            if (i > 0 && (p.charAt(i) - p.charAt(i - 1) == 1 || (p.charAt(i - 1) - p.charAt(i) == 25)))
                maxLengthCur++;
            else 
                maxLengthCur = 1;
            
            int index = p.charAt(i) - 'a';   //判斷當前字符是那個字母
            count[index] = Math.max(count[index], maxLengthCur);  //更新字符中的數據,如果連續字符越長,數字越大,例如abcdfe, abcdef,其中前面的e和f為1,而后面字符串的e和f為5和6
        }
        
        // Sum to get result
        int sum = 0;
        for (int i = 0; i < 26; i++) {
            sum += count[i];
        }
        return sum;
    }
}

13.Maximum Product Subarray

問題描述

在數組中找到連續的子數組(至少包含一個數字),使該數組中包含最大的產品(相乘為最大)。
例如,給定數組[2,3,-2,4],則連續的子數組[2,3]具有最大的product = 2*3= 6
給定數組[2,3,-2,-1],則連續的子數組為[2,3,-2,-1]product = 12.

代碼實現

public class Solution {
  public int maxProduct(int[] A) {
    if (A == null || A.length == 0) {
        return 0;
    }
    int[] f = new int[A.length];
    int[] g = new int[A.length]; //用來存儲最小值,因為有可能有多個復數
    f[0] = A[0];
    g[0] = A[0];
    int res = A[0];
    for (int i = 1; i < A.length; i++) {
        f[i] = Math.max(Math.max(f[i - 1] * A[i], g[i - 1] * A[i]), A[i]);
        g[i] = Math.min(Math.min(f[i - 1] * A[i], g[i - 1] * A[i]), A[i]);
        res = Math.max(res, f[i]);
    }
    return res;
  }
}

空間復雜度更好的代碼

int maxProduct(int A[], int n) {
    // store the result that is the max we have found so far
    int r = A[0];

    // imax/imin stores the max/min product of
    // subarray that ends with the current number A[i]
    for (int i = 1, imax = r, imin = r; i < n; i++) {
        // multiplied by a negative makes big number smaller, small number bigger
        // so we redefine the extremums by swapping them
        if (A[i] < 0)
            swap(imax, imin);

        // max/min product for the current number is either the current number itself
        // or the max/min by the previous number times the current one
        imax = max(A[i], imax * A[i]);
        imin = min(A[i], imin * A[i]);

        // the newly computed max value is a candidate for our global result
        r = max(r, imax);
    }
    return r;
}

14.Unique Paths

題目描述

機器人位于一個m x n網格的左上角(在下圖中標記為“start”)。

機器人只能隨時向下或向右移動。機器人正在嘗試到達網格的右下角(在下圖中標記為“finish”)。

有多少可能的獨特路徑?


代碼實現(O(n*m)空間復雜度)

標準的方式,計算每一個位置的信息,這種方式可以完整的記憶內容,但是如果不需要就浪費空間了

class Solution {
    int uniquePaths(int m, int n) {
        vector<vector<int> > path(m, vector<int> (n, 1));
        for (int i = 1; i < m; i++)
            for (int j = 1; j < n; j++)
                path[i][j] = path[i - 1][j] + path[i][j - 1];
        return path[m - 1][n - 1];
    }
};

代碼實現(O[min(n,m)]空間復雜度)

因為我們只要知道當前列或前一列的信息就夠了

class Solution {
    int uniquePaths(int m, int n) {
        if (m > n) return uniquePaths(n, m); 
        vector<int> pre(m, 1);
        vector<int> cur(m, 1);
        for (int j = 1; j < n; j++) {
            for (int i = 1; i < m; i++)
                cur[i] = cur[i - 1] + pre[i];
            swap(pre, cur);
        }
        return pre[m - 1];
    }
};

15.Create Maximum Number

題目描述

給定兩個長度數組m和n數字0-9代表兩個數字。k <= m + n從兩個數字中創建最大長度數。必須保留來自同一陣列的數字的相對順序。返回k數字的數組。您應該嘗試優化算法的時間和空間復雜性。

例1:
nums1 = [3, 4, 6, 5]
nums2 = [9, 1, 2, 5, 8, 3]
k = 5
返回[9, 8, 6, 5, 3]

例2:
nums1 = [6, 7]
nums2 = [6, 0, 4]
k = 5
返回[6, 7, 6, 0, 4]

例3:
nums1 = [3, 9]
nums2 = [8, 9]
k = 3
返回[9, 8, 9]

題目分析

本題的情況是從兩個數組中找到子數組使結果順序最大值,其實本題有點類似于歸并排序問題,但是由于保持原來的順序,并且從中只選取k個數據,所以特點又有點不一樣。

代碼實現

public int[] maxNumber(int[] nums1, int[] nums2, int k) {
    int n = nums1.length;
    int m = nums2.length;
    int[] ans = new int[k];
    //在兩個數組中選擇i個數據和k-i個數據,然后找出對應數組中的i個或k-i個最大順序數據
    for (int i = Math.max(0, k - m); i <= k && i <= n; ++i) {
        int[] candidate = merge(maxArray(nums1, i), maxArray(nums2, k - i), k);
        if (greater(candidate, 0, ans, 0)) ans = candidate;
    }
    return ans;
}
//合并從兩個數組中選取數據,大的放入ans數組中,這也是因為歸并排序為穩定排序的原因可以實現此算法
private int[] merge(int[] nums1, int[] nums2, int k) {
    int[] ans = new int[k];
    for (int i = 0, j = 0, r = 0; r < k; ++r)
        ans[r] = greater(nums1, i, nums2, j) ? nums1[i++] : nums2[j++];
    return ans;
}
//比較兩個數組在相同索引位置上的值,那個大,[4,3,2,1]>[4,3,1,1]
public boolean greater(int[] nums1, int i, int[] nums2, int j) {
    while (i < nums1.length && j < nums2.length && nums1[i] == nums2[j]) {
        i++;
        j++;
    }
    return j == nums2.length || (i < nums1.length && nums1[i] > nums2[j]);
}
//選擇數組中的k個最大數據,注意這里的k個要與數組的個數比對,如果k=數組長度,則全部取值,否則在當前索引后面的個數還大于k個的時候,我們先取最大值。
//比如,[3,4,6,1]取兩個,則先取3,如果3當前的索引后面還有多余2個的數據,當前索引為0,后面還有3個,所以比較3和4,大的放入ans數組中,后面是4小于6,取6,此時ans=[6,],后面只有一個數據了,所以不管多大,取值后ans=[6,1]
public int[] maxArray(int[] nums, int k) {
    int n = nums.length;
    int[] ans = new int[k];
    for (int i = 0, j = 0; i < n; ++i) {
        while (n - i + j > k && j > 0 && ans[j - 1] < nums[i]) j--;
        if (j < k) ans[j++] = nums[i];
    }
    return ans;
}

復雜度

在最壞的情況下,算法的時間復雜度為O((m + n)^ 3)

16.Longest Valid Parentheses

題目描述

給定一個只包含字符'('和')'的字符串,找出最長且有效的括號子字符串的長度。

"(()"最長的有效括號子字符串"()"長度= 2。

而)()())"最長的有效括號子字符串"()()",其長度= 4。

代碼實現

class Solution {
    public int longestValidParentheses(String s) {
        if(s.length() <= 0) return 0;
        Stack<Integer> stack = new Stack<Integer>(); //保存'('的索引位置
        int max = 0; //記錄最大長度
        int leftIndex = -1; //記錄stack加入'('的初始位置
        for(int i = 0; i < s.length(); ++i){
            if(s.charAt(i) == '(') stack.push(i); 
            else{
                if(stack.isEmpty()) leftIndex = i;//如果第一個字符為')'時,會出現前面為空的情況,例如")()'
                else{
                    stack.pop(); //只要不為空,則必然之前壓入了'(',先依次計算最近距離,然后繼續彈出,看是否還有'(',則繼續拿當前字符索引減去stack中的值
                    if(stack.isEmpty()) max = Math.max(max, i - leftIndex);//如果為空了,則只能減去保存在leftIndex中的值,因為那個是起點
                    else max = Math.max(max, i - stack.peek()); //如果不為空則,表示之前還有'('
                }
            }
        }
        return max;
    }
}

參考文獻
[1] leetcode 518. Coin Change 2

[2] 跳臺階

[4] 變態跳臺階

[5] java 動態規劃策略原理及例題(很詳細)

[6] 常見的動態規劃問題分析與求解

[7] 貪心、遞歸、遞推以及動態規劃算法的分析與對比

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,882評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,208評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,746評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,666評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,477評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,960評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,047評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,200評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,726評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,617評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,807評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,327評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,049評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,425評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,674評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,432評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,769評論 2 372

推薦閱讀更多精彩內容

  • rljs by sennchi Timeline of History Part One The Cognitiv...
    sennchi閱讀 7,388評論 0 10
  • 面試時被問道一個問題: 一個10階樓梯 每次只能走1步或者2步,走到頭有幾種走法 當時第一反應想到的是采用暴力枚舉...
    smallThree1閱讀 495評論 0 1
  • 在網上查資料的時候無意間看到了這道谷歌面試題,據說這道面試題刷了好多的大牛(可怕)。讀了幾篇文章,讀懂以后感覺這種...
    進擊的諾基亞閱讀 5,121評論 1 4
  • 動態規劃(英語:Dynamic programming,簡稱DP)是一種通過把原問題分解為相對簡單的子問題的方式求...
    szu_bee閱讀 565評論 1 0
  • 暫時不用這個了 后會有期
    宜相安閱讀 179評論 0 0