简单算法题我重拳出击 | leetcode.[121]买卖股票的最佳时机-JAVA

194 阅读2分钟

这是我参与更文挑战的第3天,活动详情查看: 更文挑战

原题

买卖股票的最佳时机

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

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

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

  • 1<=prices.length<=1051 <= prices.length <= 10^5
  • 0<=prices[i]<=1040 <= prices[i] <= 10^4

示例 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

重拳出击

这题依然标签是动态规划,所以还是按照基本套路来。

首先仔细看了看题目,关键是低买高卖,所以这题一定要顺序遍历,并且最底线就是不亏钱,也就是不买股票(确实,不买股票我永远赚)。

  • 边界条件

    题目写了prices.length>=1 那这个也就没什么特殊的边界了,和前一天的题目贼像,就记一下第一个值 prices[i]用来推算就行。

    dp(n)={prices[0] n=1??? n>1dp(n)=\left\{ \begin{aligned} prices[0] & &\ n = 1 \\ ??? & & \ n > 1 \\ \end{aligned} \right.
  • 子问题

    prices 是一个长度为 i 的数组,根据题目低买高卖的意思,也就是以数组的顺序为准,从前往后前面的数字减后面的数字,找到差最大的情况,不能计算绝对值哈。当然有一个条件,就是结果必须大于零。

    这波把子问题设置为:数组长度为n时候,低买高卖的最大值

    然后人脑推算一下,这是一个长度为 n (0 < n << i) 的数组,首先需要找到从前往后的最低买入点,即买入的最小值,可以使用一个参数 min 保留。那么,假如目前 dp[n] 数组赚的最多的是 res ,此时的最低买入点为 min。当它往下推算一下,数组变为长度为 n + 1 ,这时候 res 数值就和这个数组的第 n 个元素有关,需要用这第n个元素去减去前头那个最低买入点然后与现有值去做比较,同时对于最小买入点的替换也要继续进行。这样,就会有两个结果:

    1. 长度为 n 时候就能赚的更多,继续向下比较第 n个元素

    2. 在第 n 个元素的时候卖出,赚的比前面更多,记录 dp[n] ,然后继续

    上头这个结论是能够一直往后面计算的,直到 n = i 。最终遍历dp[n] 找到最大值就好

  • 方程

    根据上一条,可以得到如下:

    dp(n)={prices[0] n=1Max(dp[n],prices[n]min) n>1dp(n)=\left\{ \begin{aligned} prices[0] & &\ n = 1 \\ Max(dp[n], prices[n] - min) & & \ n > 1 \\ \end{aligned} \right.

    当数组 prices[i] 遍历结束之后,会得到一个 dp[n] 数组中存的是长度为n时候,高卖低买能赚到的最多的钱。

  • 结合各种条件写代码

    public int maxProfit(int[] prices) {
        //题目的边界条件
        if (prices.length == 1) {
            return 0;
        }
        //最小买入点比较,记住要初始化理论最大值
        int min = Integer.MAX_VALUE;
        int[] dp = new int[prices.length+1];
        //初始值
        dp[0] = 0;
        for (int i = 1; i < prices.length; i++) {
            //循环替换能找到的最低买入点
            min = Math.min(min, prices[i-1]);
            //比较那种情况得到的差更大
            dp[i] = Math.max(dp[i], prices[i] - min);
        }
        //题目要求最少赚0元,不然应该理论最小值Integer.MIN_VALUE
        int res = 0;
        //遍历找到最赚的情况
        for (int i : dp) {
            if (res < i){
                res = i;
            }
        }
        return res;
    }

然后运行,发现 执行耗时击败了19.44%,内存击败了6.19%

太拉跨了,这个情况可以空间优化,因为只需要一边迭代,一边判断差最大就行了。

改一改:

    public int maxProfit(int[] prices) {
        if (prices.length == 1) {
            return 0;
        }
        int min = Integer.MAX_VALUE;
        int dp = 0;
        int res = 0;
        for (int i = 1; i < prices.length; i++) {
            min = Math.min(min, prices[i-1]);
            dp = Math.max(dp, prices[i] - min);
            if (dp > res) {
                res = dp;
            }
        }
        return res;
    }

然后运行,发现 执行耗时击败了60.90%,内存击败了56.83%

效果还行吧,再往下,暂时没想到,日后在搞,毕竟我只会简单技巧的样子。

  • 动态规划的空间优化还是很有必要的