【1.2】动态规划-买卖股票的最佳时机

一、题目

        给定一个数组,它的第i个元素是一支给定的股票在第i天的价格。设计一个算法来计算你所能获取的最大利润。你最多可以完成两笔交易。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

示例 1:
输入:p ri ce s = [ 3 , 3 , 5 , 0 , 0 , 3 , 1 , 4 ]
输出:6
解 释 : 在 第 4 天 ( 股 票 价 格 = 0 ) 的 时 候 买 入 , 在 第 6 天 ( 股 票 价 格 = 3 ) 的 时候卖出,这笔交易所能获得利润 = 3 -0 = 3 。
随后,在第 7 天(股票价格 = 1)的时候买入,在第 8 天 (股票价格 = 4)的时候卖出,这笔交易所能获得利润 = 4 -1 = 3 。


示例 2:
输入:p ri ce s = [ 1 , 2 , 3 , 4 , 5 ]
输出:4
解释:在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出,
这笔交易所能获得利润 = 5 -1 = 4 。
注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。
因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。


示例 3:
输入:p ri ce s = [ 7 , 6 , 4 , 3 , 1 ]
输出:0
解释:在这个情况下, 没有交易完成, 所以最大利润为 0。

示例 4:
输入:p ri ce s = [ 1 ]
输出:0


提示:
1)1 <= prices.length <= 10^5
2)0 <= prices[i] <= 10^5

二、求解思路

        这题让求的是股票最多交易两次的情况下所能获取的最大利润,我们定义dp[i][ j]表示 在j天结束之后最多交易i次所能获得的最大利润。很明显i只能是0,1,2。

表示要么没有交易,要么交易一次,要么交易两次,但不能超过两次,dp的定义如下

int[][] dp = new int[3][prices .length];

在第j天的时候我们可以选择不进行任何交易,那么,当天结束之后的利润也就是前一 天的利润。 在第j天的时候我们可以选择卖出一支股票,那么既然能卖出,说明在之前我们肯定买 过一支股票,那么当天结束之后的利润是

dp[i][j] = prices[j] - prices[k] + dp[i-1][k-1], k=[0..j-1] prices [ j] - prices [k]

表示的是在第k天买入一支股票,然后再第j天把它给卖掉所获 得的利润,其中k的范围是0…j -1。买一次卖一次算一次完整的交易,dp[i -1][k-1]表 示的是在前k-1天最多进行i -1次交易所获得的的最大利润。

所以第j天我们可以选择卖出一支股票也可以选择不进行任何操作,取最大值即可,所以递推公式如下

dp[i][j] = Math.max(dp[i][j-1], prices[j] - prices[k] + dp[i - 1][k - 1]);

算法步骤

  1. 定义状态

    • dp[i][j] 表示在第 j 天结束时,最多进行 i 次交易所能获得的最大利润。

  2. 初始化

    • dp[0][j] 表示在第 j 天结束时,没有进行任何交易的最大利润,显然为 0。

    • dp[i][0] 表示在第 0 天结束时,最多进行 i 次交易的最大利润,显然为 0。

  3. 状态转移方程

    • 对于 dp[i][j],我们有两种选择:

      1. 不进行任何交易,即 dp[i][j] = dp[i][j-1]

      2. 进行一次交易,即在第 k 天买入,在第 j 天卖出,dp[i][j] = prices[j] - prices[k] + dp[i-1][k-1],其中 k 的范围是 0j-1

    • 因此,递推公式为:

      dp[i][j] = Math.max(dp[i][j-1], prices[j] - prices[k] + dp[i-1][k-1]);
  4. 最终结果

    • 最终结果为 dp[2][prices.length - 1],即在最后一天结束时,最多进行两次交易的最大利润。

三、代码实现

C语言实现

#include <stdio.h>
#include <stdlib.h>// 函数用于计算最大利润
int maxProfit(int* prices, int pricesSize) {// 如果价格数组为空,则直接返回0利润if (pricesSize == 0) return 0;// 动态分配内存,创建一个二维数组dp,用于存储中间计算结果int** dp = (int**)malloc(3 * sizeof(int*));for (int i = 0; i < 3; ++i) {dp[i] = (int*)malloc(pricesSize * sizeof(int));// 初始化dp数组的每个元素为0for (int j = 0; j < pricesSize; ++j) {dp[i][j] = 0;}}// 动态规划计算最大利润for (int i = 1; i <= 2; ++i) {int maxDiff = -prices[0]; // 初始化最大差价为第一天的价格的负值for (int j = 1; j < pricesSize; ++j) {// 更新maxDiff为之前的maxDiff和第j天之前进行i-1次交易的最大利润减去第j-1天的价格的较大者maxDiff = (maxDiff > dp[i-1][j-1] - prices[j-1]) ? maxDiff : dp[i-1][j-1] - prices[j-1];// 更新第i次交易第j天的最大利润为前一天的最大利润和第j天价格加上maxDiff的较大者dp[i][j] = (dp[i][j-1] > prices[j] + maxDiff) ? dp[i][j-1] : prices[j] + maxDiff;}}// 结果为完成两次交易后的最后一天的最大利润int result = dp[2][pricesSize-1];// 释放动态分配的内存for (int i = 0; i < 3; ++i) {free(dp[i]);}free(dp);return result;
}// 主函数
int main() {// 给定的股票价格数组int prices[] = {3, 3, 5, 0, 0, 3, 1, 4};// 计算数组的长度int pricesSize = sizeof(prices) / sizeof(prices[0]);// 调用maxProfit函数并打印最大利润printf("Maximum profit: %d\n", maxProfit(prices, pricesSize));return 0;
}

C++代码实现

#include <iostream>
#include <vector>
#include <algorithm>using namespace std;// 函数用于计算最大利润,参数是一个包含股票价格历史的整数向量
int maxProfit(vector<int>& prices) {// 如果价格向量为空,直接返回0利润if (prices.empty()) return 0;int n = prices.size(); // 获取价格向量的大小// 创建一个二维动态数组dp,用于存储中间计算结果,初始化所有值为0vector<vector<int>> dp(3, vector<int>(n, 0));// 动态规划计算最大利润for (int i = 1; i <= 2; ++i) {int maxDiff = -prices[0]; // 初始化最大差价为第一天的价格的负值for (int j = 1; j < n; ++j) {// 更新maxDiff为之前的maxDiff和第j天之前进行i-1次交易的最大利润减去第j-1天的价格的较大者maxDiff = max(maxDiff, dp[i-1][j-1] - prices[j-1]);// 更新第i次交易第j天的最大利润为前一天的最大利润和第j天价格加上maxDiff的较大者dp[i][j] = max(dp[i][j-1], prices[j] + maxDiff);}}// 结果为完成两次交易后的最后一天的最大利润return dp[2][n-1];
}// 主函数
int main() {// 给定的股票价格向量vector<int> prices = {3, 3, 5, 0, 0, 3, 1, 4};// 调用maxProfit函数并打印最大利润cout << "Maximum profit: " << maxProfit(prices) << endl;return 0;
}
  1. 初始化

    • dp 是一个二维数组,dp[i][j] 表示在第 j 天结束时,最多进行 i 次交易所能获得的最大利润。

    • maxDiff 用于记录在第 j 天之前,最多进行 i-1 次交易的最大利润减去第 j-1 天的价格。

  2. 状态转移

    • 对于每个 i(1 和 2),我们计算 maxDiff 并更新 dp[i][j]

    • maxDiff 的更新公式是 maxDiff = max(maxDiff, dp[i-1][j-1] - prices[j-1])

    • dp[i][j] 的更新公式是 dp[i][j] = max(dp[i][j-1], prices[j] + maxDiff)

  3. 返回结果

    • 最终结果是 dp[2][n-1],即在最后一天结束时,最多进行两次交易的最大利润。

四、另外解法

        动态规划相关的题怎么解,主要看状态的定义。上面我们定义的是二维数组,这里我们还可以定义一个三维数组。
        定义dp[i][ j][k]表示在第i天交易结束后,最多进行j次交易所获得的最大利润。注意这里的k要么是0,要么是1。0表示手里没有股票,1表示手里有一支股票。

dp[i][ j][0]:表示第i天交易结束之后,最多进行j次交易,并且手里没有股票的最大利润。
dp[i][ j][1]:表示第i天交易结束之后,最多进行j次交易,并且手里持有股票的最大利润。

那么在当天结束之后我们会有6种状态
1、没有进行过任何交易,利润永远为0
 dp[i][0][0]=0

2、卖出过一次股票(完成一次交易),但目前手上没有股票:可能是今天卖出,也可
能是之前卖的(注意:买一次卖一次才能算一次完整的交易)
dp[i][1][0]=max(dp[i-1][0][1]+prices[i],dp[i-1][1][0])

3、卖出过两次股票(完成两次交易),但目前手上没有股票:也可能是今天卖的,也可能是之前卖的。
dp[i][2][0]=max(dp[i-1][1][1]+prices[i],dp[i-1][2][0])

4、没有卖出过任何股票,但目前手上持有股票:可能是今天持有的,也可能是之前持有的
dp[i][0][1]=max(dp[i-1][0][0]-prices[i],dp[i-1][0][1])

5,卖出过一次股票(完成一次交易),但目前手上持有股票:可能是今天持有的,也
可能是之前持有的
dp[i][1][1]=max(dp[i-1][1][0]-prices[i],dp[i-1][1][1])

6,卖出过两次次股票(完成两次交易),但目前手上持有股票:由于最多交易2次,
这种情况是无效的
dp[i][2][1]:无效

        有了上面的递推公式,我们再来看一下base case,第一天的时候我们要么买一支股
票,要么什么也不做。

C++代码实现

#include <vector>
#include <algorithm>
#include <climits>// 定义一个名为Solution的类
class Solution {
public:// 类的公有成员函数,用于计算最大利润int maxProfit(std::vector<int>& prices) {int n = prices.size(); // 获取价格数组的长度// 如果数组为空,则返回0if (n == 0) return 0;// 使用三维向量dp来保存动态规划的状态// dp[i][j][k]代表第i天进行j次交易,并且是否持有股票(k为1表示持有,0表示不持有)std::vector<std::vector<std::vector<int>>> dp(n, std::vector<std::vector<int>>(3, std::vector<int>(2, 0)));// 初始化第一天的状态dp[0][0][0] = 0; // 第一天没有进行任何交易,也没有持有股票dp[0][0][1] = -prices[0]; // 第一天买入股票// 将不可能的情况初始化为一个很小的值,表示这些状态不会被选择dp[0][1][0] = INT_MIN / 2; // 第一天不可能已经卖出股票dp[0][1][1] = INT_MIN / 2; // 第一天不可能已经进行了一次买入和一次卖出dp[0][2][0] = INT_MIN / 2; // 第一天不可能已经进行了两次卖出dp[0][2][1] = INT_MIN / 2; // 第一天不可能已经进行了两次买入和卖出// 遍历每一天,更新dp数组for (int i = 1; i < n; ++i) {// 更新当天持有股票的状态,考虑前一天已经持有或者当天买入dp[i][0][1] = std::max(dp[i - 1][0][0] - prices[i], dp[i - 1][0][1]);// 更新当天不持有股票并且进行了一次交易的状态,考虑前一天持有股票并在当天卖出或者前一天已经进行了一次交易dp[i][1][0] = std::max(dp[i - 1][0][1] + prices[i], dp[i - 1][1][0]);// 更新当天持有股票并且进行了一次交易的状态,考虑前一天已经进行了一次交易并在当天买入或者前一天持有股票并且进行了一次交易dp[i][1][1] = std::max(dp[i - 1][1][0] - prices[i], dp[i - 1][1][1]);// 更新当天不持有股票并且进行了两次交易的状态,考虑前一天持有股票并在当天卖出或者前一天已经进行了两次交易dp[i][2][0] = std::max(dp[i - 1][1][1] + prices[i], dp[i - 1][2][0]);// dp[i][2][1] 是不可能的状态,因为我们只能进行两次交易,所以不需要更新}// 返回最终的最大利润,可能是没有进行交易、进行了一次交易或者进行了两次交易return std::max({dp[n - 1][0][0], dp[n - 1][1][0], dp[n - 1][2][0]});}
};
上面的三维数组我们还可以使用4个变量来表示,下面有详细注释,具体可以看下

#include <vector>
#include <algorithm>
#include <climits>// 定义一个名为 Solution 的类,用于解决最大利润问题
class Solution {
public:// 定义一个函数 maxProfit,它接受一个整数类型的 vector 引用,表示每天的股票价格int maxProfit(std::vector<int>& prices) {// 初始化第一次买入股票的最大利润为 INT_MIN / 2,以避免溢出int buy1 = INT_MIN / 2;// 初始化第一次卖出股票的最大利润为 0int sell1 = 0;// 初始化第二次买入股票的最大利润为 INT_MIN / 2,以避免溢出int buy2 = INT_MIN / 2;// 初始化第二次卖出股票的最大利润为 0int sell2 = 0;// 遍历每一天的股票价格for (int price : prices) {// 更新第二次卖出的最大利润,取决于在这一天卖出或保持之前的卖出状态sell2 = std::max(buy2 + price, sell2);// 更新第二次买入的最大利润,取决于在这一天买入或保持之前的买入状态buy2 = std::max(sell1 - price, buy2);// 更新第一次卖出的最大利润,取决于在这一天卖出或保持之前的卖出状态sell1 = std::max(buy1 + price, sell1);// 更新第一次买入的最大利润,取决于在这一天买入或保持之前的买入状态buy1 = std::max(-price, buy1);}// 最终返回的是最大利润,可能是没有交易,进行了一次交易,或者进行了两次交易的最大值return std::max(0, std::max(sell1, sell2));}
};

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

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

相关文章

运用sessionId redis中获取用户的信息rg.springframework.session.SessionRepository

这个类是 org.springframework.session.SessionRepository 接口&#xff0c;它定义了操作 Session 的基本方法。SessionRepository 是 Spring Session 框架的一部分&#xff0c;用于管理和存储用户的 HTTP Session。下面是这个接口的详细解释&#xff1a; SessionRepository&l…

Sentinel-1 Level 1数据处理的详细算法定义(二)

《Sentinel-1 Level 1数据处理的详细算法定义》文档定义和描述了Sentinel-1实现的Level 1处理算法和方程&#xff0c;以便生成Level 1产品。这些算法适用于Sentinel-1的Stripmap、Interferometric Wide-swath (IW)、Extra-wide-swath (EW)和Wave模式。 今天介绍的内容如下&…

C++:CV::Point函数简介

cv::Point 是 OpenCV 库中用于表示二维坐标系下点的一个基本数据类型。它提供了在图像处理、计算机视觉等领域中定位和操作点的能力。以下是关于 cv::Point 的详细介绍&#xff1a; 基本属性 类型&#xff1a;cv::Point 是一个模板类&#xff0c;通常用于处理整数坐标&#x…

解决打印PDF文本不清楚的处理办法

之前打印PDF格式的电子书&#xff0c;不清晰&#xff0c;影响看书的心情&#xff0c;有时看到打印的书的质量&#xff0c;根本不想看&#xff0c;今天在打印一本页数不多&#xff0c;但PDF格式的书感觉也不太清楚&#xff0c;我想应该有办法解决&#xff0c;我使用的是解决福昕…

2017年,我成为了技术博主

2017年9月&#xff0c;我已经大三了。 >>上一篇&#xff08;爪哇&#xff0c;我初窥门径&#xff09; 我大二学了很多java技术&#xff0c;看似我一会就把javaweb/ssh/ssm这些技术栈给学了。 这些技术确实不难&#xff0c;即便是我&#xff0c;我都能学会&#xff0c;…

可以添加todo清单桌面小组件的便签哪个好?

在我们快节奏的生活中&#xff0c;有效的时间管理和任务追踪是必不可少的。为了实现这一目标&#xff0c;许多人选择使用桌面便签&#xff0c;尤其是那些具有Todo清单桌面小组件的便签。但是&#xff0c;面对市场上众多选择&#xff0c;可以添加todo清单桌面小组件的便签哪个好…

C++八股(四)之STL

目录 一、讲一下C++的STL 二、vector list异同⭐⭐ 三、vector的底层实现⭐⭐ 四、vector和deque的区别 ⭐⭐ 五、deque和queue的区别⭐⭐ 六、为什么list里面还要再定义一个sort函数⭐ 七、STL底层数据结构实现⭐ 八、利用迭代器删除元素会发生什么?⭐⭐⭐⭐ 九、map…

14 Portainer轻量级图形化监控

目录 Portainer&#xff1a;Docker轻量级可视化工具 1. 安装与访问 2. 使用 3. Portainer配置 nginx Portainer&#xff1a;Docker轻量级可视化工具 Portainer是一款轻量级的应用&#xff0c;它提供了图形化界面&#xff0c;用于方便地管理Docker环境&#xff0c;包括单机…

db期末复习自用[应试向 附习题]

第一章 数据库系统实现整体数据的结构化&#xff0c;主要特征之一&#xff0c;是db区别于文件系统的本质区别。 数据库系统三个阶段&#xff1a;人工、文件、数据库系统。 数据库管理系统的功能&#xff1a;数据库定义、操纵 、&#xff08;保护、存储、维护&#xff09;、数…

Jmeter在信息头中设置Bearer与 token 的拼接值

思路&#xff1a;先获取token&#xff0c;将token设置成全局变量&#xff0c;再与Bearer拼接。 第一步&#xff1a;使用提取器将token值提取出来&#xff0c;使用setProperty函数将提取的token值设置成全局变量&#xff0c;在登录请求后面添加BeanShell取样器 或者 BeanShell后…

数据结构作业/2024/7/9

2>实现双向循环链表的创建、判空、尾插、遍历、尾删、销毁 fun.c #include "head.h" //1.双向循环链表的创建 doubleloop_ptr create_list() …

STM32实战篇:按键控制LED

按键控制LED 功能要求 有两个按键&#xff0c;分别控制两个LED灯。当按键按下后&#xff0c;灯的亮暗状态改变。实物如下图所示&#xff1a; 由图可知&#xff0c;按键一端直接接地&#xff0c;故另一端所对应IO引脚的输入模式应该为上拉输入模式。 实现代码 #include "…

《C++20设计模式》中单例模式

文章目录 一、前言二、饿汉式1、实现 三、懒汉式1、实现 四、最后 一、前言 单例模式定义&#xff1a; 单例模式&#xff08;Singleton Pattern&#xff09;是一种创建型设计模式&#xff0c;其主要目的是确保一个类只有一个实例&#xff0c;并提供全局访问点来访问这个实例。…

mysql实战入门-基础篇

目录 1、MySQL概述 1.1、数据库相关概念 1.2、MySQL数据库 1.2.1、版本 1.2.2、下载 1.2.3、安装 输入MySQL中root用户的密码,一定记得记住该密码 1.2.4、启动停止 1.2.5、客户端连接 1.2.6、数据模型 2、SQL 2.1、SQL通用语法 2.2、SQL分类 2.3、DDL 2.3.1、数据…

GNU/Linux - Linux Kernel Device model

Linux 设备模型是 Linux 内核中的一个框架&#xff0c;它提供了一种统一、一致的方式来管理和表示硬件设备。设备模型抽象了硬件的细节&#xff0c;使得开发和维护驱动程序和子系统变得更加容易。以下是 Linux 设备模型的关键组成部分和概念&#xff1a; 关键组成部分 设备 (D…

算力感知网络系统架构模型、任务模型、 通信模型、计算和存储资源模型

目录 移动边缘计算——计算卸载 计算卸载 算力感知网络系统架构模型、任务模型、 通信模型、计算和存储资源模型 算力感知路由和算力资源分配 香农定理 1. 通信系统设计与优化 2. 数据压缩 3. 杂音抵消 4. 信道容量评估 香农公式计算 计算步骤 举例说明 传输信号的…

金融电商社交媒体等领域的大数据应用案例

大数据在各个行业都有广泛的应用&#xff0c;以下是几个典型领域的例子&#xff1a; 金融领域&#xff1a; 大数据帮助金融机构进行风险评估和信贷决策&#xff0c;通过收集和分析客户的交易历史、信用记录等信息&#xff0c;预测贷款违约率。例如&#xff0c;信用卡公司可以…

PHP验证日本免费电话号码格式

首先&#xff0c;您需要了解免费电话号码的格式。 日本免费电话也就那么几个号段&#xff1a;0120、0990、0180、0570、0800等开头的&#xff0c;0800稍微特殊点&#xff0c;在手机号里面有080 开头&#xff0c;但是后面不一样了。 关于免费电话号码的划分&#xff0c;全部写…

【前端从入门到精通:第十二课: JS运算符及分支结构】

JavaScript运算符 算数运算符 关于自增自减运算 自增或者自减运算就是在本身的基础上进行1或者-1的操作 自增或者自减运算符可以在变量前也可以在变量后&#xff0c;但是意义不同 自增自减运算符如果在变量前&#xff0c;是先进行自增或者自减运算&#xff0c;在将变量给别人用…

Python面试题:请解释什么是反射(reflection)?

在计算机科学中&#xff0c;反射&#xff08;reflection&#xff09;是指程序在运行时检查、修改和调用自身结构的能力。这种能力允许程序在运行时动态地获取有关其自身的信息&#xff08;如类、方法、属性等&#xff09;&#xff0c;并进行操作。反射通常用于创建灵活且可扩展…