力扣动态规划专题(四)劫舍问题与股票问题 打家劫舍Ⅰ Ⅱ Ⅲ 买卖股票最佳时机Ⅰ Ⅱ Ⅲ IV 步骤及C++实现

news2024/11/25 7:11:18

文章目录

  • 198. 打家劫舍
  • 213. 打家劫舍 II
  • 337. 打家劫舍 III
  • 121. 买卖股票的最佳时机
    • 动态规划
    • 贪心算法
  • 122. 买卖股票的最佳时机 II
    • 动态规划
    • 贪心算法
  • 123.买卖股票的最佳时机III
  • 188.买卖股票的最佳时机IV
  • 309.最佳买卖股票时机含冷冻期
  • 714.买卖股票的最佳时机含手续费

198. 打家劫舍

在这里插入图片描述
步骤

  1. 确定dp数组以及下标的含义
    dp[i]:考虑下标i(包括i)以内的房屋,最多可以偷窃的金额为dp[i]

  2. 确定递推公式

  • 决定dp[i]的因素就是第i房间偷还是不偷,当前房屋偷与不偷取决于 前一个房屋和前两个房屋是否被偷了
  • 偷第i房间,那么dp[i] = dp[i - 2] + nums[i] ,第i-1房不能偷,找出下标i-2(包括i-2)以内的房屋,最多可以偷窃的金额为 dp[i-2] +第i房间偷到的钱。
  • 不偷第i房间,那么dp[i] = dp[i - 1],考虑i-1房,不一定要偷i-1房,最多可以偷窃的金额不变,仍为dp[i-1]
  • dp[i]取最大值,dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]);
  1. dp数组如何初始化
    递推公式的基础依赖于dp[0] 和 dp[1]
    i=0时,dp[0]只能是nums[0],i=1时,dp[1]取nums[0]和nums[1]的最大值
    dp[0]= =nums[0],dp[1]=max(nums[0], nums[1]);

  2. 确定遍历顺序
    dp[i] 是根据dp[i - 2] 和 dp[i - 1] 推导出来的,那么从前到后遍历

  3. 举例推导dp数组
    输入: [2,7,9,3,1]

  4. C++实现

class Solution {
public:
    int rob(vector<int>& nums) {
        if(nums.size() == 0) return 0;
        if(nums.size() == 1) return nums[0];
        vector<int> dp(nums.size(), 0);
        dp[0] = nums[0];
        dp[1] = max(nums[0], nums[1]);
        for(int i=2; i<nums.size(); i++)
        {
            dp[i] = max(dp[i-1], dp[i-2]+nums[i]);
        }
        return dp[nums.size()-1];
    }
};

213. 打家劫舍 II

在这里插入图片描述
和198.打家劫舍类似,但是房屋是闭环的,分三种情况

情况三考虑包含尾元素,但不一定要选尾部元素,并且对于情况三,取nums[1] 和 nums[3]就是最大的。
同时情况二和情况三都包含了情况一,所以只考虑情况二和情况三就可以了。

步骤

  1. 确定dp数组以及下标的含义
    dp[i]:考虑下标i(包括i)以内的房屋,最多可以偷窃的金额为dp[i]

  2. 确定递推公式

  • 决定dp[i]的因素就是第i房间偷还是不偷,当前房屋偷与不偷取决于 前一个房屋和前两个房屋是否被偷了
  • 偷第i房间,那么dp[i] = dp[i - 2] + nums[i] ,第i-1房不能偷,找出下标i-2(包括i-2)以内的房屋,最多可以偷窃的金额为 dp[i-2] +第i房间偷到的钱。
  • 不偷第i房间,那么dp[i] = dp[i - 1],考虑i-1房,不一定要偷i-1房,最多可以偷窃的金额不变,仍为dp[i-1]
  • dp[i]取最大值,dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]);
  1. dp数组如何初始化
    递推公式的基础依赖于dp[0] 和 dp[1]
    i=0时,dp[0]只能是nums[0],i=1时,dp[1]取nums[0]和nums[1]的最大值
    dp[0]= =nums[0],dp[1]=max(nums[0], nums[1]);

  2. 确定遍历顺序
    dp[i] 是根据dp[i - 2] 和 dp[i - 1] 推导出来的,那么从前到后遍历

  3. C++实现

class Solution {
public:
    int rob(vector<int>& nums) {
        if(nums.size() == 0) return 0;
        if(nums.size() == 1) return nums[0];
        int result1 = robrange(nums, 0, nums.size()-2); //情况二 包含首元素,不包含尾元素
        int result2 = robrange(nums, 1, nums.size()-1); //情况三 包含尾元素,不包含首元素
        return max(result1, result2);
    }
    int robrange(vector<int>& nums, int start, int end)
    {
        if(start == end) return nums[start];
        vector<int> dp(nums.size());
        dp[start] = nums[start];
        dp[start+1] = max(nums[start], nums[start+1]);
        for(int i=start+2; i<=end; i++)
        {
            dp[i] = max(dp[i-1], dp[i-2]+nums[i]);
        }
        return dp[end];
    }
};

337. 打家劫舍 III

在这里插入图片描述
和198.打家劫舍类似,但是房屋是二叉树形结构,对于树的遍历方式,前中后序(深度优先搜索),层序遍历(广度优先搜索)。本题一定是后序遍历,因为通过递归函数的返回值来做下一步计算。

与198.打家劫舍,213.打家劫舍II一样,关键在于当前节点抢还是不抢。如果抢了当前节点,两个孩子就不能动,如果没抢当前节点,就可以考虑抢左右孩子

动态规划使用状态转移容器来记录状态的变化,可以使用一个长度为2的数组,记录当前节点偷与不偷所得到的的最大金钱。

步骤

  1. 确定递归函数的参数和返回值
  • 要求一个节点 偷与不偷的两个状态所得到的金钱,那么返回值就是一个长度为2的数组也就是dp数组。
  • dp数组以及下标的含义:dp数组是一个长度为2的数组,下标为0记录不偷该节点所得到的的最大金钱,下标为1记录偷该节点所得到的的最大金钱。
  1. 确定终止条件
    遍历的过程中,如果遇到空节点的话,无论偷还是不偷都是0,返回,相当于dp数组的初始化

  2. 确定遍历顺序

  • 使用后序遍历,要通过递归函数的返回值来做下一步计算。
  • 通过递归左节点,得到左节点偷与不偷的金钱。
  • 通过递归右节点,得到右节点偷与不偷的金钱。
  1. 确定单层递归的逻辑
  • 偷当前节点,左右孩子不能偷,val1 = cur->val + left[0] + right[0];
  • 不偷当前节点,左右孩子可以偷,选最大的,val2 = max(left[0], left[1]) + max(right[0], right[1]);
  • 当前节点的状态为{val2, val1}; 即 {不偷当前节点得到的最大金钱,偷当前节点得到的最大金钱}
  1. 举例推导dp数组
    示例1

  2. C++实现

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    int rob(TreeNode* root) {
        vector<int> result = robTree(root);
        return max(result[0], result[1]);
    }
    vector<int> robTree(TreeNode* cur)
    {
        if(cur == nullptr) return vector<int>{0, 0};
        vector<int> left = robTree(cur->left);
        vector<int> right = robTree(cur->right);
        //偷cur 不偷左右节点
        int var1 = cur->val + left[0] + right[0];
        //不偷cur 可以偷或者不偷左右节点 取较大值
        int var2 = max(left[0], left[1]) + max(right[0], right[1]);
        return {var2, var1};
    }
};

121. 买卖股票的最佳时机

在这里插入图片描述

动态规划

步骤

  1. 确定dp数组以及下标的含义
  • dp[i][0],表示第i天持有股票所得最多现金
  • dp[i][1],表示第i天不持有股票所得最多现金
  • “持有”不代表就是当天“买入”,也有可能是昨天就买入了,今天保持持有的状态
  1. 确定递推公式
  • dp[i][0]可以由两个状态推出来

    • 第i-1天就持有股票,所得现金=昨天持有股票的所得现金,dp[i - 1][0]
    • 第i天买入股票,所得现金=买入今天的股票后所得现金即,-prices[i]
    • dp[i][0]取所得现金最大的,dp[i][0] = max(dp[i - 1][0], -prices[i]);
  • dp[i][1]可以由两个状态推出来

    • 第i-1天就不持有股票,所得现金=昨天不持有股票的所得现金,dp[i - 1][1]
    • 第i天卖出股票,所得现金=按照今天股票价格卖出后所得现金,prices[i] + dp[i - 1][0]
    • dp[i][1]取所得现金最大的,dp[i][1] = max(dp[i - 1][1], prices[i] + dp[i - 1][0]);
  1. dp数组如何初始化
  • dp[0][0]表示第0天持有股票,买入了股票,dp[0][0] = -prices[0];
  • dp[0][1]表示第0天不持有股票,没有买股票,现金为0,dp[0][1] = 0;
  1. 确定遍历顺序
    dp[i]由dp[i - 1]推导出而来,从前向后遍历

  2. 举例推导dp数组
    输入: 输入:[7,1,5,3,6,4]

    最终结果是dp[5][1],而不是dp[5][0],因为不持有股票状态所得金钱一定比持有股票状态得到的多

  3. C++实现

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        //二维dp数组
        int len = prices.size();
        if(len == 0) return 0;
        vector<vector<int>> dp(len, vector<int>(2));
        dp[0][0] = -prices[0];
        dp[0][1] = 0;
        for(int i=1; i<len; i++)
        {
            dp[i][0] = max(dp[i-1][0], -prices[i]);
            dp[i][1] = max(dp[i-1][1], prices[i]+dp[i-1][0]);
        }
        return dp[len - 1][1];

        /*
        //二维dp滚轮数组
        int len = prices.size();
        if(len == 0) return 0;
        vector<vector<int>> dp(2, vector<int>(2));
        dp[0][0] -= prices[0];
        dp[0][1] = 0;
        for(int i=1; i<len; i++)
        {
            dp[i % 2][0] = max(dp[(i-1) % 2][0], -prices[i]);
            dp[i % 2][1] = max(dp[(i-1) % 2][1], prices[i] + dp[(i-1)%2][0]);
        }
        return dp[(len-1) % 2][1];*/
    }
};

贪心算法

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        //贪心算法
        int low = INT_MAX;
        int result = 0;
        for(int i=0; i<prices.size(); i++)
        {
            low = min(low, prices[i]);// 取最左最小价格
            result = max(result, prices[i] - low);// 直接取最大区间利润
        }
        return result;
    }
};

122. 买卖股票的最佳时机 II

在这里插入图片描述

动态规划

本题和121. 买卖股票的最佳时机的唯一区别是股票可以买卖多次,注意最多只能持有 一只股票,再次购买前要出售掉之前的股票。区别主要是体现在递推公式上,其他都一样。

步骤

  1. 确定dp数组以及下标的含义
  • dp[i][0],表示第i天持有股票所得最多现金
  • dp[i][1],表示第i天不持有股票所得最多现金
  • “持有”不代表就是当天“买入”,也有可能是昨天就买入了,今天保持持有的状态
  1. 确定递推公式
  • dp[i][0]可以由两个状态推出来

    • 第i-1天就持有股票,所得现金=昨天持有股票的所得现金,dp[i - 1][0]
    • 第i天买入股票,所得现金=昨天不持有股票的所得现金减去今天的股票价格,dp[i - 1][1] - prices[i],区别点
    • dp[i][0]取所得现金最大的,dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] - prices[i]);
  • dp[i][1]可以由两个状态推出来

    • 第i-1天就不持有股票,所得现金=昨天不持有股票的所得现金,dp[i - 1][1]
    • 第i天卖出股票,所得现金=按照今天股票价格卖出后所得现金,dp[i - 1][0] + prices[i]
    • dp[i][1]取所得现金最大的,dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] + prices[i]);
  1. dp数组如何初始化
  • dp[0][0]表示第0天持有股票,dp[0][0] -= prices[0];
  • dp[0][1]表示第0天不持有股票,没有买股票,现金为0,dp[0][1] = 0;
  1. 确定遍历顺序
    dp[i]由dp[i - 1]推导出而来,从前向后遍历

  2. C++实现

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        //动态规划
        int n = prices.size();
        vector<vector<int>> dp(n, vector<int>(2, 0));
        dp[0][0] = -prices[0];//持股票
        dp[0][1] = 0;//持现金
        for(int i=1; i<n; i++)
        {
            dp[i][0] = max(dp[i-1][0], dp[i-1][1] - prices[i]);//第i天买了股票
            dp[i][1] = max(dp[i-1][1], dp[i-1][0] + prices[i]);//第i天卖了股票
        }
        return dp[n-1][1];

        /*
        //二维dp滚轮数组
        int len = prices.size();
        if(len == 0) return 0;
        vector<vector<int>> dp(2, vector<int>(2));
        dp[0][0] -= prices[0];
        dp[0][1] = 0;
        for(int i=1; i<len; i++)
        {
            dp[i % 2][0] = max(dp[(i-1) % 2][0], -prices[i]);
            dp[i % 2][1] = max(dp[(i-1) % 2][1], prices[i] + dp[(i-1)%2][0]);
        }
        return dp[(len-1) % 2][1];*/
    }
};

贪心算法

class Solution {
public:
    //贪心算法
    int maxProfit(vector<int>& prices) {
        int result = 0;
        for(int i=1; i<prices.size(); i++)//i从1开始,第二天才有利润
        {
            //累加每天的正利润,最后求的最大利润
            result += max(prices[i]-prices[i-1], 0);
        }
        return result;
    }
};

123.买卖股票的最佳时机III

在这里插入图片描述

和122.买卖股票的最佳时机II主要区别在于至多买卖两次,这意味着可以买卖一次,可以买卖两次,也可以不买卖。因此,可以假设一天有五种状态来记录:
0-没有操作
1-第一次持有股票
2-第一次不持有股票
3-第二次持有股票
4-第二次不持有股票

步骤

  1. 确定dp数组以及下标的含义
    dp[i][j]:i表示第i天,j为 [0 - 4] 五个状态,dp[i][j]表示第i天状态j所剩最大现金。
    要注意的是,dp[i][1]表示的是第i天持有股票的状态,并不一定是第i天买入股票,有可能 第 i-1天 就买入了,那么 dp[i][1] 延续持有股票的这个状态。

  2. 确定递推公式
    五种状态的推导

  • dp[i][1],持有股票,两个状态中取最大值

    • 第i天买入股票,那么dp[i][1] = dp[i-1][0] - prices[i]
    • 第i天没有操作,而是沿用前一天买入的状态,即:dp[i][1] = dp[i - 1][1]
    • dp[i][0]取所得现金最大的,dp[i][1] = max(dp[i-1][0] - prices[i], dp[i - 1][1]);
  • dp[i][2],不持有股票,两个状态中取最大值

    • 第i天卖出股票了,那么dp[i][2] = dp[i - 1][1] + prices[i]
    • 第i天没有操作,沿用前一天卖出股票的状态,即:dp[i][2] = dp[i - 1][2]
    • dp[i][2]取所得现金最大的,dp[i][2] = max(dp[i - 1][1] + prices[i], dp[i - 1][2]);
  • dp[i][3] = max(dp[i - 1][3], dp[i - 1][2] - prices[i]);

  • dp[i][4] = max(dp[i - 1][4], dp[i - 1][3] + prices[i]);

  1. dp数组如何初始化
  • 第0天没有操作,dp[0][0] = 0;
  • 第0天第一次买入的操作,dp[0][1] = -prices[0];
  • 第0天第一次卖出的操作,当天买入,当天卖出,dp[0][2] = 0;
  • 第0天第二次买入操作,第二次买入依赖于第一次卖出的状态,相当于第0天买入-卖出-再买入,dp[0][3] = -prices[0];
  • 第0天第二次买入操作,第0天买入-卖出-再买入-再卖出,dp[0][4] = 0;
  1. 确定遍历顺序
    dp[i]依赖dp[i - 1],从前向后遍历

  2. 举例推导dp数组
    输入[1,2,3,4,5]

红色框为最后两次卖出的状态,现金最多的时候一定是卖出的状态,而两次卖出的状态现金最大一定是最后一次卖出,因为dp[i][j]表示第i天状态j所剩最大现金。

  1. C++实现

保存每一天的五种状态

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        if(prices.size() == 0) return 0;
        //保存每一天的五种状态
        vector<vector<int>> dp(prices.size(), vector<int>(5, 0));//第0天没有操作 第一次卖出 第二次卖出操作
        dp[0][1] = -prices[0];//第0天第一次买股票
        dp[0][3] = -prices[0];//第0天第二次买股票
        for(int i = 1; i<prices.size(); i++)
        {
            dp[i][0] = dp[i-1][0];
            dp[i][1] = max(dp[i-1][1], dp[i-1][0] - prices[i]);//第一次持有股票
            dp[i][2] = max(dp[i-1][2], dp[i-1][1] + prices[i]);//第一次不持有股票
            dp[i][3] = max(dp[i-1][3], dp[i-1][2] - prices[i]);
            dp[i][4] = max(dp[i-1][4], dp[i-1][3] + prices[i]);
        }
        return dp[prices.size()-1][4];//第二次不持有股票的时候所得现金最多
    }
};

滚动数组-优化空间-只保存当天的五种状态

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        if(prices.size() == 0) return 0;
        //保存当天的五种状态
        vector<int> dp(5, 0);
        dp[1] = -prices[0];
        dp[3] = -prices[0];
        for(int i=1; i<prices.size(); i++)
        {
            dp[1] = max(dp[1], dp[0] - prices[i]);//max()里的dp都是前一天的状态
            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 dp[4];//第二次不持有股票的时候所得现金最多
    }
};

188.买卖股票的最佳时机IV

在这里插入图片描述

在 123.买卖股票的最佳时机III的基础上,要求至多有k次交易,那么当天有2k+1次操作:
0-不操作(可以不定义)
1-第一次持有股票
2-第一次不持有股票
3-第二次持有股票
4-第二次不持有股票
5-第三次持有股票

除了0以外,偶数就是卖出,奇数就是买入

步骤

  1. 确定dp数组以及下标的含义
    dp[i][j]:i表示第i天,j有2k+1种状态,dp[i][j]表示第i天状态j所剩最大现金。
    要注意的是,dp[i][1]表示的是第i天持有股票的状态,并不一定是第i天买入股票,有可能 第 i-1天 就买入了,那么 dp[i][1] 延续持有股票的这个状态。

  2. 确定递推公式

  • dp[i][1],持有股票,两个状态中取最大值

    • 第i天买入股票,dp[i][1] = dp[i-1][0] - prices[i]
    • 第i天没有操作,沿用前一天买入的状态,即:dp[i][1] = dp[i - 1][1]
    • dp[i][0]取所得现金最大的,dp[i][1] = max(dp[i-1][0] - prices[i], dp[i - 1][1]);
  • dp[i][2],不持有股票,两个状态中取最大值

    • 第i天卖出股票了,那么dp[i][2] = dp[i - 1][1] + prices[i]
    • 第i天没有操作,沿用前一天卖出股票的状态,即:dp[i][2] = dp[i - 1][2]
    • dp[i][2]取所得现金最大的,dp[i][2] = max(dp[i - 1][1] + prices[i], dp[i - 1][2]);

和123.买卖股票的最佳时机III 最大的区别就是j为奇数是买入,偶数是卖出状态

  1. dp数组如何初始化
  • 第0天没有操作,dp[0][0] = 0;
  • 第0天第一次买入的操作,dp[0][1] = -prices[0];
  • 第0天第一次卖出的操作,当天买入,当天卖出,dp[0][2] = 0;
  • 第0天第二次买入操作,第二次买入依赖于第一次卖出的状态,相当于第0天买入-卖出-再买入,dp[0][3] = -prices[0];
  • 第0天第二次买入操作,第0天买入-卖出-再买入-再卖出,dp[0][4] = 0;

dp[0][j]当j为奇数时都初始化为 -prices[0],j为偶数是卖、奇数是买的状态

  1. 确定遍历顺序
    dp[i]依赖dp[i - 1],从前向后遍历

  2. 举例推导dp数组
    输入[1,2,3,4,5],k=2

最后一次卖出,一定是利润最大的,dp[prices.size() - 1][2 * k],红框部分

  1. C++实现
    注意怎么模拟j为偶数是卖、奇数是买的状态
class Solution {
public:
    int maxProfit(int k, vector<int>& prices) {
        if(prices.size() == 0) return 0;
        vector<vector<int>> dp(prices.size(), vector<int>(2*k+1, 0));
        //初始化dp[i][j] j为奇数的状态
        for(int j=1; j<2 * k; j += 2)
        {
            dp[0][j] = -prices[0];
        }
        for(int i=1; i<prices.size(); i++)
        {
            for(int j=0; j< 2 * k - 1; j += 2)
            {
                dp[i][j+1] = max(dp[i-1][j+1], dp[i-1][j] - prices[i]);
                dp[i][j+2] = max(dp[i-1][j+2], dp[i-1][j+1] + prices[i]);
            }
        }
        return dp[prices.size()-1][2 * k];
    }
};

309.最佳买卖股票时机含冷冻期

在这里插入图片描述
步骤

  1. 确定dp数组以及下标的含义
    dp[i][j],第i天状态为j,所剩的最多现金为dp[i][j]。
    在122.买卖股票的最佳时机II的基础上加了一个冷冻期,相比于122题有两个状态,持有股票和不持有股票,而本题有四个状态:
  • j=0,状态一:持有股票状态,今天买入股票,或者之前买入了股票然后没有操作,一直持有
  • j=1,状态二:不持有股票状态,保持卖出股票的状态,比如两天前卖出了股票,度过一天冷冻期,或者前一天卖出股票状态,一直没操作
  • j=2,状态三:不持有股票状态,今天卖出股票
  • j=3,状态四:今天为冷冻期状态,但冷冻期状态不可持续,只有一天
    要注意的是,冷冻期的前一天,只能是状态三—今天卖出股票状态,如果是状态二—不持有股票状态,就不一定是卖出股票的操作了。
  1. 确定递推公式
  • dp[i][0],状态一,持有股票状态的两种方法
    1)前一天持有股票,dp[i][0] = dp[i - 1][0]
    2)前一天是冷冻期,dp[i - 1][3] - prices[i];或者前一天是保持卖出状态,dp[i - 1][1] - prices[i]
    dp[i][0]取所得现金最大的,dp[i][0] = max(dp[i - 1][0], dp[i - 1][3] - prices[i], dp[i - 1][1] - prices[i]);

  • dp[i][1],状态二,保持卖出股票状态的两种方法
    1)前一天是保持卖出状态,dp[i - 1][1]
    2)前一天是冷冻期,dp[i - 1][3]
    dp[i][1]取所得现金最大的,dp[i][1] = max(dp[i - 1][1], dp[i - 1][3]);

  • dp[i][2],状态三,今天卖出股票状态
    前一天只能是持有股票,今天卖出,dp[i][2] = dp[i - 1][0] + prices[i];

  • dp[i][3],状态四,达到冷冻期状态
    前一天只能是卖出股票状态,dp[i][3] = dp[i - 1][2];

  1. dp数组如何初始化——第0天如何初始化
  • 状态一,持有股票的话一定是当天买入股票,dp[0][0] = -prices[0]
  • 状态二,保持卖出股票状态,如果i为1,第1天买入股票,那么dp[i - 1][1] - prices[i] = dp[0][1] - prices[1]。而dp[0][1],也就是第0天的状态二,只能初始为0。如果初始为其他数值,说明第1天买入股票后手里还剩有现金
  • 状态三,今天卖出了股票,i=1,第1天买入股票再卖出股票,dp[i - 1][1] - prices[i] + prices[i] = dp[0][1] - prices[1] + prices[1]。那么dp[0][2]只能初始化为0。
  • 状态四,同上分析,dp[0][3]也初始为0
  1. 确定遍历顺序
    dp[i]由dp[i - 1]推导出而来,从前向后遍历

  2. 举例推导dp数组
    输入[1,2,3,0,2]

    最后结果是取状态二,状态三,和状态四的最大值。状态四是冷冻期,最后一天如果是冷冻期也可能是最大值。

  3. C++实现

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int n = prices.size();
        if(n == 0) return 0;
        //0-状态一 持有股票
        //1-状态二 保持卖出 之前的某一天卖出了股票
        //2-状态三 今天卖出
        //3-状态四 冷冻期
        vector<vector<int>> dp(n, vector<int>(4, 0));//状态二三四 都初始化为0
        dp[0][0] = -prices[0];//状态一 持有股票
        for(int i=1; i<n; i++)
        {
            //前一天持有股票 或者前一天是冷冻期/前一天是保持卖出状态
            dp[i][0] = max(dp[i-1][0], max(dp[i-1][3], dp[i-1][1]) - prices[i]);
            //前一天是保持卖出  或者冷冻期
            dp[i][1] = max(dp[i-1][1], dp[i-1][3]);
            //前一天只能是持有股票
            dp[i][2] = dp[i-1][0] + prices[i];
            //前一天只能是卖出
            dp[i][3] = dp[i-1][2];
        }
        return max(dp[n-1][1], max(dp[n-1][2], dp[n-1][3]));
    }
};

714.买卖股票的最佳时机含手续费

在这里插入图片描述
相对于122.买卖股票的最佳时机II,本题只需要在计算卖出操作的时候减去手续费,其他一样,主要区别体现在递推公式

步骤

  1. 确定dp数组以及下标的含义
  • dp[i][0],表示第i天持有股票所得最多现金,之前的某一天买入股票,不一定是今天买入
  • dp[i][1],表示第i天不持有股票所得最多现金
  1. 确定递推公式
  • dp[i][0]可以由两个状态推出来

    • 第i-1天就持有股票,所得现金=昨天持有股票的所得现金,dp[i - 1][0]
    • 第i天买入股票,所得现金=昨天不持有股票的所得现金减去今天的股票价格,dp[i - 1][1] - prices[i]
    • dp[i][0]取所得现金最大的,dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] - prices[i]);
  • dp[i][1]可以由两个状态推出来

    • 第i-1天就不持有股票,所得现金=昨天不持有股票的所得现金,dp[i - 1][1]
    • 第i天卖出股票,所得现金=按照今天股票价格卖出后所得现金,需要给手续费,dp[i - 1][0] + prices[i] - fee
    • dp[i][1]取所得现金最大的,dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] + prices[i] - fee);
  1. dp数组如何初始化
  • dp[0][0]表示第0天持有股票,买入了股票,dp[0][0] = -prices[0];
  • dp[0][1]表示第0天不持有股票,没有买股票,现金为0,dp[0][1] = 0;
  1. 确定遍历顺序
    dp[i]由dp[i - 1]推导出而来,从前向后遍历

  2. C++实现

class Solution {
public:
    int maxProfit(vector<int>& prices, int fee) {
        int len = prices.size();
        vector<vector<int>> dp(len, vector<int>(2, 0));
        dp[0][0] = -prices[0];
        //0-持有股票
        //1-不持有股票
        for(int i=1; i<len; 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] - fee);
        }
        return dp[len-1][1];
        /*//贪心算法
        int result = 0;
        int minprice = prices[0];
        for(int i=1; i<prices.size(); i++)
        {
            //低价买入 买入日期
            if(prices[i] < minprice) minprice = prices[i];

            //不买不卖 买不便宜 卖亏本
            if(prices[i] >= minprice && prices[i] <= minprice+fee) continue;

            //计算利润 最后一天计算利润才是真的卖出日期
            if(prices[i] > minprice+fee)
            {
                result += prices[i] - minprice - fee;
                minprice = prices[i] - fee;//每天要更新最低价格
            }
        }
        return result;*/
    }
};

劫舍系列分别是198题数组上连续元素二选一,213题成环之后连续元素二选一,377题在树上连续元素二选一,所能得到的最大价值。

股票系列分别是从买卖一次到买卖多次,从最多买卖两次到最多买卖k次,从冷冻期再到手续费:121题只能买卖一次,122题可以买卖多次,123题最多买卖两次,188题最多买卖k次,309题买卖多次且卖出一天有冷冻期,714题买卖多次且有手续费。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/668100.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

为摸鱼助力:一份Vue3的生成式ElementPlus表单组件

目录 一、实现背景 二、简介 三、组织架构设计 四、实现方式 五、代码示例 六、示例代码效果预览 七、项目预览地址 & 项目源码地址 目前项目还有诸多待完善的地方&#xff0c;大家有好的想法、建议、意见等欢迎再次评论&#xff0c;或于github提交Issues 一、实现…

杭州市等级保护测评机构名录-2023年

等级保护测评机构并不是一成不变的&#xff0c;因为有年审不符合条件被撤销的&#xff0c;也有符合条件新增的&#xff0c;所以需要不定时查看的。这里小编就给大家汇总了2023年杭州市等级保护测评机构名录。 杭州市等级保护测评机构名录-2023年 序号&#xff1a;1 机构名称…

开源SCRM营销平台MarketGo-营销通道

一、概述 互联网逐步由蓝海市场往红海市场走&#xff0c;互联网增量的红利基本到顶了。营销层面过去要获取新用户&#xff0c;现在需要考虑用户的留存、活跃、复购等&#xff0c;重心从拉新向留存用户的精细化运营转移&#xff1b;当人口红利慢慢消失&#xff0c;成本也在逐渐…

零基础学会Python编程——开发环境的搭建

作者简介&#xff1a;一名云计算网络运维人员、每天分享网络与运维的技术与干货。 座右铭&#xff1a;低头赶路&#xff0c;敬事如仪 个人主页&#xff1a;网络豆的主页​​​​​​ 目录 前言 学习目标 一.python 介绍 1.Python 的历史 2.Python 的应用领域 二.Pytho…

电源开关这个丨和0哪个在上方?

开关这个丨和0哪个在上方&#xff1f; 开关的I或O的位置&#xff0c;根据安装的方向不同而不同。一般情况下上下方向安装时&#xff0c;都是O在上面。而水平安装时则是左O右I。 开关图片 在这种类型的开关中&#xff0c;是将“|”和“O”作为一个电源开闭循环的标示&#xff0…

PtaPython练习

一、3位水仙花数计算 1、题目 3位水仙花数”是指一个三位整数&#xff0c;其各位数字的3次方和等于该数本身。例如&#xff1a;ABC是一个“3位水仙花数”&#xff0c;则&#xff1a;A的3次方&#xff0b;B的3次方&#xff0b;C的3次方 ABC。‪‬‪‬‪‬‪‬‪‬‮‬‪‬‮‬…

《面试1v1》Spring循环依赖

&#x1f345; 作者简介&#xff1a;王哥&#xff0c;CSDN2022博客总榜Top100&#x1f3c6;、博客专家&#x1f4aa; &#x1f345; 技术交流&#xff1a;定期更新Java硬核干货&#xff0c;不定期送书活动 &#x1f345; 王哥多年工作总结&#xff1a;Java学习路线总结&#xf…

Web前端 3D开发入门规划 3D效果将不再是桌面应用的专利

随着 WEB领域的快速发展 3D技术开始不再是桌面应用的专利 WEB3D技术的应用 实现了启用网址的3维呈现 让界面更直观 立体的展示 他打破了传统平面的展示形式 那么 目前的话 政府也有大量的新基建的项目 如 数字孪生 智慧城市 智慧园区 智慧工厂 智慧消费等等项目都涉及到了 3D…

系统磁盘从MBR格式转换成GPT格式来升级win11

之前的《用移动硬盘当系统盘&#xff0c;即插即用》中说到&#xff0c;需要把磁盘格式转化为MBR格式才能执行下去。问题是&#xff0c;win10升级win11要求启动方式为UEFI的话&#xff0c;磁盘格式不能为MBR。其实不升级也不影响啥&#xff0c;但是就是想好看点。所以花了点时间…

推荐系统学习

推荐系统 系统职能&#xff1a;头条/抖音/快手&#xff0c;都是以推荐系统作为流量的分发的主要手段&#xff1b; 职业发展&#xff1a;大数据处理/流式计算/数据挖掘/机器学习/高并发服务等领域。 更具用户的离十信息和行为&#xff0c;向用户推荐他感兴趣的内容 基于行为的…

Modbus TCP 协议详解及C语言示例

Modbus TCP 是一种应用于以太网的通讯协议&#xff0c;基于Modbus RTU协议。Modbus协议是一种应用于串行数据通信的协议&#xff0c;广泛应用于工业控制系统。Modbus TCP 将传统的 Modbus RTU 消息封装在 TCP/IP 报文中&#xff0c;使其能够在现代的以太网环境中进行通信。本文…

VUE 2X MVVM模型 ③

目录 文章有误请指正&#xff0c;如果觉得对你有用&#xff0c;请点三连一波&#xff0c;蟹蟹支持✨ V u e j s Vuejs Vuejs M V V M MVVM MVVM模型Data与El的2种写法总结 文章有误请指正&#xff0c;如果觉得对你有用&#xff0c;请点三连一波&#xff0c;蟹蟹支持✨ ⡖⠒⠒⠒…

【数据分享】1929-2022年全球站点的逐年平均风速(Shp\Excel\12000个站点)

气象数据是在各项研究中都经常使用的数据&#xff0c;气象指标包括气温、风速、降水、能见度等指标&#xff0c;说到气象数据&#xff0c;最详细的气象数据是具体到气象监测站点的数据&#xff01; 对于具体到监测站点的气象数据&#xff0c;之前我们分享过1929-2022年全球气象…

linux-7 awk

目录 1.awk默认规则 2.处理方式 3.格式 4.运算 5.getline 6. 文件内容匹配过滤打印 7.begin . end模式 8.awk条件判断打印 9.awk三元表达 10.awk精准筛选 11.awk数组 1.awk默认规则 当以多空格为分隔符时 自动压缩成一个 默认操作就是打印 默认分隔符时空格 2.处理…

解锁Gradio Interface的便捷与扩展性:load、from_pipeline、integrate和queue方法的魔力

❤️觉得内容不错的话&#xff0c;欢迎点赞收藏加关注&#x1f60a;&#x1f60a;&#x1f60a;&#xff0c;后续会继续输入更多优质内容❤️ &#x1f449;有问题欢迎大家加关注私戳或者评论&#xff08;包括但不限于NLP算法相关&#xff0c;linux学习相关&#xff0c;读研读博…

Day25 实战篇 ——Jmeter实现Java测试实战

Day25 实战篇 ——Jmeter实现Java测试实战 文章目录 Day25 实战篇 ——Jmeter实现Java测试实战一、新建Maven项目二、编写输入参数类、测试类三、编译、打包四、验证开发的函数是否正常1、性能测试过程中,有时候开发想对JAVA代码进行性能测试,Jmeter是支持对Java请求进行性能…

论文解读In-Depth Mouse: Integrating Desktop Mouse into Virtual Reality

In-Depth Mouse: Integrating Desktop Mouse into Virtual Reality HCI2022 honorable ❤️ 将2d的鼠标应用到3d的虚拟空间中&#xff0c;对可选对象进行选择 Challenge 1、如果单纯利用3d虚拟鼠标的3d位置对可选物体进行选择&#xff0c;有可能出现距离更近的物体将虚拟鼠标…

技术讨论:我心中TOP1的编程语言

欢迎关注博主 六月暴雪飞梨花 或加入【六月暴雪飞梨花】一起学习和分享Linux、C、C、Python、Matlab&#xff0c;机器人运动控制、多机器人协作&#xff0c;智能优化算法&#xff0c;滤波估计、多传感器信息融合&#xff0c;机器学习&#xff0c;人工智能等相关领域的知识和技术…

JAVA实现问财爬虫

通过 RestTemplate 将查询语句申请发送至问财&#xff0c;实现同花顺问财的爬虫获取数据&#xff0c;例子中实现了将爬取的数据写入excel文件并染成红色&#xff0c;可将其改造放入数据库中. 通过测试发现爬虫自动能访问一百多次左右&#xff0c;会被官方识别为爬虫&#xff0c…

RocketMQ简介

目录 MQ介绍 MQ的优点和缺点 各种MQ产品的比较 消息发送者步骤分析 消息消费者步骤分析 顺序消息 延时消息 事务消息 1&#xff09;事务消息发送及提交 2&#xff09;事务补偿 3&#xff09;事务消息状态 使用限制 重试队列 重试配置 怎么保证消息消费的时候0丢失…