数组 & 双指针 · 2/10

买卖股票最佳时机

给定股票价格数组,计算一次买卖的最大利润

问题

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

你只能选择某一天买入,并在未来的某一天卖出。计算你能获取的最大利润。如果无法获利,返回 0。

输入: [7, 1, 5, 3, 6, 4]
输出: 5
解释: 第 2 天买入(价格 1),第 5 天卖出(价格 6),利润 = 6 - 1 = 5

解答

一次遍历

/**
 * 买卖股票的最佳时机
 * @param {number[]} prices - 股票价格数组
 * @return {number} 最大利润
 */
function maxProfit(prices) {
  if (prices.length < 2) return 0;

  let minPrice = prices[0]; // 记录遍历过程中的最低价格
  let maxProfit = 0; // 记录最大利润

  for (let i = 1; i < prices.length; i++) {
    // 如果当前价格更低,更新最低价格
    if (prices[i] < minPrice) {
      minPrice = prices[i];
    } else {
      // 否则计算以当前价格卖出的利润,更新最大利润
      maxProfit = Math.max(maxProfit, prices[i] - minPrice);
    }
  }

  return maxProfit;
}

// 测试
console.log(maxProfit([7, 1, 5, 3, 6, 4])); // 5
console.log(maxProfit([7, 6, 4, 3, 1])); // 0 (价格一直下跌,无法获利)
console.log(maxProfit([2, 4, 1])); // 2

简化写法

function maxProfit(prices) {
  let minPrice = Infinity;
  let maxProfit = 0;

  for (const price of prices) {
    minPrice = Math.min(minPrice, price);
    maxProfit = Math.max(maxProfit, price - minPrice);
  }

  return maxProfit;
}

动态规划思路

/**
 * dp[i] 表示第 i 天卖出能获得的最大利润
 * dp[i] = max(dp[i-1] + prices[i] - prices[i-1], 0)
 */
function maxProfit(prices) {
  let dp = 0; // 空间优化,只保留前一天的状态
  let maxProfit = 0;

  for (let i = 1; i < prices.length; i++) {
    // 今天卖出的利润 = 昨天卖出的利润 + 今天与昨天的差价
    // 如果为负,说明应该今天买入,重置为 0
    dp = Math.max(0, dp + prices[i] - prices[i - 1]);
    maxProfit = Math.max(maxProfit, dp);
  }

  return maxProfit;
}

关键点

  • 只需遍历一次,时间复杂度 O(n),空间复杂度 O(1)
  • 核心思路:遍历时记录最低买入价,计算当前卖出的利润
  • 买入必须在卖出之前,所以从左到右遍历时,最低价一定在当前价格之前
  • 如果价格一直下跌,最大利润为 0(不交易)
  • 动态规划思路:将问题转化为求最大连续子数组和