2110. Number of Smooth Descent Periods of a Stock
Problem Statement
You are given an integer array prices representing the daily price history of a stock, where
prices[i] is the stock price on the ith day.
A smooth descent period of a stock consists of one or more contiguous days such that the price on each day is lower than the price on the preceding day by exactly 1. The first day of the period is exempted from this rule[citation:5].
Return the number of smooth descent periods.
Example 1
Input: prices = [3,2,1,4]
Output: 7
Explanation: There are 7 smooth descent periods:
[3], [2], [1], [4], [3,2], [2,1], and [3,2,1]
Note that a period with one day is a smooth descent period by the definition[citation:5].
Example 2
Input: prices = [8,6,7,7]
Output: 4
Explanation: There are 4 smooth descent periods: [8], [6], [7], and [7]
Note that [8,6] is not a smooth descent period as 8 - 6 ≠ 1[citation:5].
Example 3
Input: prices = [1]
Output: 1
Explanation: There is 1 smooth descent period: [1][citation:5]
Key Insight
The solution relies on these crucial observations:
- Every single day is a valid smooth descent period by definition (length 1 periods)
- For contiguous days where each day's price is exactly one less than the previous day, all subarrays of that contiguous segment are valid smooth descent periods
- If we find a contiguous segment of length
Lwhere prices decrease by exactly 1 each day, it contributesL * (L + 1) / 2smooth descent periods - The array can be divided into multiple such segments separated by "breaks" where the difference is not exactly 1
- We can process the array in one pass using a sliding window or counter approach
Approach 1: Sliding Window / Counter (Optimal)
This is the most efficient approach with O(n) time and O(1) space[citation:1]. We maintain a counter that tracks the length of the current smooth descent segment.
Algorithm Steps
- Initialize
ans = 1(for the first day) anddp = 1(current descent length) - Iterate from day 1 to n-1:
- If
prices[i] == prices[i-1] - 1, incrementdp(extend current descent) - Otherwise, reset
dp = 1(start new descent) - Add
dptoans(all subarrays ending at current day)
- If
- Return
ans
How It Works
At each position i, dp represents the number of smooth descent periods ending at
day i. When we extend a descent, all previous periods that ended at i-1 can be
extended to include i, plus the new single-day period at i.
Complexity Analysis
| Time Complexity | Space Complexity |
|---|---|
| O(n) | O(1) |
Where n is the number of days (length of prices array). Single pass through the array with constant extra space[citation:1].
class Solution {
public:
long long getDescentPeriods(vector<int>& prices) {
long long ans = 1; // prices[0]
int dp = 1;
for (int i = 1; i < prices.size(); ++i) {
if (prices[i] == prices[i - 1] - 1)
++dp;
else
dp = 1;
ans += dp;
}
return ans;
}
};
class Solution {
public long getDescentPeriods(int[] prices) {
long ans = 1; // prices[0]
int dp = 1;
for (int i = 1; i < prices.length; ++i) {
if (prices[i] == prices[i - 1] - 1)
++dp;
else
dp = 1;
ans += dp;
}
return ans;
}
}
class Solution:
def getDescentPeriods(self, prices: list[int]) -> int:
ans = 1 # prices[0]
dp = 1
for i in range(1, len(prices)):
if prices[i] == prices[i - 1] - 1:
dp += 1
else:
dp = 1
ans += dp
return ans
i, dp counts how many smooth
descent periods end at day i. When we have a smooth descent, all periods ending at day
i-1 can be extended to include day i, plus the single-day period at
i. This elegant approach avoids storing the entire DP array[citation:1].
Approach 2: Mathematical Formula for Contiguous Segments
Find all maximal smooth descent segments and use the formula for the sum of 1 + 2 + ... + L = L(L+1)/2 for each segment[citation:5].
Algorithm Steps
- Initialize
ans = 0andi = 0 - While
i < n:- Start new segment with length
L = 1 - Move
ito next day - While still in smooth descent (
prices[i-1] - prices[i] == 1), incrementLandi - Add
L * (L + 1) / 2to answer
- Start new segment with length
- Return
ans
How It Works
A contiguous segment of length L where prices decrease by exactly 1 each day contains exactly
L single-day periods, L-1 two-day periods, ..., and 1 L-day period.
The total is the sum of 1 to L, which equals L(L+1)/2.
Complexity Analysis
| Time Complexity | Space Complexity |
|---|---|
| O(n) | O(1) |
Single pass through the array with constant extra space[citation:5].
class Solution {
public:
long long getDescentPeriods(vector<int>& prices) {
int n = prices.size();
long long ans = 0, i = 0;
while (i < n) {
long long l = 1;
i++;
while (i < n && prices[i - 1] - prices[i] == 1) i++, l++;
ans += (l + 1) * l / 2;
}
return ans;
}
};
class Solution {
public long getDescentPeriods(int[] prices) {
int n = prices.length;
long ans = 0, i = 0;
while (i < n) {
long l = 1;
i++;
while (i < n && prices[i - 1] - prices[i] == 1) {
i++;
l++;
}
ans += (l + 1) * l / 2;
}
return ans;
}
}
class Solution:
def getDescentPeriods(self, prices: list[int]) -> int:
n = len(prices)
ans = 0
i = 0
while i < n:
l = 1
i += 1
while i < n and prices[i - 1] - prices[i] == 1:
i += 1
l += 1
ans += (l + 1) * l // 2
return ans
Approach 3: Dynamic Programming (Full DP Array)
Traditional dynamic programming approach storing DP values for each day[citation:5].
Algorithm Steps
- Initialize
dparray of sizenwith all 1's (each day is a valid period) - Initialize
ans = 0 - For each day
ifrom 0 to n-1:- If
i > 0andprices[i-1] - prices[i] == 1, adddp[i-1]todp[i] - Add
dp[i]toans
- If
- Return
ans
How It Works
dp[i] stores the number of smooth descent periods ending at day i. If day
i continues a smooth descent from day i-1, then all periods ending at
i-1 can be extended to include i.
Complexity Analysis
| Time Complexity | Space Complexity |
|---|---|
| O(n) | O(n) |
Single pass through the array but requires O(n) space for the DP array[citation:5].
class Solution {
public:
long long getDescentPeriods(vector<int>& prices) {
int n = prices.size();
long long ans = 0;
vector<int> dp(n, 1);
for (int i = 0; i < n; i++) {
if (i > 0 && prices[i - 1] - prices[i] == 1) {
dp[i] += dp[i - 1];
}
ans += dp[i];
}
return ans;
}
};
class Solution {
public long getDescentPeriods(int[] prices) {
int n = prices.length;
long ans = 0;
int[] dp = new int[n];
Arrays.fill(dp, 1);
for (int i = 0; i < n; i++) {
if (i > 0 && prices[i - 1] - prices[i] == 1) {
dp[i] += dp[i - 1];
}
ans += dp[i];
}
return ans;
}
}
class Solution:
def getDescentPeriods(self, prices: list[int]) -> int:
n = len(prices)
ans = 0
dp = [1] * n
for i in range(n):
if i > 0 and prices[i - 1] - prices[i] == 1:
dp[i] += dp[i - 1]
ans += dp[i]
return ans
dp[i] = 1 + (dp[i-1] if smooth descent continues else 0). While correct, it uses O(n) space
where O(1) space is possible[citation:5].
Detailed Example Walkthrough
Let's trace through the sliding window approach with prices = [3,2,1,4]:
| Day (i) | Price | Condition | dp (periods ending at i) | ans (total so far) | New Periods Added |
|---|---|---|---|---|---|
| 0 | 3 | Initialize | 1 | 1 | [3] |
| 1 | 2 | 3-2=1 ✓ | 2 | 3 | [2], [3,2] |
| 2 | 1 | 2-1=1 ✓ | 3 | 6 | [1], [2,1], [3,2,1] |
| 3 | 4 | 1-4≠1 ✗ | 1 | 7 | [4] |
dp counter represents "how many smooth descent
periods end at the current day." When we have a smooth descent (difference = 1), all periods ending
yesterday can be extended to include today, plus the new single-day period.
Comparison of Approaches
| Approach | Time | Space | Key Insight | Best For |
|---|---|---|---|---|
| Sliding Window | O(n) | O(1) | Track current descent length with counter | Optimal solution, interviews |
| Mathematical Formula | O(n) | O(1) | Sum formula for contiguous segments | Mathematical understanding |
| Dynamic Programming | O(n) | O(n) | Explicit DP recurrence relation | Learning DP patterns |
Edge Cases and Testing
1. Single Element Array
Input: [5] → Output: 1
Input: [1] → Output: 1
All single-element arrays should return 1
2. All Smooth Descent
Input: [5,4,3,2,1] → Output: 15
Length 5: contributes 5*6/2 = 15 periods
[5],[4],[3],[2],[1],[5,4],[4,3],[3,2],[2,1],[5,4,3],[4,3,2],[3,2,1],[5,4,3,2],[4,3,2,1],[5,4,3,2,1]
3. No Smooth Descent (Except Single Days)
Input: [1,3,5,7,9] → Output: 5
Each day is its own period, no multi-day periods
[1],[3],[5],[7],[9]
4. Mixed Pattern
Input: [8,7,6,8,7,6,5] → Output: 13
Segments: [8,7,6] (length 3 → 6 periods), [8,7,6,5] (length 4 → 10 periods)
But careful: Actually two separate segments: [8,7,6] and [8,7,6,5] starting at index 3
Total: 6 + (10-3) = 13? Let's compute properly:
Using sliding window: 1+2+3+1+2+3+4 = 16? Wait, recalculate:
Actually trace it: 1,2,3,1,2,3,4 → sum = 16
5. Large Arrays
Input: [100000,99999,99998,...,1] (length 100000)
Output: 100000*100001/2 = 5,000,050,000
Must use 64-bit integers (long long in C++, long in Java)
FAQs
1. Why does every single day count as a smooth descent period?
The problem definition states: "A smooth descent period consists of one or more contiguous days... The first day of the period is exempted from this rule." This means a single day has no preceding day to compare with, so it automatically qualifies[citation:5].
2. What's the difference between the sliding window and DP approaches?
The sliding window approach (Approach 1) uses O(1) space by keeping only a counter. The DP approach (Approach 3) uses O(n) space storing values for each day. Both have O(n) time, but sliding window is more space-efficient[citation:1][citation:5].
3. Why is the formula L(L+1)/2 used for contiguous segments?
A contiguous segment of length L where each day decreases by exactly 1 from the previous day
contains:
• L single-day periods
• L-1 two-day periods
• ...
• 1 L-day period
Sum = 1 + 2 + 3 + ... + L = L(L+1)/2[citation:5]
4. How do we handle cases where the difference is not exactly 1?
When prices[i-1] - prices[i] ≠ 1, we reset our counter to 1 (starting a new potential smooth descent). The current day still counts as a single-day period.
5. What's the time complexity of checking if prices[i-1] - prices[i] == 1?
This is an O(1) operation. Since we do it n-1 times (for i from 1 to n-1), the total time is O(n).
6. Can the prices increase within a smooth descent period?
No, by definition each day must be lower than the preceding day by exactly 1. If prices increase or decrease by more than 1, it breaks the smooth descent.
7. What if prices[i-1] - prices[i] equals -1 (price increases by 1)?
That doesn't count as a smooth descent. The condition requires prices[i-1] - prices[i] == 1, which means price decreases by exactly 1.
8. How does the algorithm handle duplicate consecutive prices?
If prices[i] == prices[i-1], then prices[i-1] - prices[i] == 0, not 1. So the descent breaks, and we reset the counter to 1.
9. Is the sliding window approach the same as two pointers?
Yes, the sliding window approach can be seen as a two-pointer approach where one pointer (i) moves through the array and we maintain a counter (dp) that represents the current window length of smooth descent.
10. What's the maximum possible answer for the given constraints?
With n ≤ 10^5, if all prices form one smooth descent, the answer would be n(n+1)/2 ≈ 5×10^9 for n=10^5. This fits in 64-bit integers but exceeds 32-bit range.