当前位置: 首页 > news >正文

leetcode_122 买卖股票的最佳时机II

1. 题意

给定一个数组,你可以多次的买入和卖出股票。

你可以在当天买入然后卖出。求最大的获利。

2 题解

还是没有状态这个概念,所以暴力的解法都没有写出来。

主要有两种状态,一是持有股票的最大收益,另一种是不持有股票的最大收益。

当持有股票的时候,你可以卖出股票;而在你没有持有股票的时候,

你可以买入股票。当然你也可以什么也不做。

2.1 暴力
class Solution {
public:void getMaxProfit(const vector<int> &prices, int depth, bool hv_stocks,int cur, int &ans) {if (depth == prices.size()) {ans = max(ans, cur);return ;}getMaxProfit( prices, depth + 1, hv_stocks, cur, ans);if ( hv_stocks ) {getMaxProfit( prices, depth + 1, false, cur + prices[depth], ans);}else {getMaxProfit( prices, depth + 1, true, cur - prices[depth], ans);}}int maxProfit(vector<int>& prices) {int ans = 0;getMaxProfit( prices, 0, false, 0, ans);return ans;}
};
2.2 动态规划

我们定义dp[i][0]dp[i][0]dp[i][0]表示在遍历到iii时手上没有股票的最大获利,

dp[i][1]dp[i][1]dp[i][1]则表示在遍历到iii时手上有股票的最大获利。

那么状态转移方程为

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]}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]\} dp[i][0]=max{dp[i1][0],dp[i1][1]+prices[i]}dp[i][1]=max{dp[i1][1],dp[i1][0]prices[i]}

同时我们需要注意,初始化dp[0][0]=0,dp[0][1]=−prices[0]dp[0][0]=0,dp[0][1] = -prices[0]dp[0][0]=0,dp[0][1]=prices[0]

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

可以优化下空间

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

时间复杂度O(n)O(n)O(n), 空间复杂度O(1)O(1)O(1)

2.3 贪心

自己感觉最难理解的方法,不过写着写着博客突然想到了怎么理解

这个问题了。

其实最根本的是证明连续上升区间的最优性,最终最优的买入卖出的操作区间集合一定是若干个连续上升区间的并集。

为了方便说明,我们用区间来代替一次买入卖出的操作:

[l,r][l,r][l,r]表示在lll处买入而在rrr处卖出。

我们说区间[l,r][l,r][l,r]是一个连续上升区间,

也就是说p[l]<p[l+1]<⋯<p[r]p[l] <p[l+1]<\cdots<p[r]p[l]<p[l+1]<<p[r]

S[l,r]=p[r]−p[l]S_{[l,r]}=p[r]-p[l]S[l,r]=p[r]p[l]来表示一次买入卖出(区间)的收益。

首先我们要有收益,必然有p[r]>p[l]p[r] >p[l]p[r]>p[l]

接下来我们来证明为什么一定要是连续上升区间,我们假设有一个最优的操作区间[l,r][l,r][l,r],它不满足这个连续上升这个性质;

那么它必然存在一个最小的r′,r′<rr', r'<rr,r<r

满足p[r′−1]≥p[r]p[r'-1] \ge p[r]p[r1]p[r]

此时我们不妨把区间[l,r][l,r][l,r]给分成两个区间[l,r′−1]∪[r′,r][l,r'-1]\cup[r',r][l,r1][r,r]

此时这两个区间的收益为

S[l,r′−1]∪[r′,r]=(p[r′−1]−p[l])+(p[r]−p[r′])=(p[r]−p[l])+(p[r′−1]−p[r′])=S[l,r]+(p[r′−1]−p[r′])\begin{align*} S_{[l,r'-1]\cup[r',r]} &=(p[r'-1] -p[l]) +(p[r]-p[r'])\\ &=(p[r]-p[l])+(p[r'-1]-p[r'])\\ &=S_{[l,r]} + (p[r'-1]-p[r']) \end{align*} S[l,r1][r,r]=(p[r1]p[l])+(p[r]p[r])=(p[r]p[l])+(p[r1]p[r])=S[l,r]+(p[r1]p[r])
由于p[r′−1]≥p[r′]p[r'-1]\ge p[r']p[r1]p[r],因此p[r′−1]−p[r′]≥0p[r'-1]-p[r'] \ge 0p[r1]p[r]0;

S[l,r′−1]∪[r′,r]≥S[l,r]S_{[l,r'-1]\cup[r',r]} \ge S_{[l,r]} S[l,r1][r,r]S[l,r]

因此分解为两个区间后的收益是非递减的,有可能会增。

若区间[r′,r][r',r][r,r]依然不是连续上升区间,我们可以继续进行相同的操作使得

分解后收益不减。

这意味着什么呢?

任何一个非连续上升区间都可以分解成若干个连续上升区间使得最终的

收益不减。

S[l,r]≤S[l,r0]∪[l1,r1]∪⋯[lk,r]S_{[l,r]} \le S_{[l,r_0]\cup[l_1,r_1]\cup\cdots[l_k,r]} S[l,r]S[l,r0][l1,r1][lk,r]

至此,我们其实就可以写出代码了。

class Solution {
public:int maxProfit(vector<int>& prices) {int n = prices.size();int ans = 0;int l = 0;for (int i = 1; i < n; ++i) {// 上升连续区间结束了// 上升区间左端点l, 右端点i - 1// [l, i - 1]if ( prices[i] <= prices[i-1]) {ans += prices[i - 1] - prices[l];l = i;}}// 累加最后一个上升连续区间return ans + (prices[n - 1] - prices[l]);}
};

而对于官解中的写法,我更倾向于认为它是一种差分的技巧

S[l,r]=∑i=lr−1S[i,i+1]S_{[l,r]}=\sum_{i=l}^{r-1}S_{[i,i+1]} S[l,r]=i=lr1S[i,i+1]

类似于

p[r]−p[l]=∑i=lr−1p[i+1]−p[i]p[r] -p[l] =\sum_{i=l}^{r-1}p[i+1] -p[i] p[r]p[l]=i=lr1p[i+1]p[i]

它将一个连续上升区间的收益给划分到多个相邻长度为222的上升区间了,

如果这个区间[i,i+1][i,i+1][i,i+1]非上升区间了,说明连续区间结束了在之前基础上增加的收益为000

最终的代码当然是如此的简洁,以至于没做出来的我觉得我是个sb,当然

我确实是个sb。

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

3. 参考

leetcode 122官解

http://www.lryc.cn/news/599448.html

相关文章:

  • Axios基本使用
  • 分别使用 Java 8 和 Python 调用 Elasticsearch 接口简单获取数据
  • Web前端:JavaScript 随机点名系统案例详解
  • 常用设计模式系列(十二)—享元模式
  • OpenTelemetry学习笔记(十二):在APM系统中,属性的命名空间处理遵循规则
  • 基于讯飞星火AI的文学作品赏析系统开发实战:从通用聊天到专业文学分析的完整技术方案
  • 新房装修是中央空调还是壁挂空调好?
  • 滑动窗口---6(稍难)
  • GDB调试命令学习
  • 【开源软件】SimpleAI一款轻量级的桌面随身AI助手
  • 航段导航计算机 (Segment_Navigator) 设计与实现
  • OSPF 协议(多区域)
  • Python智能优化算法实战指南
  • 汪小菲食通达公司成立新零售公司,布局餐饮零售新赛道
  • 轻量级音乐元数据编辑器Metadata Remote
  • SpringBoot整合Liquibase提升数据库变更的可控性、安全性、自动化程度(最详细)
  • 自动化UI测试工具TestComplete的AI双引擎:即时数据集 + 自愈测试
  • SpringBoot学习路径二--Spring Boot自动配置原理深度解析
  • Qt 多媒体开发:音频与视频处理
  • 剪映将绿幕视频扣成透明背景视频转webm格式可以在网页上透明播放
  • 软件工程之可行性研究:从理论到实践的全面解析
  • SpringBoot 集成Mybatis Plus
  • ESLint前端工程实践
  • CMake保姆级教程
  • 力扣1472. 设计浏览器历史记录
  • Execel文档批量替换标签实现方案
  • 三维图像识别中OpenCV、PCL和Open3D结合的主要技术概念、部分示例
  • 【vue3+vue-pdf-embed】实现PDF+图片预览
  • Ubuntu22 上,用C++ gSoap 创建一个简单的webservice
  • 前端学习9:JavaScript--对象与原型