昨天在刷题时碰上了一个系列算法题,叫做股票问题,刚开始做的还行,做着做着发现事情没有想象的那么简单,于是记录一下。

  1. best-time-to-buy-and-sell-stock-ii

题目描述:
假设你有一个数组,其中第i个元素表示某只股票在第i天的价格。
设计一个算法来寻找最大的利润。你可以完成任意数量的交易(例如,多次购买和出售股票的一股)。但是,你不能同时进行多个交易(即,你必须在再次购买之前卖出之前买的股票)。

Say you have an array for which the ith element is the price of a given stock on day i.
Design an algorithm to find the maximum profit. You may complete as many transactions as you like (ie, buy one and sell one share of the stock multiple times). However, you may not engage in multiple transactions at the same time (ie, you must sell the stock before you buy again).

思路:

  • 这题比较简单,指出可以完成任意数量的交易,所以只要是能赚钱的买卖我们都不能放过;
  • 开始自己的思路有点乱,不知道如何描述,看了大佬们的解析发现我就是这么想的:找到所有递增的区间

代码:

class Solution {public:int maxProfit(vector<int> &prices) {if (prices.size() == 0)return 0;int ans = 0;int buy_prices = INT_MAX;int sell_prices = INT_MAX;for (auto val : prices){if (val < buy_prices && buy_prices == sell_prices)buy_prices = sell_prices = val;else if (val >= sell_prices)sell_prices = val;else{ans += (sell_prices - buy_prices);buy_prices = sell_prices = val;}}ans += (sell_prices - buy_prices);return ans;}
};

我的这个思路其实是复杂了,但是思路说到底也就是找到所有递增的区间
大佬们的代码:

public class Solution {public int maxProfit(int[] prices) {if(prices == null || prices.length<2)return 0;int res = 0;for(int i =1;i<prices.length;i++){if(prices[i]>prices[i-1])res += prices[i]-prices[i-1];}return res;}
}
  1. best-time-to-buy-and-sell-stock-iii

题目描述:
假设你有一个数组,其中第i个元素是某只股票在第i天的价格。
设计一个算法来求最大的利润。你最多可以进行两次交易。
注意:
你不能同时进行多个交易(即,你必须在再次购买之前出售之前买的股票)。

Say you have an array for which the i th element is the price of a given stock on day i.
Design an algorithm to find the maximum profit. You may complete at most two transactions.
Note:
You may not engage in multiple transactions at the same time (ie, you must sell the stock before you buy again).

思路:

  • 刚看到这道题的时候觉得和上面一题差不多嘛,只不过只让买卖两次了,那我找到两个递增区间最大的两个地方不就好了,定义两个变量first和second存这两个值最后加起来OK了!
  • 后来发现并不是这样,因为有时候在一次买卖区间内可能既有递增又有递减,所以只找递增区间是不够的。
  • 然后没有思路了,只能求助大佬。

大佬告诉我可以这样做:

class Solution {public:int maxProfit(vector<int>& prices) {int buy1 = INT_MIN, sell1 = 0, buy2 = INT_MIN, sell2 = 0;for(int i = 0; i < prices.size(); i++) {buy1 = max(buy1, -prices[i]);sell1 = max(sell1, buy1 + prices[i]);buy2 = max(buy2, sell1 - prices[i]);sell2 = max(sell2, buy2 + prices[i]);}return sell2;}
};

嗯,看不懂。
大佬再解释:

/*
题目意思: 数组下标为i的元素存储股票i天时的价格,要求进行2次交易,求出最大利益,并且买第2支股票前必须先抛售第一支股票
假如只进行1次交易的话会更简单一些:用sell1表示初始时身上的净利润为0,buy1之后用于存放最便宜股价的价格。一个循环表示时间一天天推移,第一天时buy1记录下第一天买入股票身上净利润,之后每进入新的一天(今天),就用buy1表示前些天最便宜的股价,sell1保存了前些天买入最便宜股票之后再在股价最高时卖出股票的最大利润。新的一天到来,再用buy1继续记录最低股价,再计算出在今天抛售那个最低价股票后的利润,如果这个利润比之前保存的sell1高,那就更新sell1,否则,sell1不变。
如此循环下去,到最后一天结束,sell1就记录了一次交易的最大利润。
进行2次交易的道理是可以类推的。
*/
class Solution {
public:
int maxProfit(vector<int> &prices) {
int buy1 = INT_MIN, sell1 = 0, buy2 = INT_MIN, sell2 = 0;
for(auto u:prices) { //每次循环表示进入新的一天
buy1 = max(buy1,-u); //记录之前所有天最便宜的股价
sell1 = max(sell1,buy1 + u); //记录之前所有天只进行1次买卖的最大利益
buy2 = max(buy2,sell1 - u); //记录之前天先进行1次交易获得最大利益后,
//再买到那次交易后最便宜股价时剩余的净利润
sell2 = max(sell2,buy2 + u); //记录之前天进行2次完整交易的最大利润
}
return sell2;
}
};

哦…好像看懂了一点点,但是这惊为天人的代码是怎么想到的呢?
newcode没有看到更好的解释了,于是乎去求助了leetcode上的大佬,看到了这样一篇文章:
一个通用方法团灭 6 道股票问题

作者指出所有的股票问题都可以总结成一个状态转移方程问题,通过定义一个三维数组进行求解,称为“三维dp”。

所有股票问题的状态无非可以总结为三个:天数,交易次数,当日是否持股。

于是我们可以定义一个三维数组来描述这个状态:dp[i][k][x]
其中,
i 表示第 i 天,它的取值范围为[0, day-1](day为总的天数)
k 表示已发生的交易次数,这里我们定义每次购买股票时算一次交易(文章中说 buy 时和 sell 时都可算一次交易,但是按 sell 算时 base case 的定义特别困难)
x 表示当日的持股状态,持股状态无非两种:持股和未持股。这里用 0 表示当日未持股,用 1 表示当日持股。

最后,这个状态数组的值表示当前状态的最大利润,所以 dp[i][k][0] 表示的就是在第 i 天进行 k 次交易后的最大利润(未持股肯定比持股利润大啦)。

于是乎我们可以写出状态转移方程:

  • 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][1] - prices[i])

第一个状态方程可以解释为:
如果当天未持股,则会是两种情况
1、昨天也未持股,今天没有买股票,即dp[i-1][k][0];
2、昨天持股了,今天把股票卖了,即dp[i-1][k][1] + prices[i];
我们取这两种情况中利润最大的一种。

同理第二个状态方程可以解释为:
如果当天持股了,则会是两种情况
1、昨天也持股了,今天没有卖股票,即dp[i-1][k][1];
2、昨天未持股了,今天买了股票,即dp[i-1][k-1][0] - prices[i];
注意今天买股票算作一次交易,所以今天交易的次数要比昨天多一次,同样我们取利润最大的一种。

最后,我们考虑 base case:
1、dp[-1][k][0] = 0 // -1天时不可能买股票,所以利润为0
2、dp[-1][k][1] = INT_MIN // -1天不可能持股,我们把利润定义成负无穷,也就是无意义
3、dp[i][0][0] = 0 // 没有交易所以利润一定为0
4、dp[i][0][1] = INT_MIN // 没有交易不可能持股,所以这种情况也无意义

知道了这些,我们就可以套用状态转移方程解决股票问题了:
当只能进行一次交易时,也就是k = 1。
题目:leetcode121
代码:

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

不限制交易次数时,也就是k = 无穷大
题目:leetcode122
代码:

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

当限制交易次数为2时,也就是k = 2。
题目:leetcode123
代码:

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

当未指定 k 的具体值时:
题目:leetcode188
这题比较头痛,当我兴冲冲的打算继续按照上面的框架写答案的时候,问题来了:
怎么new一个三维数组?
说来惭愧,刷了这么久的题,连个new都弄不好,最终通过各路大佬帮忙写出了答案:

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

大家对new不太熟悉的同学可以看一下我是怎么new的,但是就当我势在必得的时候又出岔子了: 答案错误。
这不禁让我怀疑人生,难道看了这么久的答案是错的?在时候VS一步一步进行调试后我发现了问题所在:

原来时new的内存没有初始化,导致我的利润变成了负无穷!怪不得我赚不到钱!
修改后的代码:

class Solution {public:int maxProfit(int k, vector<int>& prices) {int day = prices.size();if (day <= 1)return 0;int *** dp = new int**[day];for (int i=0; i<day; i++){dp[i] = new int*[k+1]; dp[i][0] = new int[2];memset(dp[i][0], 0, 2 * sizeof(int));//初始化for (int j=1; j<=k; j++){dp[i][j] = new int[2];memset(dp[i][j], 0, 2 * sizeof(int)); //初始化if (i == 0){dp[i][j][0] = 0;dp[i][j][1] = -prices[i];}else{dp[i][j][0] = max(dp[i-1][j][0], dp[i-1][j][1]+prices[i]);dp[i][j][1] = max(dp[i-1][j][1], dp[i-1][j-1][0]-prices[i]);}}}return dp[day-1][k][0];}
};

再次提交,这次错倒是没错:

这又是咋了?可能是数据太大这个方法还需要优化吧…不过对了209的样例应该可以说明代码是没错的…

总结:

对于股票问题,可以使用状态转移方程求解。方法如上。
可以将这类问题扩展,类似于 “求数组中其中四个数和的最大值”这样的问题
珍爱生命远离算法题

leetcode之股票问题相关推荐

  1. leetcode买卖股票问题(思路、方法、code)

    一文解决Leetcode买卖股票问题 对于前3个问题,均采用了比较巧妙的解法.由于第4个问题具有非常强的泛型,因此采用了DP,第4个问题的dp如果理解的话,实际上只需要稍加修改状态便可以用该dp思路应 ...

  2. LeetCode买卖股票之一:基本套路(122)

    欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 关于<LeetCode买卖股票>系列 在L ...

  3. LeetCode买卖股票的最佳时机系列总结

    LeetCode买卖股票的最佳时机系列总结 此类动态规划从二维动规理解后优化到一维动规,部分题目还可以用到贪心. 目录: 121 买卖股票的最佳时机1 122 买卖股票的最佳时机2 123 买卖股票的 ...

  4. leetcode 买卖股票问题

    leetcode 买卖股票问题 lc121 买卖股票最佳时机 lc122 买卖股票最佳时机II lc123. 买卖股票的最佳时机 III lc188. 买卖股票的最佳时机 IV lc121 买卖股票最 ...

  5. LeetCode 2110. 股票平滑下跌阶段的数目(滑动窗口)

    文章目录 1. 题目 2. 解题 1. 题目 给你一个整数数组 prices ,表示一支股票的历史每日股价,其中 prices[i] 是这支股票第 i 天的价格. 一个 平滑下降的阶段 定义为:对于 ...

  6. LeetCode 2034. 股票价格波动(set + map)

    文章目录 1. 题目 2. 解题 1. 题目 给你一支股票价格的数据流.数据流中每一条记录包含一个 时间戳 和该时间点股票对应的 价格 . 不巧的是,由于股票市场内在的波动性,股票价格记录可能不是按时 ...

  7. leetcode 买卖股票系列题目总结

    总结:买卖股票系列题目 1.买卖股票的最佳时机(简单) 121. 买卖股票的最佳时机 难度简单1093 给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格. 如果你最多只允许完成一笔交易( ...

  8. LeetCode买卖股票问题集合(六种股票问题)

    一.买卖股票的最佳时机 (LeetCode 121) 简单的动态规划问题,只能完成一次买卖,定义二维 dp[i] [] 数组,dp [i] [0]表示没有股票的状态,dp [i] [1]表示持有股票的 ...

  9. LeetCode 买卖股票的最佳时机 - 超详细讲解系列题

    1.分析 使用通用方法,也即动态规划DP (1)LeetCode 121. 买卖股票的最佳时机 class Solution {public int maxProfit(int[] prices) { ...

最新文章

  1. 网站服务器的ip地址会变吗,网站的服务器变了 IP地址变吗
  2. android webview 自定义404错误页面!
  3. java需要前台封装对象吗_javaEE之-----------类反射直接封装前台传过来的参数
  4. 全球及中国教育信息化行业投资模式与发展建议咨询报告2022版
  5. echart中拆线点的偏移_Qt中圆弧和扇形的绘制
  6. C#最简单最完整的webservice实例
  7. Android_Layout (一)
  8. online游戏服务器架构--数据库及事件相关 .
  9. Python模块之MyQR——制作个性化动态二维码(超详细)
  10. Linux ls常见的命令选项【转载】
  11. linux nginx编译详解,Linux下nginx编译安装教程和编译参数详解
  12. HTML打地鼠小游戏代码
  13. 数据结构:线性表的顺序存储结构,实现集合的交差并补
  14. 贪心科技机器学习训练营(九)
  15. 「Ubuntu」ubuntu18.04键盘输入卡顿、延迟输入
  16. 数字图像处理实验之图像压缩
  17. 利用python实现星座运势查询APP
  18. 2021年消防工程师证报考条件是什么?
  19. 《皇室战争》游戏设计师:顶级卡牌竞技游戏的‘平衡之道’
  20. 睡眠不好有什么办法可以解决?几个快速入睡小妙招

热门文章

  1. Python 能说话吗?调用Windows系统自带语音合成
  2. 酒吧管理系统(大一c语言课程设计)
  3. window lcd css,纯CSS实现液晶字体效果
  4. python实现朗读内容
  5. 小米VR安装第三方APP的方法!
  6. 学习通项目需要用到的
  7. 手机云盘share php,宝塔面板安装云盘目录列表TCShare – 支持和彩云/天翼云
  8. Metro UI CSS 的简介
  9. 查看交叉编译gcc的版本
  10. Sipp工具实现呼叫中心的性能测试