【算法-LeetCode】121. 买卖股票的最佳时机(动态规划;贪心)

121. 买卖股票的最佳时机 - 力扣(LeetCode)

发布:2021年9月15日13:44:26

问题描述及示例

给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。

你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。

返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。

示例 1:
输入:[7,1,5,3,6,4]
输出:5
解释:在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。

注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。

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

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

提示:
1 <= prices.length <= 105
0 <= prices[i] <= 104

我的题解

更新:2021年10月15日17:26:39

又写了一道股票问题的题解,本篇博客中的代码没有相关的注释,而下面参考博客中有我的详细解释,可以作为参考:

参考:【算法-LeetCode】122. 买卖股票的最佳时机 II(动态规划;贪心)_赖念安的博客-CSDN博客

【更新结束】

我的题解1(暴力解法—因超时而无法通过)

这个思路还是很直接的,就是遍历 prices 数组,然后在遍历过程中再进行内部遍历,假设外层遍历到了 i,那内层遍历就是由 0i-1。在遍历过程中用 profit 记录利润的历史最大值。由于有两层嵌套的遍历,这就导致程序的时间复杂度为 O ( n 2 ) O(n^2) O(n2),当遇到比较大的数据量时,程序运行时间就会超出时间限制。

/**
 * @param {number[]} prices
 * @return {number}
 */
var maxProfit = function(prices) {
  // profit用于动态存储利润最大值,遍历完成后,其值就是我们想要的结果
  let profit = 0;
  // 因为不能再同一天买卖股票获得利润,所以当prices的长度为1时应当直接返回0。
  if(prices.length === 1) {
    return 0;
  }
  // 开始遍历prices数组,sell是我们假设卖出股票的日期
  for(let sell = 1; sell < prices.length; sell++) {
    // buy是我们假设买入股票的日期,买入日一定是在卖出日之前的
    for(let buy = 0; buy < sell; buy++) {
     // 若假设的卖出日的股票价格不比买入日的股票价格高,
     // 那么说明这种买卖方案的利润不为正,可以直接结束本次内层遍历
      if(prices[sell] <= prices[buy]) {
        continue;
      }
      // 如果有利可图,那么就计算当前买卖方案所能得到的利润,
      // 与历史最大利润做比较,并把profit更新为较大的那个值
      profit = Math.max(profit, prices[sell] - prices[buy]);
    }
  }
  // 遍历完成后,profit就是所能得到的最大利润
  return profit;
};


提交记录
201 / 211 个通过测试用例
状态:超出时间限制
时间:2021/09/14 21:42

在这里插入图片描述
很可惜,这种解法确实是很好理解,但是碰到超长的测试用例是还是力不从心……

我的题解2(动态规划—失败)

有关动态规划的解法,我之前写了相关的总结步骤,可以参考下面的博客:

参考:【算法-LeetCode】53. 最大子序和(动态规划初体验)_赖念安的博客-CSDN博客

因为偷瞄到【提示】里有“动态规划”的标签,所以,我就开始往这个角度思考。写出了下面这个看起来很不像动态规划的动态规划解法。

我这里的 dp 数组的含义是:dp[i] 表示第 i 天所能获得的最大利润。而指针 p 则用于记录当前获得最大利润时的日期下标。

程序本质还是在寻找一段股价总体上升的区间的。

在这里插入图片描述

程序所匹配的目标股价区间

/**
 * @param {number[]} prices
 * @return {number}
 */
var maxProfit = function (prices) {
  // 创建长度和prices数组相同的dp数组,并默认填充0,这在后面的判断中会用上
  let dp = Array.from({ length: prices.length }).fill(0);
  // 初始化dp
  dp[0] = 0;
  // p指针用于标识当前获得最大利润时的那个卖出日
  let p = 0;
  for (let i = 1; i < prices.length; i++) {
    // 如果当前股价大于此前所能获取最大利润时的股价,则说明我现在卖出股票能获取更多的利润
    if (prices[i] > prices[p]) {
      // 如果当前卖出能够获取更多利润,那么当前能够获得利润就是在之前的最大利润基础上
      // 加上当前股价与此前获得最大利润时的股价的差价
      dp[i] = dp[p] + prices[i] - prices[p];
      // 更新p指针的指向,标识目前第i天卖出股票所能获得的利润最大
      p = i;
      // 如果已经完成了p指针的更新,那么后续的操作就不需要进行了,直接结束本次循环
      continue;
    }
    // 如果现在卖出不能获得更大的利润,则进入下一次循环,此前需要判断p指针是否需要移动
    // 这里判断dp[p]是否是初始状态是为了让应对还未遇到下降的股价时的情况
    if (dp[p] === 0) {
      p++;
    }
  }
  // 最后,在p指针所指向的日期卖出股票能获得最大利润,将其返回
  return dp[p];
};

这次我没有点击提交,毕竟提交后也是不通过的

然而很不幸的是,虽然内置的两个小测试用例能够通过测试,但是那个超长测试用例还是没有通过,这次不是超时,而是压根就是逻辑错误。

在这里插入图片描述

两个内置测试用例的运行结果(通过)

在这里插入图片描述

上面的那个超长用例的运行结果(未通过)

于是,我就开始思考为什么没有通过……后来突然想明白了,我这种解法就是默认了 prices 中第一次遇到的最小值(也就是下图中的A点)就是整个数组的最小值了(也即是我们心里预期的那个最佳的股票买入日的股价)。可事实上,也许后续还有可能出现比A点价格更低的股价(比如下面的点B)。由下面的图可以看到,很显然,在B点买入才能让后期卖出股票时利润最大。

在这里插入图片描述

我的题解3(动态规划—成功)

严格来说,这个解法不是我想出来的,在此感谢分享题解的博主和题友。

下面这种动态规划的解法主要还是参考以下两位博主的思路:

参考:暴力解法、动态规划(Java) - 买卖股票的最佳时机 - 力扣(LeetCode)
参考: 「代码随想录」带你学透股票系列!121. 买卖股票的最佳时机【暴力】【贪心】【动态规划】详解 - 买卖股票的最佳时机 - 力扣(LeetCode)

/**
 * @param {number[]} prices
 * @return {number}
 */
var maxProfit = function (prices) {
  if(prices.length === 1) {
    return 0;
  }
  let dp = Array.from({length: prices.length}).map(
    () => Array.from({length: 2})
  );
  dp[0][0] = 0;
  dp[0][1] = -prices[0];
  for(let i = 1; i < prices.length; i++) {
    dp[i][0] = Math.max(dp[i-1][0], dp[i-1][1] + prices[i]);
    dp[i][1] = Math.max(dp[i-1][1], -prices[i]);
  }
  return dp[dp.length-1][0];
};

提交记录
211 / 211 个通过测试用例
状态:通过
执行用时:632ms,在所有JavaScript提交中击败了5.14%的用户
内存消耗:70MB,在所有JavaScript提交中击败了5.01%的用户
时间:2021/09/17 16:30

可以看到,这种解法只需要遍历一次数组,没有双层嵌套的 for 循环。这是二维数组的解法,当然还是可以像我之前做的滚动数组那样通过滚动数组的方式来降低空间消耗。这在上面提到的参考题解中也有提到,思路还是和之前提到的差不多,这里就不再叙述了, 毕竟滚动数组确实不好描述。

详细的解释在我上面提到的两个参考题解中都有非常详细的解释,我这里就不再重复了。总的来说还是动态规划的那几个流程,我之所以没有想到这种二维数组的解法,就是在第一步确定 dp 数组的含义时想歪了,本题中最关键的地方就在于此,只要明白了这个点,后面的推导就水到渠成了。不得不说虽然动态规划的解题特征比较清晰,但是对其的应用确实千变万化的……

当然,在【官方题解】中,也有一种看起来形式不太相同的动态规划解法,其评论区里也有题友提出了一些比较好的理解思路,非常值得参考。

我的题解3(贪心)

在相关的题解中,也有一种利用贪心的解法:

/**
 * @param {number[]} prices
 * @return {number}
 */
var maxProfit = function (prices) {
  if(prices.length === 1) {
    return 0;
  }
  let profit = 0;
  let minPrice = Infinity;
  for(let i = 0; i < prices.length; i++) {
    minPrice = Math.min(minPrice, prices[i]);
    profit = Math.max(profit, prices[i] - minPrice);
  }
  return profit;
};


提交记录
211 / 211 个通过测试用例
状态:通过
执行用时:96 ms, 在所有 JavaScript 提交中击败了67.27%的用户
内存消耗:47.3 MB, 在所有 JavaScript 提交中击败了95.02%的用户
时间:2021/09/17 16:53

总体思路就是在遍历 prices 的过程中,用一个 minPrices 存储所遇到的最低股价,用 profit 存储目前能获取的最大利润,而且这个最大利润就是遍历过程中所获取的最大股价差。其实这种方式我和我上面自己写的那个失败的动态规划有点相似的味道。不过我的那个只能适应一小部分情况。

这段时间感觉做题的节奏没能保持下去,主要还是一对乱七八糟的东西要处理,还有一些必要的技能要学,总的来说还是有点怠惰了。这篇题解记录就不做详细的逐句注释了,因为主要还是看别人的题解,而且别人的题解也已经讲得很清楚了。到时候回顾的时候应该也没啥大问题吧。

官方题解

更新:2021年7月29日18:43:21

因为我考虑到著作权归属问题,所以【官方题解】部分我不再粘贴具体的代码了,可到下方的链接中查看。

更新:2021年9月15日13:49:21

参考:买卖股票的最佳时机 - 买卖股票的最佳时机 - 力扣(LeetCode)

【更新结束】

有关参考

更新:2021年9月16日22:38:13
参考:暴力解法、动态规划(Java) - 买卖股票的最佳时机 - 力扣(LeetCode)
参考: 「代码随想录」带你学透股票系列!121. 买卖股票的最佳时机【暴力】【贪心】【动态规划】详解 - 买卖股票的最佳时机 - 力扣(LeetCode)
更新:2021年9月17日17:30:57
参考:【算法-LeetCode】53. 最大子序和(动态规划初体验)_赖念安的博客-CSDN博客
更新:2021年10月15日17:26:39
参考:【算法-LeetCode】122. 买卖股票的最佳时机 II(动态规划;贪心)_赖念安的博客-CSDN博客

后言

更新:2021年9月19日11:34:05

昨天睡觉前看了下自己的CSDN博客消息,发现有人给我这篇博客点赞,其中还有一位关注了我,我的粉丝数也从5变为了6,虽说谈不上多么兴奋,因为我的博客内容更多地是用于自己回顾的,没想着能真正帮到其他人。但是总归是自己的内容收到了其他人的肯定,还是非常感谢的~

在这里插入图片描述

昨晚收到的点赞消息

在这里插入图片描述

昨晚关注我的用户

说实话,这篇博客我倒觉得写得粗制滥造,因为之前我的题解博客中基本都是有逐行的解释过程的,但是这篇博客却没有,尤其是关键的“动态规划”和“贪心”算法,只是把从被人那里看到的内容大概理解之后再自己写了一遍而已,代码中也没有相关的注释,虽然要写注释也可以,但是我却因为种种原因没有写(文章后面也有提到),其实主要原因还是我觉得这道题花费的周期有点长了,我有点不耐烦了,再加上还有其他事想忙,就导致没有详写的热情了。所以就想着水一水吧,反正自己回顾的时候能看得懂就好了。但是没想到从前一两天开始,这篇博客居然好像有越来越多的人阅读,首先是有2人收藏(看到有人收藏的时候我自己还疑惑了一下),然后可能是因为收藏的缘故导致文章的权重变高,所以就使得有越来越多的人看到文章,阅读量自然也就上升了(是突然上升的那种,刚开始看到的时候还是有点奇怪的),然后就是上面提到的昨晚发生的事了。然后早上起来想继续写博客,发现消息通知里有点赞、评论和私信,我想可能是我之前那篇阅读量最高的文章有人点赞评论了吧:

参考:【win10 企业版 LTSC一键安装微软应用商店Microsoft Store】直接使用GitHub上的开源项目,不用自己敲命令(亲测有效!!!),附卸载工具_赖念安的博客-CSDN博客_win10企业版ltsc安装应用商店

因为这篇博客确实是解决了别人的痛点的,会受到点赞我也不意外。

但是点开消息后却发现居然是本篇博客的点赞评论和私信,而且私信是CSDN官方发的消息,说是我的博客上了什么热榜。

在这里插入图片描述

CSDN热榜官方给我的私信

点击去一看还真有!

在这里插入图片描述

我的这篇博客到了热榜49名

后来一看文章的收藏量也直接从2变成了9,点赞数倒是保持为3,最奇怪的是,我的粉丝数,突然就从6变为了19(更新这里的时候好像变为21了)。好家伙,两年的粉丝量都比不上这半天的……

想来估计是短时间内有人给我点赞收藏,提高了文章权重,然后上了热榜,然后又有人看到,然后继续提到权重……不得不感叹流量的神奇。

讲到这里,我觉得自己反而没有什么特别的兴奋之情了,就是觉得一篇感觉质量不如我之前的博客的文章受到了比往常更多的关注这件事让我有点疑惑。同时也怕到后来自己写博客的时候会考虑太多,反而忘了自己的目的,说实话,我还挺怀念自己五个小粉丝的时候的……(当然,大家关注我,我还是报以感激的~谢谢大家的支持!)希望我的其他自我感觉质量更好的博客也能给大家带来帮助吧。

这篇博客的关键解题思路不是我独立想出来的,所以更多地是在拾人牙慧,其实更希望大家去原作者那里点赞之类的(有关的参考博客我都在相关地方写了)。

逼逼赖赖这么久,算是记录一下自己对这篇博客的看法吧。总结下来就是:

我很疑惑:为啥自己觉得写得不好的博客却受到了小关注,但是自己觉得写得好的文章反而没有呢?不过,无论如何,如果博客能顺便给别人带来帮助,那真的很让我开心!

另外,推荐一个这段时间看得比较多的LeetCode题解博主的个人题解网站,感觉质量还是很高的,非常感谢这位博主的分享!

参考:代码随想录

当然,LeetCode题解区也有很多精彩的题解描述,可以对照思考。

【更新结束】

  • 5
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
根据引用\[1\]和引用\[2\]的内容,买卖股票最佳时机可以使用动态规划算法来解决。动态规划是一种通过将问题分解为子问题并利用子问题的解来求解原问题的方法。在这个问题中,我们可以定义一个状态数组dp,其中dp\[i\]表示第i天卖出股票所能获得的最大利润。然后,我们可以通过遍历价格数组prices,计算出每一天卖出股票所能获得的最大利润,并更新dp数组。最后,dp数组中的最后一个元素即为所求的最大利润。 根据引用\[3\]的内容,区间动态规划算法是解决买卖股票最佳时机问题的一种方法。在这个算法中,我们需要考虑每个可能的买入和卖出的时间点,并计算出每个时间点的最大利润。然后,我们可以通过比较这些最大利润,找到最大的利润作为结果。 所以,买卖股票最佳时机可以使用区间动态规划算法来解决。 #### 引用[.reference_title] - *1* *3* [【算法-LeetCode121. 买卖股票最佳时机动态规划贪心)](https://blog.csdn.net/qq_44879358/article/details/120306943)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [【算法-LeetCode】122. 买卖股票最佳时机 II(动态规划贪心)](https://blog.csdn.net/qq_44879358/article/details/120783546)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值