动态规划的经典问题初探: Best Time to Buy and Sell Stock(DP)

本文针对LeetCode上的经典问题——寻找股票买卖的最佳时机以获取最大利润,提出了两种解决方案。首先介绍了基于遍历的方法,其次深入探讨了一种动态规划的高效算法,并通过构造差分数组简化问题。此外,还提供了一个简洁优雅的参考解法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.Description

leetcode-121. Best Time to Buy and Sell Stock(DP)

Say you have an array for which the ith element is the price of a given stock on day i.

If you were only permitted to complete at most one transaction (ie, buy one and sell one share of the stock), design an algorithm to find the maximum profit.

Example 1:

Input: [7, 1, 5, 3, 6, 4]
Output: 5

max. difference = 6-1 = 5 (not 7-1 = 6, as selling price needs to be larger than buying price)
Example 2:

Input: [7, 6, 4, 3, 1]
Output: 0

In this case, no transaction is done, i.e. max profit = 0.


2.Analysis

无非是找到后面的数相对当前的差距最大。算法的复杂度还是O(n2),暂时想不出好的方法,刚接触DP。很显然第一种解法不是dp方法。


3.code

class Solution {
public:
    int maxProfit(vector<int>& prices) {

    int max = 0;
    int index = 0;
    for(int i = 0; i < prices.size(); ) {
        if(prices[i+1] < prices[i]) {   
        //如果下一个小于当前的话,跳到下一个,避免无用的内嵌套循环
            i++;
        } else {
            for(int j = i+1; j < prices.size(); j++) {
                int tmp = prices[j] - prices[i];
                if( tmp > 0 && tmp > max) {
                    max = prices[j] - prices[i];
                    index = j;
                } 
            }
            i++;
        }
    }
    return max;
}
};

4. improvement

想了很久,熟悉了dp问题的一些相关概念,比如:状态、状态转移、状态转移方程,想到在这道题中,这三者分别是什么呢。从题目给的数据似乎不能只直观地看出来这些变量。所以我构造了一个数组,来观察是否能构造出“状态”。

  int diff[n];
  for (int i = 1; i < prices.size(); i++) {
    diff[i] = prices[i] - prices[i-1];
  }

所以,我把每一个元素减去前面的元素作为新数据的元素(也就是后一天相比前一天的估值变化,除去第一天),这样,显然可以看到“状态”了,那就是要么“涨”,要么“贬”。而我们对这个问题分“阶段”,因为dp问题是“多阶段”的,那么这里的“阶段”是什么呢?比如说,给定一个数据 [1,2,3,4,5],显然diff的元素都是正的,那么这些元素都可以归结到同一个阶段,也就是说连续为正的子集是该问题的“阶段”,如果给定数组[1,2,3,4,5,8,6,7,10,11],对应的diff数组为[0,1,1,1,1,3,2,1,3,1], 那么,当元素为6时,该位置的diff是负数,所以出现了“贬”的状态,而在此之前,最大的收益是 81=7=0+1+1+1+1+3,此后,该阶段的最大值可能变小,所以这可能就是”状态”要转移了,也就是阶段行的最大值可能发生变化。而当到达元素10的时候,[1...10] 的阶段性最大值又超过了 [1...8] 的最大值,此时我们可以认为状态发生了转移。所以导致状态发生转移的条件是当前的diff子列的和已经无法再长大,并且开始萎缩,甚至萎缩到小于0【见下面解释】

    count += diff[index];
    maxNum = max(count, maxNum);

也就是我们连续计算一个diff子列的和,而且始终保持记录当前出现过的子列的阶段性最大值maxNum(备忘录法)为该子列的和就很好地表示了最大收益。如:

87=(85)+(54)+(43)+(32)+(21)+0

而这刚好就是diff数组。所以我们的构造方法是对的,很好地刻画了最大收益。
  while(index < prices.size()) {
    count += diff[index];
    maxNum = max(count, maxNum);
    if(count < 0)
      count = 0;
    index ++;
  }

【解释】:当count萎缩到小于等于等于0的时候(要发生状态转移),必然存在一个使之萎缩的diff子列,而在这个子列当中,阶段性最大值max不可能再长大(否则不会萎缩到0,使之长大的必然在长大阶段的子列中)

所以最后的出来的DP解法如下:

#include <iostream>
using namespace std;
int maxProfit(vector<int>& prices) {
  if(prices.size() <= 1) return 0;

  int diff[n];
  for (int i = 1; i < prices.size(); i++) {
    diff[i] = prices[i] - prices[i-1];
  }
  int count = 0, maxNum = 0, index = -1;
  //找出diff中第一个不为负数的元素,开始进入长大阶段
  for(int k = 1; k < prices.size(); k++) {
    if(diff[k] > 0) {
      index = k;
      break;
    }
  }
  //如果diff元素都为非正数数,也就是原数组的元素是非递增的
  if(index == -1) return 0;

  while(index < prices.size()) {
    count += diff[index];
    maxNum = max(count, maxNum);
    if(count < 0)
      count = 0;
    index ++;
  }
  return maxNum;
}

int max(int a, int b) {
  return (a >= b)? a:b;
}

5. others’ perfect solution

同样的维护一个当前的最大值,记录出现的更小的值,从而等待更大的最大值。

class Solution {
public:
    int maxProfit(vector<int>& prices) {

        int minprice = INT_MAX;
        int maxprofit = 0;
        for (int i = 0; i < prices.size(); i++) {
            if (prices[i] < minprice)
                minprice = prices[i];
            else if (prices[i] - minprice > maxprofit)
                maxprofit = prices[i] - minprice;
        }
        return maxprofit;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值