2021新年算法小专题—2.股票买卖利润刷题(Java)
本篇是股票买卖问题的更多题解,在上一篇文章中我们已经介绍了这一题型,实际上是一类dp
问题,我们用自动机的思想去解决,在上一篇中我们以一道限定只买卖一次股票的题目为例进行了讲解,文章链接。下面我们继续完成剩下的题目。
1.可以多次买卖的股票买卖问题
本题链接:122.买卖股票的最佳时机II(简单)
题目
给定一个数组,它的第 i
个元素是一支给定股票第 i
天的价格。设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。
注意: 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
示例 :
输入: [7,1,5,3,6,4]
输出: 7
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。
分析
和上一篇题目的唯一区别就是不再限制只允许买一次,因此我们只需改变一个位置即可。回忆上一篇的题目,我们是这样递推的:
dp0=Math.max(dp0,dp1+prices[i]);dp1=Math.max(dp1,-prices[i]);
dp0
代表今天不持有股票获得的最大利润,dp1
代表今天持有股票获得的最大利润。我们是这样总结出上面的状态转移方程的:今天不持有股票,对应两种情况:前一天就不持有股票、前一天持有股票但今天卖出了,在这两种情况中取最大值,即max(dp0,dp1+prices[i])
。今天持有股票也对应两种情况:前一天就持有股票、前一天不持有股票但是今天买入了,在这两种情况中取最大值,即max(dp1,-prices[i])
。
在上题中,由于只能买卖一次,因此在今天持有股票的“前一天不持有但今天买入”情况下只需要计算本次买入股票的花费即可,跟之前的状态无关(之前不可能买卖过股票,也就是一直在观望,利润没有发生变化),而本题则不再是这样,在本次购买之前可能已经发生过买卖了,这就是与例题唯一的不同之处。我们只需要更改这个位置为以下代码即可:
int dp0_tmp=dp0;// 记录上一次不持有股票的情况。dp0=Math.max(dp0,dp1+prices[i]);dp1=Math.max(dp1,dp0_tmp-prices[i]);
参考代码
class Solution {public int maxProfit(int[] prices) {int dp0=0;int dp1=-prices[0];for(int i=1;i<prices.length;i++){int tmp=dp0;dp0=Math.max(dp0,dp1+prices[i]);dp1=Math.max(dp1,tmp-prices[i]);}return dp0;}
}
2.限制最多买卖两次的股票买卖问题
本题链接:123.买卖股票的最佳时机III(困难)
题目
给定一个数组,它的第 i
个元素是一支给定的股票在第 i
天的价格。
设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。
**注意:**你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
示例 :
输入:prices = [3,3,5,0,0,3,1,4]
输出:6
解释:在第 4 天(股票价格 = 0)的时候买入,在第 6 天(股票价格 = 3)的时候卖出,这笔交易所能获得利润 = 3-0 = 3 。随后,在第 7 天(股票价格 = 1)的时候买入,在第 8 天 (股票价格 = 4)的时候卖出,这笔交易所能获得利润 = 4-1 = 3 。
分析
现在要求只能完成两笔交易了,这就跟以前大不相同了,以前我们要么是只能买一次,要么是可以买无限次,而现在限制某一个具体数字了,我们就不得不把这个次数也考虑进状态转移方程了。我们在原来的dp
数组中再加入一个维度,记录当前的已经买卖次数。
因为现在
k
也是一个限制我们买卖股票的因素了,因为当你买卖超过两次后就不能再买入了,所以要把这个状态也算进去,天数、当前允许交易的次数、当前股票的持有状态这三个变化的因素我们都要考虑进去,所以使用**三维数组dp
**来记录。(当然,后面我们会优化成几个变量的形式,现在这样说只是为了更好理解)
举例:dp[i][k][0]
表示第i
天时不持有股票,最多还能进行k
次交易,(这里买入并卖出看作一次交易,如果买入了则必然要卖出,所以我们以买入时对k
做减一操作,卖出时就不用重复考虑了)
本题中k是从2开始取,因此这个维度只会出现2、1两个值(为0时则代表什么都不能做了,不会对获利产生影响,因此省略不写即可),我们的状态转移方程如下:
dp[i][2][0] = max(dp[i-1][2][0], dp[i-1][2][1] + prices[i]);dp[i][2][1] = max(dp[i-1][2][1], dp[i-1][1][0] - prices[i]);dp[i][1][0] = max(dp[i-1][1][0], dp[i-1][1][1] + prices[i]);dp[i][1][1] = max(dp[i-1][1][1], -prices[i]);
前面已经说明了,我们在买入股票时做k
的更新,因此dp[i][2][1]
即“今天持有股票,还能最多买两次股票”
这个状态下的最大利润,在“前一天就持有股票”
和“前一天未持有股票,但今天买入了股票(做k-1,k变为1)”
这两个状态下利润较大值中产生。
和上一篇介绍的一样,我们到这里就发现当前每个状态事实上只与上一天的状态相关,我们没有必要用数组去记录完整的数据,我们只需要变量做缓存、每次更新递推求解即可。最终代码如下。
参考代码
class Solution {public int maxProfit(int[] prices) {// 初始值int dp2_0=0;// 当前不持有股票,利润是0int dp1_0=0;int dp2_1=-prices[0];// 当前持有了股票,利润是-prices[0]int dp1_1=-prices[0];for(int i=1;i<prices.length;i++){dp2_0=Math.max(dp2_0,dp2_1+prices[i]);// 可以rest或卖出(卖出后加股票价格的利润)dp2_1=Math.max(dp2_1,dp1_0-prices[i]);// 可以rest或买入(买入k-1,减股票价格的花费)dp1_0=Math.max(dp1_0,dp1_1+prices[i]);// 可以rest或卖出(卖出后加股票价格的利润)dp1_1=Math.max(dp1_1,-prices[i]);// 可以rest或买入(注意这时k=1说明只能买入这一次,因此既然本次要买入,在本次购买前不可能发生过交易,所以本次利润直接等于股票价格的花费)}return dp2_0;}
}
3.可以买卖k次的股票买卖问题
本题链接:188.买卖股票的最佳时机IV(困难)
题目
给定一个整数数组 prices
,它的第 i
个元素 prices[i]
是一支给定的股票在第 i
天的价格。设计一个算法来计算你所能获取的最大利润。你最多可以完成 k
笔交易。(0 <= k <= 100
)
**注意:**你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
示例:
输入:k = 2, prices = [2,4,1]
输出:2
解释:在第 1 天 (股票价格 = 2) 的时候买入,在第 2 天 (股票价格 = 4) 的时候卖出,这笔交易所能获得利润 = 4-2 = 2 。
分析
这是这个系列中最难的一道了,在本题中我们基于上一道题k=2
的情况,拓展到**k
为任意值**的情况,因为k
可以有很多值,使得我们不能通过变量代替数组了,而且也要考虑以下k
本身的合理范围,如果题目的prices
数组长度只有10
,即十天,k
的值为100
,我们显然根本不能交易这么多次,更不能为此开辟一个100
的数组空间,所以我们要做一些限制,然后要针对每个k
值都进行一次dp
数组的递推。
像上面所说,我们先来思考一下k
的合理性如何判断。对于一个长度为len
的prices
交易天数数组,每次交易(买入、卖出)需要花费两天时间,每天最多只能进行买入、卖出中的一种,因此对于len
天来说,最多只能完成len/2
笔交易,如果给的k
值大于len/2
,那么就相当于无限次买卖了,退化成本篇第一题的情况了。对这种情况我们单独按第一题那样处理。
对于其他情况我们就只好乖乖的使用dp
数组了:(因为我们的k
这个维度是从下标1
开始用的,因此new
数组时我们把长度指定为k+1
)
int[][][] dp = new int[n][k + 1][2];
因为k
的值是任意的,我们不能直接把k=1、2
这样列举出来了,使用for
循环来遍历k
为不同值的情况:
for (int k = max_k; k >= 1; k--) {dp[i][k][0] = Math.max(dp[i-1][k][0], dp[i-1][k][1] + prices[i]);dp[i][k][1] = Math.max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i]); }
将k
从给出的值(为区分,用max_k
代表给出的值,即最大允许的交易次数)递减循环,根据每天(第i天)
的不同已经买卖次数k
进行递推。
当然我们也要处理一下第一天的初始状态,无论k
为何值,第一天未持有股票一定利润为0
、持有股票一定利润为第一天股票价格的相反数
。即:dp[i][k][0] =0;
dp[i][k][1] =-prices[i];
最值产生在最后一天最多允许k
次交易后的不持有股票状态。为什么不是持有股票状态?前面已经说过了,如果你手里有股票,在最后一天也不抛售出去就无法赚到钱,肯定是要卖出去的。
参考代码
class Solution {public int maxProfit(int max_k, int[] prices) {int len;if((len=prices.length)==0){return 0;}// k值大于len/2,相当于无限制if(max_k>len/2){int dp0=0;int dp1=-prices[0];for(int i=1;i<prices.length;i++){int tmp=dp0;dp0=Math.max(dp0,dp1+prices[i]);dp1=Math.max(dp1,dp0-prices[i]);}return dp0;}// 其他情况,使用三维数组记录天数、允许交易次数、持有状态int[][][] dp = new int[len][max_k + 1][2];for (int k = max_k; k >= 1; k--) {if (i == 0) { // 第一天时的初始值dp[i][k][0] =0;dp[i][k][1] =-prices[i];continue;}dp[i][k][0] = Math.max(dp[i-1][k][0], dp[i-1][k][1] + prices[i]);dp[i][k][1] = Math.max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i]); }return dp[len - 1][max_k][0];}
}
4.含冷冻期的股票买卖问题
本题链接:309.最佳买卖股票时机含冷冻期(中等)
题目
给定一个整数数组,其中第 i
个元素代表了第 i
天的股票价格 。设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):
你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天
)。
示例:
输入: [1,2,3,0,2]
输出: 3
解释: 对应的交易状态为: [买入, 卖出, 冷冻期, 买入, 卖出]
分析
这个题比较简单,相当于是在无限制交易的基础上多加了一个“冷冻期”的概念,即卖出股票后必须休息一天才能买入,我们只需根据这个特点对状态转移方程进行一下修改即可。回想我们前面第一题的状态转移方程:
int dp0_tmp=dp0;// 记录上一次不持有股票的情况。
dp0=Math.max(dp0,dp1+prices[i]);
dp1=Math.max(dp1,dp0_tmp-prices[i]);
dp1
的解释是:如果当天持有股票,可以是前一天就持有股票,今天休息了;也可以是前一天
没有股票,今天买入了。而在本题中,买入要和卖出间隔一天,因此第二句话要改为前两天
没有股票,今天买入了,即max(dp1,tmp-prices[i])
(用tmp
代表前二天的最大利润,当前是第i
天,前二天即第i-2
天)。然后在循环前指定一下第i-2
天的初值:0
,每次在循环中更新为dp0_tmp
的值,这样就把前二天这个事给表示出来了。
可能说的有点乱,稍微总结一下:本题关键就是把第i-2
天即tmp
的最大利润正确的更新。借助dp0_tmp
每次记录dp0
的旧值(得到第i-1
天的最大利润), tmp
每次记录dp0_tmp
的旧值(得到第i-2天的最大利润)。
参考代码
class Solution {public int maxProfit(int[] prices) {if(prices.length==0){return 0;}int dp0=0,dp1=-prices[0],tmp=0;// dp0代表第i天不持有股票,dp1代表第i天持有股票,tmp代表第i-2天不持有股票// 关键则是把tmp正确的更新 借助dp0_tmp每次记录dp0的旧值(第i-1天) tmp每次记录dp0_tmp的旧值(第i-2天)for(int i=1;i<prices.length;i++){int dp0_tmp=dp0;dp0=Math.max(dp0,dp1+prices[i]);dp1=Math.max(dp1,tmp-prices[i]);tmp=dp0_tmp;}return dp0;}
}
5.含手续费的股票买卖问题
本题链接:714.买卖股票的最佳实际含手续费(中等)
题目
给定一个整数数组 prices
,其中第 i
个元素代表了第 i
天的股票价格 ;非负整数 fee
代表了交易股票的手续费用。
你可以无限次地完成交易,但是你每笔交易都需要付手续费。如果你已经购买了一个股票,在卖出它之前你就不能再继续购买股票了。返回获得利润的最大值。
注意:这里的一笔交易指买入持有并卖出股票的整个过程,每笔交易你只需要为支付一次手续费。
示例 :
输入: prices = [1, 3, 2, 8, 4, 9], fee = 2
输出: 8
解释: 能够达到的最大利润:
在此处买入 prices[0] = 1
在此处卖出 prices[3] = 8
在此处买入 prices[4] = 4
在此处卖出 prices[5] = 9
总利润: ((8 - 1) - 2) + ((9 - 4) - 2) = 8.
分析
仔细阅读题目我们其实发现,这道题和不限制交易次数没有任何转移方程上的变化,只是说每次交易多交纳一些费用,我们可以等价的看成每笔买入费用增加了fee
,或者是每次卖出时少卖fee
,这里选择第一种。即每次买入时都需要花费prices[i]+fee
。
参考代码
class Solution {public int maxProfit(int[] prices, int fee) {int dp0=0;int dp1=-prices[0]-fee;// 多交手续费for(int i=1;i<prices.length;i++){int dp0_tmp=dp0;dp0=Math.max(dp0,dp1+prices[i]);dp1=Math.max(dp1,dp0_tmp-prices[i]-fee);// 多交手续费}return dp0;}
}
刷题总结
到此我们已经刷完了力扣股票买卖问题了,这类dp
问题的核心其实就是找到状态、然后写出状态转移方程进行递推求解。最后再总结一下我们的dp
数组的含义:
dp[i][k][0]
:第i
天,当前最多允许交易k
次,现在手上没有股票;
dp[i][k][1]
:第i
天,当前最多允许交易k
次,现在手上持有股票;
当k
为1
或无穷大时,我们可以直接省略掉k
,当k
为2
时也可以把k=1
和k=2
简单列举出来,这些情况都可以省去数组的空间,使用不同名称的变量即可,以第二题为例:
dp2_0=Math.max(dp2_0,dp2_1+prices[i])
dp2_1=Math.max(dp2_1,dp1_0-prices[i])
dp1_0=Math.max(dp1_0,dp1_1+prices[i])
dp1_1=Math.max(dp1_1,-prices[i])
而当k
为任意值时就必须使用for
循环去在每一天的循环中更新dp
数组了。如第三题:
for (int k = max_k; k >= 1; k--) {dp[i][k][0] = Math.max(dp[i-1][k][0], dp[i-1][k][1] + prices[i]);dp[i][k][1] = Math.max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i]); }
找好状态转移方程后,我们还要明确题目要求的最大利润在哪里产生。既然我们用自动机状态的方式去解决了,最值一定在最后一天的某个状态(终态)下产生,而且最后卖出股票才能获得股票具有的价值,因此持有状态应该是不持有。即dp[prices.length-1][k][0]
。
更多文章:
新年算法小专题1.滑动窗口(Java)
新年算法小专题1.滑动窗口刷题(Java)
新年算法小专题2.股票买卖(Java)
你的喜欢是我创作的动力,喜欢请关注,感谢每一个喜欢~
如有问题欢迎进行交流~
水平所限,如有错误请海涵,欢迎指正~
2021新年算法小专题—2.股票买卖利润刷题(Java)相关推荐
- 2021新年算法小专题—2.股票买卖利润(Java)
概述 熟悉这类题目的同学都知道,事实上这是一类动态规划(dp)问题,在小专题中都暂且只针对这一类问题进行解决了,更全面的动态规划文章以后有机会再努力吧! 简单介绍一句动态规划.动态规划实际是一种分治思 ...
- 2021最新 MySQL面试题精选(附刷题小程序)
推荐使用小程序阅读 为了能让您更加方便的阅读 本文所有的面试题目均已整理至小程序<面试手册> 可以通过微信扫描(或长按)下图的二维码享受更好的阅读体验! 最近梳理汇总了Java面试常遇到的 ...
- leetcode刷题java、c++、go语言三合一版本 谷歌师兄的刷题笔记、东哥的算法小抄、 Guide哥的Java面试突击版
不久前火爆 GitHub 的 LeetCode 中文刷题手册,分享给大家,让正在找工作的朋友能够快速找到心仪的offer! <LeetCode Cookbook>目前已经收录了 520 道 ...
- 2021最新 SpringBoot面试题精选(附刷题小程序)
推荐使用小程序阅读 为了能让您更加方便的阅读 本文所有的面试题目均已整理至小程序<面试手册> 可以通过微信扫描(或长按)下图的二维码享受更好的阅读体验! 文章目录 推荐使用小程序阅读 1. ...
- 一个算法笨蛋的12月leetCode刷题日记
类似文章 一个算法笨蛋的2021年11月leetCode刷题日记 一个算法笨蛋的2021年12月leetCode刷题日记 一个算法笨蛋的2022年1月leetCode刷题日记 一个算法笨蛋的2022年 ...
- 算法记录 牛客网 leetcode刷题记录
算法记录 & 牛客网 & leetcode刷题记录 解题思路 STL容器 常用算法模板 堆排序 插入排序 快速排序 BFS层序遍历 二叉树 JZ55 二叉树的深度 BST(binary ...
- 小何同学的leetcode刷题笔记 基础篇(01)整数反转
小何同学的leetcode刷题笔记 基础篇(01)整数反转[07] *** [01]数学取余法*** 对数字进行数位操作时,常见的方法便是用取余的方法提取出各位数字,再进行操作 操作(1):对10取余 ...
- 刷题神器小程序【飞刀帮刷题】,从此学习考试无忧虑
大家好,我跟大家介绍一款刷题神器小程序[飞刀帮刷题].这个小程序的主要功能有练习,考试,自创题库,分享和公开自创的题库,也可以做别人的题库.接下来介绍一下每个功能. 首先是首页,这里展示的是公开的试题 ...
- 2021 年最受欢迎的 10 个刷题网站
点击"开发者技术前线",选择"星标" 让一部分开发者看到未来 如果你想不断地提高自己的编程技能,可以不断尝试去解决那些编程中的难题.作者在本篇文章中列举出了10 ...
最新文章
- Laravel - Artisan 个人常用总结
- 用go语言制作读取excel模板批量生成表格工具
- 向数据中心劳动者致敬!
- c++ primer 5th第13章拷贝控制知识点和自编习题答案
- mysql查询正在执行的存储过程,[转]ms sql server 存储过程,查看正在执行的sql语句...
- 软考初级——计算机系统基础知识
- 对标Postman的ApiPost创始人:用户,是ApiPost唯一的信仰
- linux命令cd回退_Linux命令一
- html css 样式中100%width 仍有白边解决办法
- 协和医院等发起成立中国医学装备人工智能联盟
- sql多行插入insert多行无法分析查询文本_收藏!SQL语法全集合!
- 微分几何笔记(3) —— Frenet标架及Frenet方程组
- 如何合并多个.xlsx文件到一个Excel表格(office16)
- 线段树 HDU 4046 panda
- c语言中函数声明的作用
- Linux下安装和使用杀毒软件AntiVir(转)
- 苹果cms简介和优点及最新更新地址
- 12.4.1 索引顺序存取方法文件 / ISAM文件
- 组合框控件Combo Box和CComboBox类
- JavaEE系统架构师学习路线之基础篇