LeetCode 每日一题笔记
0. 前言
- 日期:2025.12.15
- 题目:2110.股票平滑下跌阶段的数目
- 难度:中等
- 标签:数组 数学 动态规划
1. 题目理解
问题描述 :
给你一个整数数组 prices ,表示一支股票的历史每日股价,其中 prices[i] 是这支股票第 i 天的价格。
一个 平滑下降的阶段 定义为:对于 连续一天或者多天 ,每日股价都比 前一日股价恰好少 1 ,这个阶段第一天的股价没有限制。
请你返回 平滑下降阶段 的数目。
示例:
示例 1:
输入:prices = [3,2,1,4]
输出:7
解释:总共有 7 个平滑下降阶段:
3\], \[2\], \[1\], \[4\], \[3,2\], \[2,1\] 和 \[3,2,1
注意,仅一天按照定义也是平滑下降阶段。
示例 2:输入:prices = [8,6,7,7]
输出:4
解释:总共有 4 个连续平滑下降阶段:[8], [6], [7] 和 [7]
由于 8 - 6 ≠ 1 ,所以 [8,6] 不是平滑下降阶段。
示例 3:输入:prices = [5,4,3,2,1]
输出:15
解释:单个元素阶段有5个,两个连续元素的阶段有4个,三个的有3个,四个的有2个,五个的有1个,总计 5+4+3+2+1=15。
2. 解题思路
核心观察
- 基础性质:单个元素本身就是一个平滑下降阶段,因此初始结果至少为数组长度;
- 连续平滑下降段的规律 :若存在长度为
k的连续平滑下降序列(如 [3,2,1],k=3),则该序列能贡献的额外阶段数为k-1 + k-2 + ... + 1(即k*(k-1)/2)。例如 k=3 时,额外贡献 2+1=3 个阶段([3,2]、[2,1]、[3,2,1]); - 动态规划视角 :以第
i天为结尾的平滑下降阶段数dp[i],若prices[i] = prices[i-1]-1,则dp[i] = dp[i-1] + 1;否则dp[i] = 1。总阶段数为所有dp[i]的和。
算法步骤
原有代码的算法步骤
- 初始化结果:将单个元素的阶段数(数组长度)计入结果;
- 遍历数组找连续段 :
- 从第
i个元素开始,向后遍历找到最长的连续平滑下降序列,记录序列长度count; - 计算该连续段能贡献的额外阶段数(
count-1 + count-2 + ... + 1),并累加到结果; - 调整遍历索引,跳过已处理的连续段元素;
- 从第
- 返回最终结果:累加所有额外阶段数后得到总数目。
官方题解的算法步骤(动态规划)
- 初始化 :
res = 1(第一个元素的阶段数),prev = 1(以第一个元素结尾的阶段数); - 遍历数组 :从第二个元素开始,判断是否满足平滑下降条件:
- 满足则
prev += 1(以当前元素结尾的阶段数 = 前一个的阶段数 + 1); - 不满足则
prev = 1(仅当前元素自身); - 每次将
prev累加到总结果res;
- 满足则
- 返回总结果。
3. 代码实现
java
class Solution {
public static long getDescentPeriods(int[] prices) {
long result=0;
result+=prices.length;
for (int i = 0; i < prices.length; i++) {
int count=1;
int a= prices[i];
i++;
while (i<prices.length&&prices[i]==a-1){
a=prices[i];
i++;
count++;
}
System.out.println("i:"+i);
i-=1;
System.out.println(count);
//对count进行处理加入结果之中
for (int j = count-1; j >0 ; j--) {
result+=j;
}
}
return result;
}
}
4. 代码优化说明
原有代码的优缺点
- 优点:逻辑直观,通过找连续段的方式直接计算贡献值,符合直观的数学理解;
- 缺点 :
- 存在嵌套循环(内层
for循环计算累加和),时间效率略低; - 索引操作繁琐(多次
i++/i--),容易出错; - 额外的
System.out打印语句增加冗余; - 累加和计算可优化为数学公式(
count*(count-1)/2),替代内层循环。
- 存在嵌套循环(内层
官方题解的优化点
- 时间效率优化:去掉嵌套循环,单次遍历即可完成计算,时间复杂度从 O(n)(最坏)/ O(n²)(极端情况)降至严格 O(n);
- 空间优化 :无需额外存储,仅用两个变量(
res、prev)记录状态,空间复杂度 O(1); - 逻辑简化:基于动态规划思想,避免复杂的索引调整和累加和计算,代码更简洁、不易出错;
- 数学等价性:动态规划的累加方式与「单个元素 + 连续段额外贡献」的计算方式完全等价,最终结果一致。
原有代码的小优化(不改变核心逻辑)
将内层累加循环替换为数学公式,减少循环次数:
java
// 替换原有内层for循环
if (count > 1) {
result += (long) count * (count - 1) / 2;
}
5. 复杂度分析
原有代码复杂度
- 时间复杂度 :
- 最好情况(无连续平滑下降段):O(n),仅遍历数组一次;
- 最坏情况(全数组是连续平滑下降段):O(n)(外层遍历) + O(n)(内层累加循环),整体仍为 O(n);
- 注:若累加和用嵌套循环实现,极端情况下看似 O(n²),但实际每个元素仅被处理一次,总操作数仍为 O(n)。
- 空间复杂度 :O(1),仅使用常数个变量(
result、count、a等),无额外空间开销。
官方题解复杂度
- 时间复杂度:O(n),仅单次遍历数组,所有操作均为常数级;
- 空间复杂度 :O(1),仅使用
res、prev两个变量,空间效率最优。
6. 总结
题目核心
本题的核心是识别「连续平滑下降序列」的贡献规律,无论是直接计算连续段的额外贡献,还是用动态规划累加每个位置的阶段数,本质都是对「1+2+...+(k-1)」这一数学规律的应用。
关键知识点
- 数学规律 :长度为
k的连续平滑下降序列,除了k个单个元素阶段,还能贡献k*(k-1)/2个多元素阶段; - 动态规划思想:利用「以当前元素结尾的阶段数」推导下一个状态,避免重复计算,提升效率;
- 数据类型注意 :结果可能很大(如数组长度为 10⁵ 时,总阶段数可达 ~5×10⁹),需用
long存储结果,防止整数溢出。
刷题启示
- 面对「计数类」数组问题,先找单个元素/基础情况的贡献,再分析连续段的规律,可大幅简化计算;
- 动态规划是优化「连续段计数」问题的常用手段,通过状态递推避免嵌套循环;
- 累加和类的计算优先使用数学公式(如等差数列求和),替代循环累加,提升代码效率和可读性;
- 索引操作需谨慎,原有代码中多次调整
i的值容易引入 bug,优化时应尽量简化索引逻辑。