1. 程式人生 > >【LeetCode題解】動態規劃:從新手到專家(一)

【LeetCode題解】動態規劃:從新手到專家(一)

【LeetCode題解】動態規劃:從新手到專家(一)

文章標題借用了Hawstein的譯文《動態規劃:從新手到專家》。

1. 概述

動態規劃( Dynamic Programming, DP)是最優化問題的一種解決方法,本質上狀態空間的狀態轉移。所謂狀態轉移是指每個階段的最優狀態(對應於子問題的解)可以從之前的某一個或幾個階段的狀態中得到,這個性質叫做最優子結構。而不管之前這個狀態是如何得到的,這被稱之為無後效性

DP問題中最經典的莫過於01揹包問題:

有N件物品和一個容量為V的揹包。第i件物品的費用是c[i],價值是w[i]。求解將哪些物品裝入揹包可使價值總和最大。

用子問題定義狀態:即f[i][v]表示前i件物品恰放入一個容量為v的揹包可以獲得的最大價值;則其狀態轉移方程:

f[i][v]=max{f[i-1][v],f[i-1][v-c[i]]+w[i]}

“將前i件物品放入容量為v的揹包中”這個子問題,若只考慮第i件物品的策略(放或不放),那麼就可以轉化為一個只牽扯前i-1件物品的問題。如果不放第i件物品,那麼問題就轉化為“前i-1件物品放入容量為v的揹包中”,價值為f[i-1][v];如果放第i件物品,那麼問題就轉化為“前i-1件物品放入剩下的容量為v-c[i]的揹包中”,此時能獲得的最大價值就是f[i-1][v-c[i]]再加上通過放入第i件物品獲得的價值w[i]。

2. 題解

LeetCode題目 歸類
53. Maximum Subarray 子陣列最大和
121. Best Time to Buy and Sell Stock 子陣列最大和
122. Best Time to Buy and Sell Stock II 子序列最大和
123. Best Time to Buy and Sell Stock III  
188. Best Time to Buy and Sell Stock IV  
55. Jump Game  
70. Climbing Stairs  
62. Unique Paths  
63. Unique Paths II  
64. Minimum Path Sum 最短路徑
91. Decode Ways  

以下程式碼既有Java,也有Go。

53. Maximum Subarray

子陣列最大和問題,求解方法可用Kadane演算法

121. Best Time to Buy and Sell Stock

題目大意:給定陣列a[..]a[..],求解maxa[j]−a[i]j>imaxa[j]−a[i]j>i。
解決思路:將陣列a的相鄰值相減(右邊減左邊)變換成陣列b,上述問題轉變成了求陣列b的子陣列最大和問題.

// Kadane algorithm to solve Maximum subArray problem
public int maxProfit(int[] prices) {
  int maxEndingHere = 0, maxSoFar = 0;
  for (int i = 1; i < prices.length; i++) {
    maxEndingHere += prices[i] - prices[i - 1];
    maxEndingHere = Math.max(maxEndingHere, 0);
    maxSoFar = Math.max(maxEndingHere, maxSoFar);
  }
  return maxSoFar;
}

122. Best Time to Buy and Sell Stock II

之前問題Best Time to Buy and Sell Stock的升級版,對交易次數沒有限制,相當於求解相鄰相減後形成的子序列最大和——只要為正數,則應計算在子序列內。

public int maxProfit(int[] prices) {
  int max = 0;
  for (int i = 1; i < prices.length; i++) {
    if (prices[i] > prices[i - 1]) {
      max += (prices[i] - prices[i - 1]);
    }
  }
  return max;
}

123. Best Time to Buy and Sell Stock III

最多允許交易兩次。

public int maxProfit(int[] prices) {
    int sell1 = 0, sell2 = 0;
    int buy1 = Integer.MIN_VALUE, buy2 = Integer.MIN_VALUE;
    for (int price : prices) {
        buy1 = Math.max(buy1, -price); // borrow
        sell1 = Math.max(sell1, buy1 + price);
        buy2 = Math.max(buy2, sell1 - price);
        sell2 = Math.max(sell2, buy2 + price);
    }
    return sell2;
}

188. Best Time to Buy and Sell Stock IV

最多允許交易k次。當k >= n/2時,在任意時刻都可以進行交易(一次交易包括買、賣),因此該問題退化為了問題122. Best Time to Buy and Sell Stock II。其他情況則有遞推式:

 

ci,j=max(ci,j−1, max(ci−1,t−pt)+pj),0≤t<jci,j=max(ci,j−1, max(ci−1,t−pt)+pj),0≤t<j

其中,ci,jci,j表示在tt時刻共ii次交易產生的最大收益。

public int maxProfit(int k, int[] prices) {
    int n = prices.length;
    if (n <= 1) {
        return 0;
    }
    // make transaction at any time
    else if (k >= n / 2) {
        return maxProfit122(prices);
    }
    int[][] c = new int[k + 1][n];
    for (int i = 1; i <= k; i++) {
        int localMax = -prices[0];
        for (int j = 1; j < n; j++) {
            c[i][j] = Math.max(c[i][j - 1], localMax + prices[j]);
            localMax = Math.max(localMax, c[i - 1][j] - prices[j]);
        }
    }
    return c[k][n - 1];
}

public int maxProfit122(int[] prices) {
    int max = 0;
    for (int i = 1; i < prices.length; i++) {
        if (prices[i] > prices[i - 1]) {
            max += (prices[i] - prices[i - 1]);
        }
    }
    return max;
}

55. Jump Game

限制當前最大跳躍數,問是否能到達最後一個index。需要反向往後推演。

public boolean canJump(int[] nums) {
    int n = nums.length, index = n - 1;
    for (int i = n - 2; i >= 0; i--) {
        if (i + nums[i] >= index)
            index = i;
    }
    return index <= 0;
}

70. Climbing Stairs

題目大意:每一次可以加1或加2,那麼從0加到n共有幾種加法?

假定didi表示加到i的種數,那麼就有遞推式di=di−1+di−2di=di−1+di−2。

func climbStairs(n int) int {
    if(n < 1) {
        return 0;
    }
    d := make([]int, n+1)
    d[1] = 1
    if n >= 2 {
        d[2] = 2
    }
    for i := 3; i<=n; i++ {
        d[i] = d[i-1] + d[i-2]
    }
    return d[n]
}

62. Unique Paths

題目大意:求解從左上角到右下角的路徑數。

路徑數遞推式:ci,j=ci−1,j+ci,j−1ci,j=ci−1,j+ci,j−1。

func uniquePaths(m int, n int) int {
    f := make([][]int, m)
    for i := range f {
        f[i] = make([]int, n)
    }
    // handle boundary condition: f[][0] and f[0][]
    f[0][0] = 1
    for i := 1; i < m; i++ {
        f[i][0] = 1
    }
    for j := 1; j < n; j++ {
        f[0][j] = 1
    }
    for i := 1; i < m; i++ {
        for j := 1; j < n; j++ {
            f[i][j] = f[i][j - 1] + f[i - 1][j]
        }
    }
    return f[m-1][n-1]
}

63. Unique Paths II

加了限制條件,有的點為obstacle——不允許通過。上面的遞推式依然成立,只不過要加判斷條件。另外,在實現過程中可以用一維陣列代替二維陣列,比如說按行或按列計算。

public int uniquePathsWithObstacles(int[][] obstacleGrid) {
    int columnSize = obstacleGrid[0].length;
    int[] c = new int[columnSize];
    c[0] = 1;
    for (int[] row : obstacleGrid) {
        for (int j = 0; j < columnSize; j++) {
            if (row[j] == 1)
                c[j] = 0;
            else if (j >= 1)
                c[j] += c[j - 1];
        }
    }
    return c[columnSize - 1];
}

64. Minimum Path Sum

題目大意:從矩陣的左上角到右下角的最短路徑。

加權路徑值ci,j=max(ci−1,j,ci,j−1)+wi,jci,j=max(ci−1,j,ci,j−1)+wi,j,其中,wi,jwi,j為圖中邊的權值。

// the shortest path for complete directed graph
func minPathSum(grid [][]int) int {
    var m, n = len(grid), len(grid[0])
    f := make([][]int, m)
    for i := range f {
        f[i] = make([]int, n)
    }
    // handle boundary condition: f[][0] and f[0][]
    f[0][0] = grid[0][0]
    for i := 1; i < m; i++ {
        f[i][0] = f[i - 1][0] + grid[i][0]
    }
    for j := 1; j < n; j++ {
        f[0][j] = f[0][j-1] + grid[0][j]
    }
    for i :=1; i < m; i++ {
        for j := 1; j<n; j++ {
            if(f[i-1][j] < f[i][j-1]) {
                f[i][j] = f[i-1][j] + grid[i][j]
            } else {
                f[i][j] = f[i][j-1] + grid[i][j]
            }

        }
    }
    return f[m-1][n-1]
}

91. Decode Ways

求解共有多少種解碼情況。

public int numDecodings(String s) {
    int n = s.length();
    if (n == 0 || (n == 1 && s.charAt(0) == '0'))
        return 0;
    int[] d = new int[n+1];
    d[n] = 1;
    d[n - 1] = s.charAt(n - 1) == '0' ? 0 : 1;
    for (int i = n-2; i >= 0; i--) {
        if(s.charAt(i) == '0')
            continue;
        else if(Integer.parseInt(s.substring(i, i+2)) <= 26)
            d[i] += d[i + 2];
        d[i] += d[i + 1];
    }
    return d[0];
}

如需轉載,請註明作者及出處.

作者:Treant

出處:http://www.cnblogs.com/en-heng/