本篇是股票买卖问题的更多题解,在上一篇文章中我们已经介绍了这一题型,实际上是一类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的合理性如何判断。对于一个长度为lenprices交易天数数组,每次交易(买入、卖出)需要花费两天时间,每天最多只能进行买入、卖出中的一种,因此对于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,现在手上持有股票;

k1或无穷大时,我们可以直接省略掉k,当k2时也可以把k=1k=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. 新年算法小专题1.滑动窗口(Java)

  2. 新年算法小专题1.滑动窗口刷题(Java)

  3. 新年算法小专题2.股票买卖(Java)

你的喜欢是我创作的动力,喜欢请关注,感谢每一个喜欢~
如有问题欢迎进行交流~
水平所限,如有错误请海涵,欢迎指正~

2021新年算法小专题—2.股票买卖利润刷题(Java)相关推荐

  1. 2021新年算法小专题—2.股票买卖利润(Java)

    概述 熟悉这类题目的同学都知道,事实上这是一类动态规划(dp)问题,在小专题中都暂且只针对这一类问题进行解决了,更全面的动态规划文章以后有机会再努力吧! 简单介绍一句动态规划.动态规划实际是一种分治思 ...

  2. 2021最新 MySQL面试题精选(附刷题小程序)

    推荐使用小程序阅读 为了能让您更加方便的阅读 本文所有的面试题目均已整理至小程序<面试手册> 可以通过微信扫描(或长按)下图的二维码享受更好的阅读体验! 最近梳理汇总了Java面试常遇到的 ...

  3. leetcode刷题java、c++、go语言三合一版本 谷歌师兄的刷题笔记、东哥的算法小抄、 Guide哥的Java面试突击版

    不久前火爆 GitHub 的 LeetCode 中文刷题手册,分享给大家,让正在找工作的朋友能够快速找到心仪的offer! <LeetCode Cookbook>目前已经收录了 520 道 ...

  4. 2021最新 SpringBoot面试题精选(附刷题小程序)

    推荐使用小程序阅读 为了能让您更加方便的阅读 本文所有的面试题目均已整理至小程序<面试手册> 可以通过微信扫描(或长按)下图的二维码享受更好的阅读体验! 文章目录 推荐使用小程序阅读 1. ...

  5. 一个算法笨蛋的12月leetCode刷题日记

    类似文章 一个算法笨蛋的2021年11月leetCode刷题日记 一个算法笨蛋的2021年12月leetCode刷题日记 一个算法笨蛋的2022年1月leetCode刷题日记 一个算法笨蛋的2022年 ...

  6. 算法记录 牛客网 leetcode刷题记录

    算法记录 & 牛客网 & leetcode刷题记录 解题思路 STL容器 常用算法模板 堆排序 插入排序 快速排序 BFS层序遍历 二叉树 JZ55 二叉树的深度 BST(binary ...

  7. 小何同学的leetcode刷题笔记 基础篇(01)整数反转

    小何同学的leetcode刷题笔记 基础篇(01)整数反转[07] *** [01]数学取余法*** 对数字进行数位操作时,常见的方法便是用取余的方法提取出各位数字,再进行操作 操作(1):对10取余 ...

  8. 刷题神器小程序【飞刀帮刷题】,从此学习考试无忧虑

    大家好,我跟大家介绍一款刷题神器小程序[飞刀帮刷题].这个小程序的主要功能有练习,考试,自创题库,分享和公开自创的题库,也可以做别人的题库.接下来介绍一下每个功能. 首先是首页,这里展示的是公开的试题 ...

  9. 2021 年最受欢迎的 10 个刷题网站

    点击"开发者技术前线",选择"星标" 让一部分开发者看到未来 如果你想不断地提高自己的编程技能,可以不断尝试去解决那些编程中的难题.作者在本篇文章中列举出了10 ...

最新文章

  1. Laravel - Artisan 个人常用总结
  2. 用go语言制作读取excel模板批量生成表格工具
  3. 向数据中心劳动者致敬!
  4. c++ primer 5th第13章拷贝控制知识点和自编习题答案
  5. mysql查询正在执行的存储过程,[转]ms sql server 存储过程,查看正在执行的sql语句...
  6. 软考初级——计算机系统基础知识
  7. 对标Postman的ApiPost创始人:用户,是ApiPost唯一的信仰
  8. linux命令cd回退_Linux命令一
  9. html css 样式中100%width 仍有白边解决办法
  10. 协和医院等发起成立中国医学装备人工智能联盟
  11. sql多行插入insert多行无法分析查询文本_收藏!SQL语法全集合!
  12. 微分几何笔记(3) —— Frenet标架及Frenet方程组
  13. 如何合并多个.xlsx文件到一个Excel表格(office16)
  14. 线段树 HDU 4046 panda
  15. c语言中函数声明的作用
  16. Linux下安装和使用杀毒软件AntiVir(转)
  17. 苹果cms简介和优点及最新更新地址
  18. 12.4.1 索引顺序存取方法文件 / ISAM文件
  19. 组合框控件Combo Box和CComboBox类
  20. JavaEE系统架构师学习路线之基础篇

热门文章

  1. 北大强基计划有计算机吗,北大强基计划
  2. 网络技术入门(一):网络技术基础知识系统归结
  3. 海康威视SDK通过NVR获取视频设备的状态
  4. 计算机考试小蒋在教务处,计算机二级MS-OFFICE考试EXCEL题型汇总附答案
  5. 人机融合智能的新思考
  6. linux top参数分析,Linux的top命令(分析篇)
  7. 动态令牌 (谷歌身份验证器)的实现
  8. 单向直播面临升级,网易视频云首推多路互动直播
  9. 从身份证芯片的“秘密”谈谈网络信息搜索
  10. [Learn Gst] 工程结构