Leetcode 20
从现在开始要用c和java都敲一遍了。
1 题目
121. 买卖股票的最佳时机
给定一个数组 prices
,它的第 i
个元素 prices[i]
表示一支给定股票第 i
天的价格。
你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。
返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0
。
示例 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。
提示:
1 <= prices.length <= 105
0 <= prices[i] <= 104
2 我的想法
最大利润就是a - b的最大差值,并且要求a的索引比b小,如果最大利润a - b 小于0 , 那么就是没有交易完成,也记为0.
写嵌套的for循环?
maxProfit = 0;
for ( int i = 0 ; i ++ ; i <arr.length - 1)
{ for (int j = 1 ; j ++ ; j < arr.length - 2)
if (arr [ i ] > arr [ j ])
{
profit = arr [ i ] - arr [ j ];
if (profit > maxProfit)
{
maxProfit = profit ; }
}
}
return maxProfit;
瞎写,但是我觉得这肯定不是最优的解法,时间复杂度太高了。
这样是可行的吗?一定可以,用hash?还是怎么样?要么是时间换空间,要么是空间换时间。
用c和java实现。
3 实现
你提出的嵌套循环解法虽然可行,但时间复杂度为 O (n²),对于 n=10⁵的情况会超时(超时会怎样?),因此需要更优的解法。
最优解法是使用一次遍历(O (n) 时间复杂度),我们只需要记录当前最低价格和最大利润:
- 初始化最低价格为第一个元素,最大利润为 0
- 遍历数组,对于每个价格:
- 如果当前价格低于最低价格,则更新最低价格
- 否则计算当前价格与最低价格的差值
- 如果这个差值大于当前最大利润,则更新最大利润
c
#include <stdio.h>
#include <stdlib.h>int maxProfit(int* prices, int pricesSize) {if (pricesSize <= 1) {return 0;}int minPrice = prices[0]; // 记录最低价格int maxProfit = 0; // 记录最大利润for (int i = 1; i < pricesSize; i++) {// 如果当前价格比最低价格低,更新最低价格if (prices[i] < minPrice) {minPrice = prices[i];} // 否则计算可能的利润else {int profit = prices[i] - minPrice;// 如果当前利润更大,更新最大利润if (profit > maxProfit) {maxProfit = profit;}}}return maxProfit;
}// 测试函数
int main() {int prices1[] = {7,1,5,3,6,4};int size1 = sizeof(prices1) / sizeof(prices1[0]);printf("示例1最大利润: %d\n", maxProfit(prices1, size1)); // 应输出5int prices2[] = {7,6,4,3,1};int size2 = sizeof(prices2) / sizeof(prices2[0]);printf("示例2最大利润: %d\n", maxProfit(prices2, size2)); // 应输出0return 0;
}
java
public class MaxProfit {public int maxProfit(int[] prices) {if (prices == null || 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 {int profit = prices[i] - minPrice;// 如果当前利润更大,更新最大利润if (profit > maxProfit) {maxProfit = profit;}}}return maxProfit;}// 测试方法public static void main(String[] args) {MaxProfit solution = new MaxProfit();int[] prices1 = {7,1,5,3,6,4};System.out.println("示例1最大利润: " + solution.maxProfit(prices1)); // 应输出5int[] prices2 = {7,6,4,3,1};System.out.println("示例2最大利润: " + solution.maxProfit(prices2)); // 应输出0}
}
4 关于代码超时
代码超时会直接导致程序无法通过测评,最终判定为 “解答错误” 或 “时间超限”(TLE),哪怕逻辑上能算出正确结果。
简单说,超时就是程序 “跑得太慢了”,超过了测评系统给的时间上限(比如 1 秒),系统会直接终止程序运行,不会等待它算出答案。
⏱️ 为什么你的嵌套循环会超时?
我们用具体数据对比,就能清楚看到差距:
- 你的嵌套循环(O (n²)):如果数组长度是 10⁵,需要执行
10⁵ × 10⁵ = 10¹⁰
次操作。- 普通计算机每秒大概能处理
10⁸ - 10⁹
次简单操作,10¹⁰
次操作需要 10 秒以上,远超时间限制。
- 普通计算机每秒大概能处理
- 最优的一次遍历(O (n)):同样 10⁵长度的数组,只需要执行 10⁵次操作,不到 0.001 秒就能完成,完全符合要求。
这就像你要从 1 楼到 10 楼,嵌套循环是 “走楼梯,每层都往返一次”,而一次遍历是 “坐电梯直达”,效率差距天差地别。
🚫 超时的常见后果
- 测评不通过:在 LeetCode、牛客等 OJ(在线判题)平台上,会直接显示 “Time Limit Exceeded (TLE)”,不会给你 “部分得分”。
- 实际场景失效:如果这是实际项目中的代码(比如实时处理股票数据),超时会导致程序卡顿、数据堆积,甚至影响整个系统的响应速度。
- 暴露算法缺陷:超时本质是算法效率太低,说明你用的解法没有考虑 “数据规模”,这也是面试中面试官会重点关注的点 —— 能算出正确结果只是基础,写出高效代码才是关键。
💡 怎么避免超时?
核心是 “选择更优的算法”,从时间复杂度上降低操作次数:
- 先分析题目数据规模:比如题目说
n ≤ 10⁵
,就绝对不能用 O (n²) 的算法,要优先考虑 O (n) 或 O (n log n) 的解法。 - 减少重复计算:你的嵌套循环里,很多 “价格对比” 是重复的,而一次遍历通过 “记录历史最低价”,直接跳过了这些重复计算。
- 用空间换时间(必要时):比如有些题目可以用哈希表(HashMap)存已计算的结果,避免重复运算,但本题用常数空间就够了,不需要额外存数据。
常见算法时间复杂度对比表
时间复杂度 | 名称 | 1 秒内可处理的最大数据量 | 典型算法示例 | 适用场景 |
---|---|---|---|---|
O(1) | 常数时间 | 无限制(仅受内存影响) | 数组访问、哈希表查找 | 简单运算、直接访问数据 |
O(log n) | 对数时间 | 10^18 (极大) | 二分查找、平衡树操作 | 有序数据的快速查找、插入、删除 |
O(n) | 线性时间 | 10^8 ~ 10^9 | 线性查找、单链表遍历 | 必须遍历所有元素的场景 |
O(n log n) | 线性对数时间 | 10^6 ~ 10^7 | 快速排序、归并排序、堆排序 | 大规模数据的排序操作 |
O(n²) | 平方时间 | 10^4 ~ 10^5 | 冒泡排序、插入排序、嵌套循环 | 小规模数据(n≤10⁴)的简单处理 |
O(n³) | 立方时间 | 100 ~ 1000 | 矩阵乘法、三重嵌套循环 | 极小规模数据(n≤100)的复杂计算 |
O(2ⁿ) | 指数时间 | 20 ~ 30 | 递归解决子集问题、暴力枚举 | 仅适用于 n≤20 的极小问题 |
O(n!) | 阶乘时间 | 10 ~ 12 | 全排列生成、旅行商问题暴力解法 | 几乎只用于理论研究,实际中极少使用 |
关键结论:
- 当题目数据规模 n≤10⁴时,O (n²) 算法可能通过;n>10⁴时,必须用 O (n) 或 O (n log n) 算法
- 对于股票问题(n≤10⁵),O (n²) 算法会超时,而 O (n) 算法能轻松处理
- 指数级和阶乘级复杂度只适用于非常小的输入(n<20)
这个表格可以帮你在做题时快速评估算法可行性,比如看到 n=10⁵,立刻排除 O (n²) 解法,优先考虑线性或线性对数级算法。
5 自己写的错误百出的代码
class Solution {public int maxProfit(int[] prices) {int lowPrice = prices[0];int hopeProfit = 0; if(prices.length <= 1){return 0 ;}else {for (int i = 0 ; i <prices.length - 1 ; i++){int profit = prices[i + 1] - lowPrice;if(prices[i] < lowPrice){lowPrice = prices[i];}hopeProfit = profit;}return hopeProfit;}}
}
详细的错误:
class Solution {public int maxProfit(int[] prices) {// 问题1:未考虑数组为空的情况,直接访问prices[0]会导致空指针异常int lowPrice = prices[0];int hopeProfit = 0; // 问题2:判断条件冗余,数组长度为1时也应该正常处理if(prices.length <= 1){return 0 ;}else {// 问题3:循环范围错误,i只到length-2,最后一个元素prices[length-1]未被处理// 导致无法计算最后一天卖出的利润for (int i = 0 ; i < prices.length - 1 ; i++){// 问题4:计算利润的时机错误// 应该先更新最低价,再计算当前价格的利润// 这里用i+1天的价格减去当前最低价,逻辑混乱int profit = prices[i + 1] - lowPrice;// 问题5:更新最低价的判断条件错误// 用当前i天的价格比较,却没有用i+1天的价格比较// 导致可能错过更低的买入点if(prices[i] < lowPrice){lowPrice = prices[i];}// 问题6:没有保留最大利润,只是简单覆盖// 每次循环都将当前利润直接赋值,会丢失之前的最大利润hopeProfit = profit;}return hopeProfit;}}
}
主要错误总结:
- 数组边界问题:当数组为空时,直接访问
prices[0]
会抛出异常 - 循环逻辑错误:循环范围没有包含最后一个元素,导致无法计算最后一天卖出的情况
- 利润计算时机错误:先计算利润再更新最低价,导致利润计算基于旧的最低价
- 最大利润保存方式错误:没有比较并保留历史最大值,只是简单覆盖
- 最低价更新逻辑错误:没有使用当前考察的价格进行比较,可能错过更低的买入点
这些错误共同导致代码无法正确计算出最大利润,比如在价格持续上涨的情况下会得到错误结果,或者在数组为空时直接崩溃。
代码错误点与修复方案对比表
对比维度 | 原错误代码问题 | 修复后代码方案 | 核心原因解析 |
---|---|---|---|
数组边界处理 | 直接定义 int lowPrice = prices[0] ,未先判断数组是否为空,若输入空数组会触发 空指针异常 | 先判断 if (prices.length <= 1) ,满足条件直接返回 0;仅当数组长度≥2 时,再初始化 lowPrice = prices[0] | 数组操作需先 “防御” 空值 / 短数组场景,避免直接访问索引导致崩溃 |
循环范围 | 循环条件为 i < prices.length - 1 ,遍历仅到倒数第二个元素(i 最大为 length-2),最后一个元素未参与计算 | 循环条件改为 i从1开始,i < prices.length ,遍历覆盖所有元素(包括最后一个) | 原逻辑用 prices[i+1] 间接访问后一个元素,但会遗漏最后一个元素的 “卖出” 计算;修复后直接用 prices[i] 处理每个元素,逻辑更直接 |
最低价更新时机 | 先计算 profit = prices[i+1] - lowPrice ,再判断 prices[i] < lowPrice 并更新最低价 | 先判断 prices[i] < lowPrice 并更新最低价,再计算 profit = prices[i] - lowPrice | 原逻辑 “用旧低价算利润,再更新低价”,会导致利润计算基于过时的低价;正确逻辑应 “先确认当前最低价,再算当前价格的利润”,保证利润计算的时效性 |
最大利润保存 | 直接赋值 hopeProfit = profit ,每次循环覆盖前一次利润,无法保留历史最大利润 | 增加判断 if (profit > maxProfit) ,仅当当前利润更大时,才更新 maxProfit | 原逻辑只记录 “最后一次计算的利润”,而非 “所有计算中的最大利润”;修复后通过 “比较 - 保留” 逻辑,确保最终结果是全局最大值 |
最低价更新对象 | 用 prices[i] 对比更新最低价,但利润却用 prices[i+1] 计算,低价与利润的关联对象错位 | 用 prices[i] (当前循环元素)对比更新最低价,同时用 prices[i] 计算利润,两者 |
6 手敲一遍代码
java
class Solution {public int maxProfit(int[] prices) {if (prices.length <= 1 ){return 0 ;}int maxProfit = 0 ; int lowProfit = prices[0];for (int i = 0 ; i < prices.length -1 ; i ++){if (prices[i] < lowProfit ){lowProfit = prices [i];} int profit = prices[i+1] - lowProfit;if(profit > maxProfit){maxProfit = profit;}} return maxProfit;}
}
c
int maxProfit(int* prices, int pricesSize) {if (pricesSize <= 1){return 0;}int maxProfit = 0 ;int lowPrice = prices[0] ;for (int i = 0 ; i < pricesSize - 1 ; i++){int profit = prices[i + 1 ] - lowPrice ;if(prices[i] < lowPrice){lowPrice = prices[i];}if(profit > maxProfit)maxProfit = profit;}return maxProfit;
}
------
一点点小感悟,脱离ide,不要图方便自动补全,练好基本功,这样扎扎实实手敲一遍,不看答案隔天做一遍我感觉自己才掌握了,又进步了!很好!