买卖股票的最佳时机,经典算法实战。

在这里插入图片描述

作者简介,普修罗双战士,一直追求不断学习和成长,在技术的道路上持续探索和实践。

多年互联网行业从业经验,历任核心研发工程师,项目技术负责人。

欢迎 点赞✍评论⭐收藏


算法领域知识

链接 专栏
分发糖果 算法专栏
买卖股票的最佳时机 算法专栏

经典算法题 之 买卖股票的最佳时机

在这里插入图片描述

题目如下:

给你一个整数数组 prices ,其中 prices[i] 表示某支股票第 i 天的价格。

在每一天,你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买,然后在 同一天 出售。

返回 你能获得的 最大 利润

示例 1:

输入:prices = [7,1,5,3,6,4]
输出:7
解释:在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5 - 1 = 4 。
     随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6 - 3 = 3 。
     总利润为 4 + 3 = 7

示例 2:

输入:prices = [1,2,3,4,5]
输出:4
解释:在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5 - 1 = 4 。
     总利润为 4

示例 3:

输入:prices = [7,6,4,3,1]
输出:0
解释:在这种情况下, 交易无法获得正利润,所以不参与交易可以获得最大利润,最大利润为 0

提示:

  • 1 <= prices.length <= 3 * 104
  • 0 <= prices[i] <= 104

解答这道题,可以使用 贪心算法 进行解决。

我们遍历数组,寻找股票价格上升的机会,并在最低价格时买入,最高价格时卖出。 我们维护一个变量profit来存储最大利润。在遍历的过程中,我们更新最小价格和最大利润。

算法的实现逻辑如下:

  1. 初始化最小价格为数组第一个元素 prices[0],最大利润为0。
  2. 遍历数组从第二个元素开始:
    a. 如果当前价格 prices[i] 比最小价格小,则更新最小价格为 prices[i]。
    b. 如果当前价格 prices[i] - 最小价格 大于 最大利润,则更新最大利润为 prices[i] - 最小价格。
  3. 返回最大利润。

以下是用Java代码实现逻辑的示例:

public int maxProfit(int[] prices) {
    int minPrice = prices[0]; // 初始化最小价格
    int maxProfit = 0; // 初始化最大利润

    for (int i = 1; i < prices.length; i++) {
        if (prices[i] < minPrice) { // 当前价格比最小价格小
            minPrice = prices[i]; // 更新最小价格
        } else if (prices[i] - minPrice > maxProfit) { // 当前利润大于最大利润
            maxProfit = prices[i] - minPrice; // 更新最大利润
        }
    }

    return maxProfit;
}

如果你想要继续优化算法,可以考虑将上述算法转化为动态规划算法。

这种方法使用两个变量来跟踪当前的最小值和最大差值。

我们从第二个元素开始遍历,用当前元素的值减去前面的最小值来更新最大差值。需要注意,这里取最小值的时候,我们需要在前面的所有元素中选出最小值。

算法的实现逻辑如下:

  1. 初始化最小值为数组的第一个元素 prices[0],最大差值为0。
  2. 遍历数组从第二个元素开始:
    a. 计算当前元素作为卖出价格时能够得到的最大差值(当前元素 - 前面最小值)。
    b. 将当前元素与最小值进行比较,更新最小值。
    c. 将当前最大差值与历史最大差值进行比较,更新历史最大差值。
  3. 返回历史最大差值。

以下是用Java代码实现逻辑的示例:

public int maxProfit(int[] prices) {
    int minPrice = prices[0]; // 初始化最小值
    int maxProfit = 0; // 初始化最大差值

    for (int i = 1; i < prices.length; i++) {
        int curProfit = prices[i] - minPrice; // 计算当前差值

        if (prices[i] < minPrice) { // 更新最小值
            minPrice = prices[i];
        }

        if (curProfit > maxProfit) { // 更新最大差值
            maxProfit = curProfit;
        }
    }

    return maxProfit;
}

由于遍历了整个数组,时间复杂度为 O(n),空间复杂度为 O(1)。

如果我们要将这个问题扩展到允许进行 k 次交易,我们还可以使用动态规划算法来解决。

我们可以为每个交易次数和每个价格点都维护一些变量,以解决这个问题。使用一个二维数组 dp,其中 dp[i][j] 表示在第 i 天结束时,最多进行 j 次交易所能获得的最大利润。

我们需要和之前的算法一样,使用一个 Profit 变量来维护最大差值,以便在更新 dp 数组时使用。对于每一天,我们需要遍历之前的一天中的所有可能交易次数,并在其中选出最大的利润。此外,我们需要确保交易次数 j 不会超过 i/2,因为在同一天内进行的交易分别计算为一次买入和一次卖出,所以最多可以进行 i/2 次交易。

算法的实现逻辑如下:

  1. 初始化 dp 数组为 0。
  2. 对于每个交易次数:
    a. 用 Profit 变量来跟踪历史最大差值,并初始化为 -prices[0],表示在第一天以当前价格买入股票。
    b. 遍历每天:
    i. 将 Profit 更新为之前交易次数 j - 1 的最大利润减去当前股票价格。
    ii. 更新 Profit 为之前交易次数 j 的最大利润与当前股票价格的差值。
    iii. 将 dp 值更新为 j 次交易中历史最大差值。
  3. 返回 dp[k][prices.length - 1],表示 k 次交易中在最后一天可以获得的最大利润。

以下是用Java代码实现逻辑的示例:

public int maxProfit(int k, int[] prices) {
    if (k == 0) {
        return 0; // 不允许进行交易
    }

    int n = prices.length;
    if (k >= n / 2) {
        // 交易次数太多,相当于在每一天都可以买入和卖出
        int maxProfit = 0;
        for (int i = 1; i < n; i++) {
            if (prices[i] > prices[i-1]) {
                maxProfit += prices[i] - prices[i-1];
            }
        }
        return maxProfit;
    }

    int[][] dp = new int[k+1][n]; // 初始化 dp 数组为 0

    for (int i = 1; i <= k; i++) { // 对于每个交易次数
        int maxProfit = -prices[0]; // 初始化 Profit 为 -prices[0]
        for (int j = 1; j < n; j++) { // 遍历每天
            dp[i][j] = Math.max(dp[i][j-1], maxProfit + prices[j]); // 更新 dp 值
            maxProfit = Math.max(maxProfit, dp[i-1][j-1] - prices[j]);
        }
    }

    return dp[k][n-1]; // 返回 k 次交易中在最后一天可以获得的最大利润
}

由于遍历了整个数组,时间复杂度为 O(kn),空间复杂度为 O(kn)。

关注作者,普修罗双战士,给你不一样的技术体验,一起在技术领域扶摇直上九万里,共筑坚如磐石的权。

欢迎 点赞✍评论⭐收藏

在这里插入图片描述

你可能感兴趣的:(算法专栏,算法)