剑指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[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]; } };
这道题也可以用搜索来解,代码略,下次整理记忆化搜索时附上。