1. 程式人生 > 實用技巧 >劍指Offer - 動態規劃

劍指Offer - 動態規劃

劍指Offer中的動態規劃除了一題hard(正則表示式匹配)都比較簡單,要是面試只考到這種程度就好了...

連續子陣列的最大和

這道題可以作為線性dp的模板。

用dp[i]表示以元素nums[i]為結尾的連續子陣列最大和。

當以nums[i-1]為結尾的陣列和(dp[i-1])大於0,對於以nums[i]為結尾的子陣列(dp[i]),加上前一個數組和將得到更大的和;

當以nums[i-1]為結尾的陣列和(dp[i-1])小於0,對於以nums[i]為結尾的子陣列(dp[i]),加上前一個數組和只會使結果比nums[i]自身小,所以不如捨棄前面的數。

狀態轉移方程:

dp[i-1] > 0,dp[i] = dp[i-1] + nums[i]

dp[i-1] < 0,dp[i] = nums[i]

dp[0] = nums[0]

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int N = nums.size();
        vector<int> dp(N,0);
        dp[0] = nums[0];
        int res = dp[0];
        for(int i=1;i<N;++i) {
            if(dp[i-1] >= 0) {
                dp[i] 
= dp[i-1] + nums[i]; } else { dp[i] = nums[i]; } res = dp[i] > res ? dp[i] : res; } return res; } };

空間複雜度優化

上面的解法開闢了和nums等長的dp陣列,而事實上dp陣列是可以省略的。

仔細看發現整個執行過程中,nums陣列只被掃描了一次,且dp[i]的值僅和nums[i]有關,換言之,當程式開始計算dp[i]時,nums[i-1]以及之前的資料就變成無關緊要的了。

所以,在計算出dp[i-1]時,用該結果覆蓋nums[i-1]的值,當程式開始計算dp[i]時,直接取出nums[i-1]即可。

將原陣列作為dp陣列,線上性dp中是常用的優化空間複雜度的方法。

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int res = nums[0];
        for(int i = 1; i < nums.size(); ++i) {
            if(nums[i-1] >= 0) {
                nums[i] = nums[i-1] + nums[i];
            }
            res = nums[i] > res ? nums[i] : res;
        }
        return res;
    }
};

股票的最大利潤

和最大子陣列和的思路類似

順著一般的思路來模擬即可,甚至做完才發現是dp。

狀態轉移方程:

dp[i]=max(dp[i1],prices[i]min)

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        if(prices.empty()) return 0;
        int min = prices[0];
        int res = 0;
        for(int i = 1; i < prices.size(); ++i) {
            if(prices[i] <= min) {
                min = prices[i];
            } 
            else {
                res = res > prices[i] - min ? res : prices[i] - min;
            }
        }
        return res;
    }
};

禮物的最大價值

這種題目很容易當成貪心來做,但貪心並不能保證最優解,可以輕易舉出反例。

開闢二維dp陣列,dp[i][j]表示從(0,0)出發選擇禮物,到達(i,j)時累計的禮物最大價值。

題目規定只有向右和向下兩種方式,考慮四種情況:

1.首行(i=0):此時上邊沒有禮物,不可能從上向下,所以無需考慮向下的情況。dp[0][j] = dp[0][j-1] + grid[0][j]

2.首列(j=0):此時左邊沒有禮物,不可能從左向右,所以無需考慮向右的情況。dp[i][0] = dp[i-1][0] + grid[i][0]

3.首元素(i=0,j=0):此時既不可能從上向下也不可能從左向右。dp[0][0] = grid[0][0]

4.非首行且非首列(i != 0, j != 0):從上向下或從左向右得到。dp[i][j] = max(dp[i-1][j], dp[i][j-1]) + grid[i][j]

分析的時候,刻意額外開闢了dp陣列以和原陣列區別開來。真正實現時,依然可以選擇覆蓋grid陣列來優化空間複雜度。

#define max(a, b) a > b ? a : b
class Solution {
public:
    int maxValue(vector<vector<int>>& grid) {
        int m = grid.size();
        int n = grid[0].size();
        for(int i = 0; i < m; i++) {
            for(int j = 0; j < n; j++) {
                if(i == 0 && j == 0) continue;
                if(i == 0) grid[i][j] += grid[i][j - 1] ;
                else if(j == 0) grid[i][j] += grid[i - 1][j];
                else grid[i][j] += max(grid[i][j - 1], grid[i - 1][j]);
            }
        }
        return grid[m - 1][n - 1];
    }
};

這道題也可以用搜索來解,程式碼略,下次整理記憶化搜尋時附上。