股票买卖最佳时机问题是算法面试中的经典题目,也是动态规划思想的典型应用场景。题目要求给定一个数组prices,其中prices[i]表示某支股票第i天的价格,设计算法计算你所能获取的最大利润。你只能选择某一天买入这只股票,并选择在未来的某一个不同的日子卖出该股票。
这个问题的关键在于理解两个核心约束条件:
从示例来看:
最直观的解法是双重循环暴力枚举所有可能的买卖组合:
c复制int maxProfit(int* prices, int pricesSize) {
int max_profit = 0;
for (int i = 0; i < pricesSize - 1; i++) {
for (int j = i + 1; j < pricesSize; j++) {
int profit = prices[j] - prices[i];
if (profit > max_profit) {
max_profit = profit;
}
}
}
return max_profit;
}
这种方法时间复杂度为O(n²),空间复杂度O(1)。虽然正确,但在处理大规模数据时效率低下。
我们可以将问题转化为寻找价格序列中的最大差值(后减前)。动态规划的核心思想是:
状态转移方程为:
code复制dp[i] = max(dp[i-1], prices[i] - min_price)
其中dp[i]表示前i天的最大利润。
优化后的单次遍历解法:
c复制int maxProfit(int* prices, int pricesSize) {
if (pricesSize <= 1) return 0;
int min_price = prices[0];
int max_profit = 0;
for (int i = 1; i < pricesSize; i++) {
if (prices[i] < min_price) {
min_price = prices[i];
} else if (prices[i] - min_price > max_profit) {
max_profit = prices[i] - min_price;
}
}
return max_profit;
}
这个实现的时间复杂度降为O(n),空间复杂度保持O(1),效率显著提升。
min_price:遍历过程中记录的历史最低价max_profit:当前能获得的最大利润基础情况:当n=1时,无法交易,利润为0,算法正确
归纳假设:假设对于前k天的价格序列,算法能正确计算最大利润
归纳步骤:对于第k+1天,有两种情况:
因此算法能正确维护最大利润值。
算法正确处理了以下边界情况:
算法仅需一次遍历数组,时间复杂度为O(n),n为价格序列长度。这是最优解,因为至少需要查看每个价格一次。
只使用了固定数量的额外变量(min_price和max_profit),空间复杂度为O(1)。
虽然这个简化模型与实际股票交易有差异(不考虑手续费、多次交易等),但核心思想可用于:
在C/C++实现中,可以添加以下优化:
c复制#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)
int maxProfit(int* prices, int pricesSize) {
if (unlikely(pricesSize <= 1)) return 0;
int min_price = prices[0];
int max_profit = 0;
for (int i = 1; i < pricesSize; i++) {
if (unlikely(prices[i] < min_price)) {
min_price = prices[i];
} else if (likely(prices[i] - min_price > max_profit)) {
max_profit = prices[i] - min_price;
}
}
return max_profit;
}
使用likely/unlikely宏帮助编译器优化分支预测。
虽然这个特定问题难以并行化,但对于变种问题(如多交易次数限制),可以考虑:
python复制def maxProfit(prices):
if len(prices) <= 1:
return 0
min_price = prices[0]
max_profit = 0
for price in prices[1:]:
if price < min_price:
min_price = price
elif price - min_price > max_profit:
max_profit = price - min_price
return max_profit
java复制public int maxProfit(int[] prices) {
if (prices.length <= 1) return 0;
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;
}
javascript复制function maxProfit(prices) {
if (prices.length <= 1) 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 if (prices[i] - minPrice > maxProfit) {
maxProfit = prices[i] - minPrice;
}
}
return maxProfit;
}
c复制// 正常波动
int prices1[] = {7,1,5,3,6,4};
assert(maxProfit(prices1, 6) == 5);
// 持续上涨
int prices2[] = {1,2,3,4,5};
assert(maxProfit(prices2, 5) == 4);
// 持续下跌
int prices3[] = {7,6,4,3,1};
assert(maxProfit(prices3, 5) == 0);
c复制// 空数组
int prices4[] = {};
assert(maxProfit(prices4, 0) == 0);
// 单元素数组
int prices5[] = {5};
assert(maxProfit(prices5, 1) == 0);
// 两个元素,可获利
int prices6[] = {1, 2};
assert(maxProfit(prices6, 2) == 1);
// 两个元素,不可获利
int prices7[] = {2, 1};
assert(maxProfit(prices7, 2) == 0);
c复制// 大数组测试
#define SIZE 1000000
int* prices8 = (int*)malloc(SIZE * sizeof(int));
for (int i = 0; i < SIZE; i++) {
prices8[i] = rand() % 10000;
}
// 仅测试是否能快速处理,不验证具体结果
maxProfit(prices8, SIZE);
free(prices8);
为了更好地理解算法,可以绘制价格曲线图:
这种可视化方法能直观展示算法如何跟踪最低点和最大利润。
对于高频交易等极端场景,可以考虑:
在实际金融应用中,需要考虑:
虽然这个简单算法提供了理论基础,但实际交易系统需要综合考虑更多复杂因素。