劍指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陣列,線上性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[i−1],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]; } };
這道題也可以用搜索來解,程式碼略,下次整理記憶化搜尋時附上。