[LEECODE]算法进阶自练习17 动态规划

298 阅读3分钟

[LEECODE]算法进阶自练习17 动态规划

17.动态规划

简单
  1. 爬楼梯 leetcode 70  递归解法,不能AC
    int climbStairs(int n) {
        if(n == 1) return 1;
        if(n == 2) return 2;
        return climbStairs(n-1) + climbStairs(n-2);
    }

 循环解法:类似求斐波那契数列

    int climbStairs(int n) {
        int s1 = 0, s2 = 0, ret = 1;
        for(int i=1; i<=n; i++){
            s1 = s2;
            s2 = ret;
            ret = s1 + s2;
        }
        return ret;
    }

 动态规划:存储所有的状态集合

    int climbStairs(int n) {
        if(n == 1) return 1;
        if(n == 2) return 2;
        vector<int> ret(n, 0);
        ret[0] = 1;
        ret[1] = 2;
        for(int i=2; i<n; i++){
            ret[i] = ret[i-1] + ret[i-2];
        }
        return ret[n-1];
    }
  1. 打家劫舍 leetcode 198  动态规划解法:
    int rob(vector<int>& nums) {
        int nsize = nums.size();
        if(nsize == 0) return 0;
        if(nsize == 1) return nums[0];

        vector<int> ret(nsize,0);
        ret[0] = nums[0]; ret[1] = max(nums[0],nums[1]);
        for(int i=2; i<nsize; i++){
            ret[i] = max(ret[i-1],ret[i-2] + nums[i]);
        }
        return ret[nsize-1];
    }

 滚动数组,减少空间复杂度解法:

    int rob(vector<int>& nums) {
        int nsize = nums.size();
        if(nsize == 0) return 0;
        if(nsize == 1) return nums[0];
        int first = nums[0], second = max(nums[0],nums[1]);
        for(int i=2; i<nsize; i++){
            int temp = second;
            second = max(first+nums[i], second);
            first = temp;
        }
        return second;
    }
中等
  1. 最佳买卖股票时机含冷冻期 leetcode 309  动态规划解法:
    int maxProfit(vector<int>& prices) {
        int psize = prices.size();
        if(psize < 2) return 0;
        // dp[i][0]:持有股票最大收益
        // dp[i][1]:持有股票但是在冷冻期最大收益
        // dp[i][2]:不持有股票,且不在冷冻期的最大收益
        vector<vector<int>> dp(psize, vector<int>(3,0));
        dp[0][0] = -prices[0];
        for(int i=1; i<psize; i++){
            dp[i][0] = max(dp[i-1][0], dp[i-1][2] - prices[i]);
            dp[i][1] = dp[i-1][0] + prices[i]; // 不能再买,但是可以再卖
            dp[i][2] = max(dp[i-1][1], dp[i-1][2]);
        }
        return max(dp[psize-1][1], dp[psize-1][2]);
    }

 动态规划状态压缩解法:

    int maxProfit(vector<int>& prices) {
        int psize = prices.size();
        if(psize < 2) return 0;
        // dp0:持有股票最大收益
        // dp1:持有股票但是在冷冻期最大收益
        // dp2:不持有股票,且不在冷冻期的最大收益
        int dp0 = -prices[0];
        int dp1 = 0;
        int dp2 = 0;
        for(int i=1; i<psize; i++){
            int n_dp0 = max(dp0, dp2 - prices[i]);
            int n_dp1 = dp0 + prices[i]; // 不能再买,但是可以再卖
            int n_dp2 = max(dp1, dp2);
            dp0 = n_dp0;
            dp1 = n_dp1;
            dp2 = n_dp2;
        }
        return max(dp1, dp2);
    }
  1. 打家劫舍II leetcode 213
    int myRob(vector<int>& nums){
        int nsize = nums.size();
        if(nsize == 0) return 0;
        if(nsize == 1) return nums[0];
        int first = nums[0], second = max(nums[0], nums[1]);
        for(int i=2; i<nsize; i++){
            int tmp = second;
            second =  max(second, first + nums[i]);
            first = tmp;
        }
        return second;
    }
    int rob(vector<int>& nums) {
        int nsize = nums.size();
        if(nsize == 0) return 0;
        if(nsize == 1) return nums[0];
        vector<int> nums1(nums.begin(), nums.end()-1), nums2(nums.begin()+1, nums.end());
        return max(myRob(nums1),myRob(nums2));
    }
  1. 打家劫舍III leetcode 337  这里问题其实就转换成为在节点只存在选中与不选中的情况下,不能同时选中父子节点的最大权值是多少,因此这里其实也可以用动态规划。
class Solution {
public:
    unordered_map<TreeNode*, int> sel, un_sel;
    void dfs(TreeNode* node){
        if(!node){
            return;
        }
        dfs(node->left);
        dfs(node->right);
        sel[node] = node->val + un_sel[node->left] + un_sel[node->right]; // 选中当前node节点,那么当前节点的左右子节点就不能选中了。
        un_sel[node] = max(sel[node->left], un_sel[node->left]) + max(sel[node->right], un_sel[node->right]); // 不选中当前子节点,那么就需要判断左子树和右子树中选中与不选中的最大值。
    }
    int rob(TreeNode* root) {
        dfs(root);
        return max(sel[root],un_sel[root]);
    }
};

 优化一下存储空间:

struct SelStatus{
    int sel;
    int unsel;
};
class Solution {
public:
    SelStatus dfs(TreeNode* node){
        if(!node){
            return {0,0};
        }
        SelStatus left = dfs(node->left);
        SelStatus right =  dfs(node->right);
        int sel = node->val + left.unsel + right.unsel;
        int unsel = max(left.unsel, left.sel) + max(right.sel, right.unsel);
        return {sel, unsel};
    }
    int rob(TreeNode* root) {
        SelStatus status = dfs(root);
        return max(status.sel, status.unsel);
    }
};
  1. 不同路径 leetcode 62  动态规划解法:
int uniquePaths(int m, int n) {
        vector<vector<int>> dp(m+1,vector<int>(n+1,0));
        for(int i=0; i<m; i++) for(int j=0; j<n; j++) dp[i][j] = (i==0 || j==0) ? 1 : (dp[i-1][j] + dp[i][j-1]);
        
        return dp[m-1][n-1];
    }

 排列组合解法:

    int uniquePaths(int m, int n) {
        if(m==1 || n==1) return 1;
        if(m>n) swap(m,n);
        unsigned long t = 1, ret = 1;
        for(int i=1; i<=m-1; i++) t*=i;
        for(int i=n; i<=m+n-2; i++) ret*=i;
        return ret/t;
    }
  1. 不同路径II leetcode 63  动态规划解法:
    int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
        int m = obstacleGrid.size(), n = obstacleGrid[0].size();
        vector<vector<int>> dp(m,vector<int>(n,0));

        for (int i = 0; i < m && obstacleGrid[i][0] == 0; i++) {
            dp[i][0] = 1;
        }
        for (int j = 0; j < n && obstacleGrid[0][j] == 0; j++) {
            dp[0][j] = 1;
        }
        for(int i=1; i<m; i++){
            for(int j=1; j<n; j++){
                if(obstacleGrid[i][j] == 0){
                    dp[i][j] = dp[i-1][j] + dp[i][j-1];
                }
            }
        }
        return dp[m-1][n-1];
    }
困难
  1. 买卖股票的最佳时机 IV leetcode 188
    int infMaxProfit(vector<int>& prices){
        int psize = prices.size();
        if(psize < 2) return 0;
        int ret = 0;
        for(int i=0; i<psize-1; i++){
            if(prices[i+1] > prices[i]) ret += (prices[i+1] - prices[i]);
        }
        return ret;
    }
    int maxProfit(int k, vector<int>& prices) {
        int psize = prices.size();
        if(psize < 2 || k < 1) return 0;
        if(k > psize/2) return infMaxProfit(prices);
        // k次交易
        // 0 买入股票 1卖出股票
        vector<vector<int>> dp(k, vector<int>(2,INT_MIN));
        
        for(int j=0; j<psize; j++){
            dp[0][0] = max(dp[0][0], -prices[j]); // 第一次买
            dp[0][1] = max(dp[0][1], dp[0][0] + prices[j]); // 第一次卖
            for (int i = 1; i < k; i++) 
            {
                dp[i][0] = max(dp[i][0], dp[i - 1][1] - prices[j]); // 第 i 次买
                dp[i][1] = max(dp[i][1], dp[i][0] + prices[j]);     // 第 i 次卖
            }
        }
        return dp[k-1][1];
    }
  1. 买卖股票的最佳时机 III leetcode 123  下面的题解加个k=2就可以了:
    int infMaxProfit(vector<int>& prices){
        int psize = prices.size();
        if(psize < 2) return 0;
        int ret = 0;
        for(int i=0; i<psize-1; i++){
            if(prices[i+1] > prices[i]) ret += (prices[i+1] - prices[i]);
        }
        return ret;
    }
    int maxProfit(vector<int>& prices) {
        int psize = prices.size();
        if(psize < 2) return 0;
        int k = 2;
        if(k > psize/2) return infMaxProfit(prices);
        // k次交易
        // 0 买入股票 1卖出股票
        vector<vector<int>> dp(k, vector<int>(2,INT_MIN));
        
        for(int j=0; j<psize; j++){
            dp[0][0] = max(dp[0][0], -prices[j]); // 第一次买
            dp[0][1] = max(dp[0][1], dp[0][0] + prices[j]); // 第一次卖
            for (int i = 1; i < k; i++) 
            {
                dp[i][0] = max(dp[i][0], dp[i - 1][1] - prices[j]); // 第 i 次买
                dp[i][1] = max(dp[i][1], dp[i][0] + prices[j]);     // 第 i 次卖
            }
        }
        return dp[k-1][1];
    }

 三维dp解法:

    int maxProfit(vector<int>& prices) {
        int psize = prices.size();
        if(psize < 2) return 0;
        // 第一维psize:表示psize个状态, 第二位两个状态:0表示不买股票 1表示买股票
        // 第三维 表示0,1,2表示完成交易的次数
        int dp[psize][2][3];
        dp[0][0][0] = dp[0][0][1] = dp[0][0][2] = 0;
        dp[0][1][0] = dp[0][1][1] = dp[0][1][2] = -prices[0];
        for(int i=1; i<psize; i++){
            dp[i][0][0] = 0; // 不买股票,也不交易
            dp[i][0][1] = max(dp[i-1][0][1], dp[i-1][1][0]+prices[i]);
            dp[i][0][2] = max(dp[i-1][0][2], dp[i-1][1][1]+prices[i]);
            dp[i][1][0] = max(dp[i-1][1][0], dp[i-1][0][0]-prices[i]);
            dp[i][1][1] = max(dp[i-1][1][1], dp[i-1][0][1]-prices[i]);
            dp[i][1][2] = 0; // 买股票,完成2次交易就不能动了。
        }
        
        return max(dp[psize-1][0][2],dp[psize-1][0][1]);
    }

 二维dp解法:

    int maxProfit(vector<int>& prices) {
        int psize = prices.size();
        if(psize < 2) return 0;
        // 第一维psize表示psize天
        // 第二维压缩为五个状态:0-没有交易,1-买入一次交易,2-完成一次交易,3-买入第二次交易,4-完成第二次交易
        int dp[psize][5];
        dp[0][0] = dp[0][2] = dp[0][4] = 0;
        dp[0][1] = dp[0][3] = -prices[0];
        for(int i=1; i<psize; i++){
            dp[i][0] = 0;
            dp[i][1] = max(dp[i-1][0] - prices[i], dp[i-1][1]);
            dp[i][2] = max(dp[i-1][1] + prices[i], dp[i-1][2]);
            dp[i][3] = max(dp[i-1][2] - prices[i], dp[i-1][3]);
            dp[i][4] = max(dp[i-1][3] + prices[i], dp[i-1][4]);
        }
        return max(dp[psize-1][4], dp[psize-1][2]);
    }

 一维dp解法:

    int maxProfit(vector<int>& prices) {
        int psize = prices.size();
        if(psize < 2) return 0;
        // 5个分别表示未交易状态,买入1次,卖出1次,买入2次,卖出2次
        vector<int> dp(5,INT_MIN);
        dp[0] = 0; dp[1] = -prices[0];
        for(int i=1; i<psize; i++){
            dp[0] = 0;
            dp[1] = max(dp[1],dp[0]-prices[i]);
            dp[2] = max(dp[2],dp[1]+prices[i]);
            dp[3] = max(dp[3],dp[2]-prices[i]);
            dp[4] = max(dp[4],dp[3]+prices[i]);
        }
        return max(dp[4], dp[2]);
    }
其他买卖股票最佳时机
  1. 买卖股票最佳最佳时机 leetcode 121   暴力解法,不能AC:
    int maxProfit(vector<int>& prices) {
        int psize = prices.size();
        if(psize < 2) return 0;
        int min_price = prices[0], ret = 0;
        for(int i=0; i<psize-1; i++){
            for(int j=i+1; j<psize; j++) if(prices[j] - prices[i] > ret) ret = prices[j] - prices[i];
        }
        return ret;
    }

 动态规划:

    // dp[i] = max(dp[i-1], prices[i]-min_price); 状态转移方程
    int maxProfit(vector<int>& prices) {
        int psize = prices.size();
        if(psize < 2) return 0;
        int min_price = prices[0];
        vector<int> dp(psize,0);
        for(int i=1; i<psize; i++){
            min_price = min(min_price, prices[i]);
            dp[i] = max(dp[i-1],prices[i]-min_price);
        }
        return dp[psize-1];
    }

 动态规划空间压缩:

    // dp[i] = max(dp[i-1], prices[i]-min_price); 状态转移方程
    int maxProfit(vector<int>& prices) {
        int psize = prices.size();
        if(psize < 2) return 0;
        int min_price = prices[0], max_price = 0;
        vector<int> dp(psize,0);
        for(int i=1; i<psize; i++){
            max_price = max(max_price,prices[i]-min_price);
            min_price = min(min_price, prices[i]);
        }
        return max_price;
    }
  1. 买卖股票最佳时机II leetcode 122  贪心解法:
    int maxProfit(vector<int>& prices) {
        int psize = prices.size();
        if(psize < 2) return 0;
        int ret = 0;
        for(int i=1; i<psize; i++){
            if(prices[i] > prices[i-1]) ret += (prices[i]-prices[i-1]);
        }
        return ret;
    }

 动态规划解法:

    int maxProfit(vector<int>& prices) {
        int psize = prices.size();
        if(psize < 2) return 0;
        vector<vector<int>> dp(psize, vector<int>(2,0));
        dp[0][1] = -prices[0];
        for(int i=1; i<psize; i++){
            dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i]); // 不动或者卖股票赚钱
            dp[i][1] = max(dp[i-1][1], dp[i-1][0] - prices[i]); // 不动或者花钱买股票
        }
        return dp[psize-1][0]; // 最终返回最后持有的现金数量
    }

 动态规划空间压缩解法:

    int maxProfit(vector<int>& prices) {
        int psize = prices.size();
        if(psize < 2) return 0;
        int preCash = 0;
        int preStock = -prices[0];
        for(int i=1; i<psize; i++){
            preCash = max(preCash, preStock + prices[i]); // 不动或者卖股票赚钱
            preStock = max(preStock, preCash - prices[i]); // 不动或者花钱买股票
        }
        return preCash; // 最终返回最后持有的现金数量
    }