Day49|动态规划part10:188.买卖股票的最佳时机IV、121. 买卖股票的最佳时机、122.买卖股票的最佳时机II

news2025/1/19 23:02:51

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

leetcode链接:188 题「买卖股票的最佳时机 IVopen in new window」

视频链接:动态规划来决定最佳时机,至多可以买卖K次!| LeetCode:188.买卖股票最佳时机4

给你一个整数数组 prices 和一个整数 k ,其中 prices[i] 是某支给定的股票在第 i 天的价格。

设计一个算法来计算你所能获取的最大利润。你最多可以完成 k 笔交易。也就是说,
你最多可以买 k 次,卖 k 次。

注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

示例 1:

输入:k = 2, prices = [2,4,1]
输出:2
解释:在第 1(股票价格 = 2) 的时候买入,在第 2(股票价格 = 4) 的时候卖出,
  这笔交易所能获得利润 = 4-2 = 2 。

示例 2:

输入:k = 2, prices = [3,2,6,5,0,3]
输出:7
解释:在第 2(股票价格 = 2) 的时候买入,在第 3(股票价格 = 6) 的时候卖出, 
  这笔交易所能获得利润 = 6-2 = 4 。
  随后,在第 5(股票价格 = 0) 的时候买入,在第 6(股票价格 = 3) 的时候卖出,
  这笔交易所能获得利润 = 3-0 = 3

接下来的解法来自labuladong的算法 ,从最难的188题入手,限制最多。用状态机的形式进行状态转移。

对于每天的股票,有三种选择:买,卖,不买也不卖。我们用 buy, sell, rest 表示这三种选择。但每次这三种状态不是随便选的:

  • 因为 sell 必须在 buy 之后(对于交易次数设置为无限次时,只能先卖掉手里的再买)
  • buy 必须在 sell 之后(手里有股票了才能卖)。
  • 那么 rest 操作还应该分两种状态,一种是 buy 之后的 rest(持有了股票),一种是 sell 之后的 rest(没有持有股票)。
  • 交易次数 k 的限制,就是说你 buy 还只能在 k > 0 的前提下操作
  1. 确定dp数组的含义及下标

我们的dp数组应该是几维的?这个问题的「状态」有三个,第一个是天数,第二个是允许交易的最大次数,第三个是当前的持有状态(即之前说的 rest 的状态,我们不妨用 1 表示持有,0 表示没有持有)。然后我们用一个三维数组就可以装下这几种状态的全部组合:

dp[i][k][0 or 1]
0 <= i <= n - 1, 1 <= k <= K
n 为天数,大 K 为交易数的上限,01 代表是否持有股票。
此问题共 n × K × 2 种状态,全部穷举就能搞定。

for 0 <= i < n:
    for 1 <= k <= K:
        for s in {0, 1}:
            dp[i][k][s] = max(buy, sell, rest)

比如说 dp[3][2][1] 的含义就是:今天是第三天,我现在手上持有着股票,至今最多进行 2 次交易。再比如 dp[2][3][0] 的含义:今天是第二天,我现在手上没有持有股票,至今最多进行 3 次交易。

我们想求的最终答案就是:dp[n - 1][k][0] ,因为最后一天卖出肯定比不卖出赚的多,因此求的不是dp[n-1][k][1]

  1. 确定递推公式

我们可以画出如下的状态转移图:

image

因此我们可以分两种状态:dp[i][k][0]和dp[i][k][1]

dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + prices[i])
              max( 今天选择 rest,        今天选择 sell       )

解释:今天我没有持有股票,有两种可能,我从这两种可能中求最大利润:

1、我昨天就没有持有,且截至昨天最大交易次数限制为 k;然后我今天选择 rest,所以我今天还是没有持有,最大交易次数限制依然为 k

2、我昨天持有股票,且截至昨天最大交易次数限制为 k;但是今天我 sell 了,所以我今天没有持有股票了,最大交易次数限制依然为 k

dp[i][k][1] = max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i])
              max( 今天选择 rest,         今天选择 buy         )

解释:今天我持有着股票,最大交易次数限制为 k,那么对于昨天来说,有两种可能,我从这两种可能中求最大利润:

1、我昨天就持有着股票,且截至昨天最大交易次数限制为 k;然后今天选择 rest,所以我今天还持有着股票,最大交易次数限制依然为 k

2、我昨天本没有持有,且截至昨天最大交易次数限制为 k - 1;但今天我选择 buy,所以今天我就持有股票了,最大交易次数限制为 k

  • 状态 k 的定义并不是「已进行的交易次数」,而是「最大交易次数的上限限制」。
  • sell 的时候给 k 减小 1 和在 buy 的时候给 k 减小 1 是并不是等效,因为交易是从 buy 开始,如果 buy 的选择不改变交易次数 k 的话,会出现交易次数超出限制的的错误。也就是说买的时候k - 1卖的时候不减。

这是一个反向的过程,是推图的入边而不是出边。

  1. 确定dp初始化
dp[-1][...][0] = 0
解释:因为 i 是从 0 开始的,所以 i = -1 意味着还没有开始,这时候的利润当然是 0。

dp[-1][...][1] = -infinity
解释:还没开始的时候,是不可能持有股票的。
因为我们的算法要求一个最大值,所以初始值设为一个最小值,方便取最大值。

dp[...][0][0] = 0
解释:因为 k 是从 1 开始的,所以 k = 0 意味着根本不允许交易,这时候利润当然是 0。

dp[...][0][1] = -infinity
解释:不允许交易的情况下,是不可能持有股票的。
因为我们的算法要求一个最大值,所以初始值设为一个最小值,方便取最大值。

至于-1 怎么编程表示出来呢,负无穷怎么表示呢?这都是细节问题,有很多方法实现。

  1. 确定遍历顺序

因此天数是从小到大的,因此遍历顺序肯定是从前往后。

  1. 循环打印dp数组

这样我们就确定了买卖股票问题的框架,其他的题目都是这题的特殊情况。

121. 买卖股票的最佳时机

这题就是上面说的只交易一次,就是k = 1的情况。

leetcode链接:力扣题目链接

视频链接::动态规划之 LeetCode:121.买卖股票的最佳时机1

给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。

你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 
卖出该股票。设计一个算法来计算你所能获取的最大利润。

返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。

示例 1:

输入:[7,1,5,3,6,4]
输出:5
解释:在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,
最大利润 = 6-1 = 5 。
     注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。

示例 2:

输入:prices = [7,6,4,3,1]
输出:0
解释:在这种情况下, 没有交易完成, 所以最大利润为 0

暴力解法

首先就是暴力解法,两个for循环,寻找最大的间距(这里注意左边的一定要比右边小)

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int maxValue = 0;
        for(int i = 0; i < prices.size(); i++){
            for(int j = i + 1; j < prices.size(); j++){
                maxValue = max(prices[j] - prices[i],maxValue);
            }
        }
        return maxValue;
    }
};
  • 时间复杂度:O(n^2)
  • 空间复杂度:O(1)

最后直接超时了,直接看动态规划解法。

贪心解法

这题是可以贪心做的,直觉就是取最小的买入,然后result = max(result, prices[i] - low); // 直接取最大区间利润保证利润为正:

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;
    }
};
  • 时间复杂度:O(n)
  • 空间复杂度:O(1)

动态规划

  1. 确定DP数组下标及其含义

因为这题k = 1,因此可以把k那一维简化变为二维数组:

故dp[i][0]/dp[i][1]表示今天是第i天,我手上是否持有股票,的最大利润

  1. 确定dp的递推公式
之前是:
dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + prices[i])
dp[i][k][1] = max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i])

现在去掉了k的维:
dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i])
dp[i][1] = max(dp[i-1][1], -prices[i])

这里为什么是-price[i] ?
-prices[i]只出现在k = 1的情况。因为如果只有一次交易的话,那每天的dp[i][1]状态(第i天手持股票)只会是因为我在当天购买了股票,使得我目前的资本为-prices[I],所以是负数。每天的dp[i][1]状态不会依赖于dp[i-1][0], 也就是当k =1的时候dp[i][1] 不等于dp[i-1][0] - prices[I]。

  1. dp数组的初始化
之前是:dp[-1][...][0] = dp[...][0][0] = 0
dp[-1][...][1] = dp[...][0][1] = -infinity

去掉了k维后:
dp[0][0] = 0;
dp[0][1] = -infinity
  1. 遍历顺序

因为跨度是时间,从前往后。

  1. 打印dp数组

最终代码:

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

还可以不使用dp数组,直接使用2个变量节省空间:

// 空间复杂度优化版本
int maxProfit_k_1(vector<int>& prices) {
    int n = prices.size();
    // base case: dp[-1][0] = 0, dp[-1][1] = -infinity
    int dp_i_0 = 0, dp_i_1 = INT_MIN;
    for (int i = 0; i < n; i++) {
        // dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i])
        dp_i_0 = max(dp_i_0, dp_i_1 + prices[i]);
        // dp[i][1] = max(dp[i-1][1], -prices[i])
        dp_i_1 = max(dp_i_1, -prices[i]);
    }
    return dp_i_0;
}

这个解法了解就行了,先解决一般的解法再说。

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

这题相比k是无限制的。

leetcode链接:「买卖股票的最佳时机 IIopen in new window」

视频链接:动态规划来决定最佳时机,至多可以买卖K次!| LeetCode:188.买卖股票最佳时机4

给你一个整数数组 prices ,其中 prices[i] 表示某支股票第 i 天的价格。

在每一天,你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。
你也可以先购买,然后在 同一天 出售。

返回 你能获得的 最大 利润 。

示例 1:

输入:prices = [7,1,5,3,6,4]
输出:7
解释:在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出,
     这笔交易所能获得利润 = 5 - 1 = 4 。
     随后,在第 4 天(股票价格 = 3)的时候买入,
      在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6 - 3 = 3 。
     总利润为 4 + 3 = 7 。

示例 2:

输入:prices = [1,2,3,4,5]
输出:4
解释:在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5 - 1 = 4 。
     总利润为 4 。
  
示例 3:

输入:prices = [7,6,4,3,1]
输出:0
解释:在这种情况下, 交易无法获得正利润,所以不参与交易可以获得最大利润,最大利润为 0

贪心解法

这题在贪心部分就已经做过了,贪心思想就是当天比前一天大就卖出,最终代码:

class Solution {
public:
    int maxProfit(vector<int>& prices) {
      int result = 0;
      for(int i = 1; i < prices.size(); i++){
          if(prices[i] - prices[i - 1] > 0){
              result += prices[i] - prices[i - 1];
          }
      }
      return result;
    }
};

动态规划

  1. 确定dp数组下标及其含义

这题相当于k为正无穷的情况。如果 k 为正无穷,那么就可以认为 kk - 1 是一样的。因为也就不需要记录k这个状态了, 因此故dp[i][0]/dp[i][1] 表示今天是第i天,我手上是否持有股票,的最大利润

  1. 确定dp递推公式
之前是:
dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + prices[i])
dp[i][k][1] = max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i])
又因为k可以看作和k - 1一样,可以看成:
dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + prices[i])
dp[i][k][1] = max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i])
            = max(dp[i-1][k][1], dp[i-1][k][0] - prices[i])

现在去掉了k的维:
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])

这里就不是-price[i]了,因为k - 1不等于0。

  1. dp数组的初始化:
之前是:
dp[0][0] = 0;
dp[0][1] = -infinity

在代码中表现为;
        dp[0][0] = 0;
        dp[0][1] = -prices[0];
  1. 遍历顺序

从前往后

  1. 打印dp数组

最终代码 :

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        vector<vector<int>> dp (prices.size(),vector<int>(2));
        dp[0][0] = 0;
        dp[0][1] = -prices[0];
        for(int i = 1; i < prices.size(); 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]);//原来是-prices[i]
        }
        return dp[prices.size() - 1][0];
    }
};

这里就改了第9行的递推公式。

空间简化版:

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int n = prices.size();
        // base case: dp[-1][0] = 0, dp[-1][1] = -infinity
        int dp_i_0 = 0, dp_i_1 = INT_MIN;
        for (int i = 0; i < n; i++) {
            // dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i])
            dp_i_0 = max(dp_i_0, dp_i_1 + prices[i]);
            // dp[i][1] = max(dp[i-1][1], dp[i-1][0]-prices[i])
            dp_i_1 = max(dp_i_1, dp_i_0-prices[i]);
        }
        return dp_i_0;
    }
};

总结

  • 买卖股票问题,有的能用贪心解决效率更高,但动态规划是统一方法,必须掌握,贪心不一定能想到
  • 买卖股票问题有很多变种,记住最复杂的:dp[i][k][0],三位数组,其他的都是在上面做加减法。

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

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

相关文章

【考研数学】概率论与数理统计 —— 第二章 | 一维随机变量及其分布(2,常见随机变量及其分布 | 随机变量函数的分布)

文章目录 引言三、常见的随机变量及其分布3.1 常见的离散型随机变量及其分布律&#xff08;一&#xff09;&#xff08;0-1&#xff09;分布&#xff08;二&#xff09;二项分布&#xff08;三&#xff09;泊松分布&#xff08;四&#xff09;几何分布&#xff08;五&#xff0…

利用R作圆环条形图

从理念上看&#xff0c;本质就是增加了圆环弧度的条形图。如上图2。 需要以下步骤&#xff1a; 数据处理&#xff0c;将EXCEL中的数据做成3*N的表格导入系统&#xff0c;代码如下&#xff1a;library(tidyverse) library(stringr)library(ggplot2)library(viridis) stuper &…

电脑连不上网?学会这5个方法,不用愁!

“怎么回事呢&#xff1f;我的电脑一直连不上网。尝试了好多个方法都还是不行。大家遇到这种情况的时候都是怎么处理的呀&#xff1f;” 在现代生活中&#xff0c;电脑连接到网络已经成为必不可少的一部分。如果电脑无法联网&#xff0c;可能会影响我们的工作和娱乐。那么&…

Scrum敏捷研发迭代式开发

Scrum是一个迭代式增量软件开发过程&#xff0c;是敏捷方法论中的重要框架之一。它通常用于敏捷软件开发&#xff0c;包括了一系列实践和预定义角色的过程骨架。Scrum中的主要角色包括Scrum主管&#xff08;Scrum Master&#xff09;、产品负责人&#xff08;Product Owner&…

【已解决】ZooKeeper配置中出现Error contacting service. It is probably not running

ZooKeeper配置中出现Error contacting service. It is probably not running 问题 安装zookeeper&#xff0c;启动报错了 Error contacting service. It is probably not running 思路 tail -100f logs/zookeeper-root-server-node1.itcast.cn.out 查看日志报错 zoo.cfg没…

LinuxShell变量

变量&#xff1a; 命名规则&#xff1a; 在Shell中&#xff0c;变量名可以由字母、数字或者下划线组成&#xff0c;并且只能以字母或者下划线开头。对于变量名的长度&#xff0c;Shell并没有做出明确的规定。因此&#xff0c;用户可以使用任意长度的字符串来作为变量名。但是…

算法:模拟思想算法

文章目录 实现原理算法思路典型例题替换所有问号提莫攻击N字型变换外观序列 总结 本篇总结的是模拟算法 实现原理 模拟算法的实现原理很简单&#xff0c;就是依据题意实现题意的目的即可&#xff0c;考察的是你能不能实现题目题意的代码能力 算法思路 没有很明显的算法思路…

模拟集成电路设计:Bandgap电路设计及版图实现

模拟集成电路设计 Bandgap电路设计及版图实现 一、目的&#xff1a; 1、熟悉模拟集成电路设计的基本流程&#xff0c;实现Bandgap电路设计&#xff1b; 2、熟悉Linux系统及Cadence Virtuoso icfb设计、仿真软件的使用方法。 二、原理&#xff1a; 1、设计目标&#xff1a;…

【HSPCIE仿真】输入网表文件(2)电路拓扑描述

电路拓扑描述 1.元器件描述语句(Elements)电阻(Resistor)电容(Capacitors)电感(Inductors)二极管(Diode)MOSFET 2. 模型(.MODEL)2.1 基本概念2.2 模型的使用.model 描述语句MOSFET的 .model 语句 3. 电源和激励描述语句3.1 独立源直流源梯形脉冲源Pattern Source 4. 库文件的调…

SpringBoot初级开发--加入Swagger展现接口文档(5)

Swagger 是一个规范且完整的框架&#xff0c;用于生成、描述、调用和可视化 RESTful 风格的 Web 服务。作为web开发&#xff0c;它已经成了接口文档服务的代名词了。现在很多协作型项目&#xff0c;都用它生成api文档&#xff0c;让大家能够很好的协作开发。紧接上一章&#xf…

液冷技术之液冷连接器快速接头

文章目录 系列文章目录前言一、pandas是什么&#xff1f;二、使用步骤 1.引入库2.读入数据总结 前言 热能在液冷技术的研发不断加大&#xff0c;特别是在水冷产品生产工艺上不断革新&#xff0c;在铜管自动折弯、挤型模、压管、粘连焊接等技术工艺获得了多个技术专利&#xff0…

php环境搭建步骤(与资源配套使用版)

1.将phpEnv.zip下载到D盘下 2.解压到当前文件夹 3.找到Apache24下的bin目录&#xff0c;执行cmd操作&#xff0c;回车。 4.在cmd中执行代码 Httpd -k install -n “Apache24” 4.使用winR键打开运行&#xff0c;输入services.msc &#xff0c;回车&#xff0c;进入服务 …

Java运行时jar时终端输出的中文日志是乱码

运行Jar时在控制台输出的中文日志全是乱码&#xff0c;这是因为cmd/bash默认的编码是GBK&#xff0c;只要把cmd的编码改成UTF-8即可 两种方式修改&#xff1a;临时修改和注册表永久修改 临时修改 只对当前的cmd页面有效&#xff0c;关闭后重新打开都会恢复成GBK, 打开cmd&am…

“私车公用”如何便捷又合规?百望云解决方案来支招!

“昨天有几家门店反映工作中遇到难题了&#xff0c;领导交代我赶紧去解答一下”。中午11点半&#xff0c;某大型连锁门店的督导李某顾不上吃午饭&#xff0c;开着自己的私家车赶往辖区门店。 去干公事怎么开私家车&#xff1f;面对同行人员的疑问&#xff0c;李某拿出了自己的…

Total Uninstall - 如何将程序从一台计算机传输到另一台计算机

Total Uninstall 可以帮助传输或部署程序 将程序传输到新计算机。将难以找到的程序及其设置移动到新 PC。它可以在多台计算机上安装预先配置了相同设置的程序。使用一组基本的必备程序制作备份副本。重新安装 Windows 时&#xff0c;节省安装和配置应用程序的时间。只需还原备…

超详细FPGA新手小白入门点亮LED灯

Vivado软件的基本操作&#xff08;以控制LED灯闪烁为例&#xff09; 其实之前早已用过Vivado进行FPGA的开发学习&#xff0c;但由于每次都是浅尝辄止地学了一些时间&#xff0c;加上Vivado软件和FPGA开发流程的复杂性&#xff0c;长时间不用就会遗忘。因此今天还是简单地写个笔…

空气净化器十大排名哪个除甲醛强 空气净化器品牌评测

空气净化器十大排名哪个除甲醛强 空气净化器品牌评测 十大空气净化器品牌评测&#xff0c;寻找最强除甲醛功效 房源里无时无刻不承载着我们的生活&#xff0c;我们的家庭在这个小小的空间里扎根&#xff0c;亲人的温暖也凝结其中。然而&#xff0c;室内空气污染却可能悄然渗透&…

Django基础7——用户认证系统、Session管理、CSRF安全防护机制

文章目录 一、用户认证系统二、案例&#xff1a;登陆认证2.1 平台登入2.2 平台登出2.3 login_required装饰器 三、Django Session管理3.1 Django使用Session3.1.1 Cookie用法3.1.2 Session用法 3.2 案例&#xff1a;用户登录认证 四、Django CSRF安全防护机制 一、用户认证系统…

Java-内部类:成员内部类、局部内部类、匿名内部类、静态内部类

文章目录 Java中的内部类一、成员内部类1. 成员内部类是什么&#xff1f;2. 为什么要使用成员内部类&#xff1f;3. 在哪里使用&#xff08;场景&#xff09;4. 优缺点5. 成员内部类示例代码 二、局部内部类1. 是什么&#xff1a;2. 为什么使用3. 在哪里使用4. 优缺点5. 局部内…

喜讯!哲讯科技荣获第八届“创客中国”无锡区域赛优胜奖

第八届“创客中国”暨2023年江苏省中小企业创新创业大赛无锡市区域赛于8月23日在无锡人才金融港路演厅举行。作为全国性扶持中小企业发展的重量级大赛之一&#xff0c;“创客中国”中小企业创新创业大赛一直备受行业瞩目。本次大赛以“围绕产业链、部署创新链、配置资金链、汇聚…