剑指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[imax(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];
    }
};

这道题也可以用搜索来解,代码略,下次整理记忆化搜索时附上。

 

posted @ 2020-07-31 21:35  CofJus  阅读(196)  评论(0编辑  收藏  举报