LeetCode 2110 - Number of Smooth Descent Periods of a Stock
This problem is asking us to count all "smooth descent periods" in a stock price array. A smooth descent period is defined as a contiguous sequence of days where the stock price decreases by exactly 1 each day, except the first day which is always counted as a valid period of…
Difficulty: 🟡 Medium
Topics: Array, Math, Two Pointers, Dynamic Programming, Sliding Window
Solution
Problem Understanding
This problem is asking us to count all "smooth descent periods" in a stock price array. A smooth descent period is defined as a contiguous sequence of days where the stock price decreases by exactly 1 each day, except the first day which is always counted as a valid period of length 1. Essentially, every individual day counts as a period of length 1, and any sequence of consecutive days where the difference between consecutive prices is exactly 1 contributes additional periods.
The input prices is an integer array of size up to 100,000, with each price between 1 and 100,000. The expected output is a single integer representing the total number of smooth descent periods.
Key insights from the constraints include that the array can be large, so a naive O(n²) solution that checks all subarrays will be too slow. Also, all values are positive integers, so we do not need to handle negative prices.
Important edge cases include arrays of length 1, arrays with constant prices, and arrays where no consecutive days decrease by exactly 1.
Approaches
Brute Force
A brute-force approach would involve checking every possible contiguous subarray and verifying if it is a smooth descent period. For each start index i, we would expand to the right while the difference between consecutive elements is exactly 1, counting all valid subarrays. This approach guarantees correctness but is too slow for large arrays because it has O(n²) time complexity.
Optimal
The key observation for an optimal solution is that smooth descent periods are consecutive sequences, and the number of periods in a sequence of length k is given by the sum of the first k integers. Instead of enumerating all subarrays, we can iterate through the array once, maintaining a running count of the current descent length. For each day, if prices[i-1] - prices[i] == 1, we extend the current sequence; otherwise, we reset the sequence. At each step, the number of smooth descent periods ending on that day is equal to the length of the current sequence. Summing these counts gives the total number of periods.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(n²) | O(1) | Check all subarrays explicitly |
| Optimal | O(n) | O(1) | Single pass, maintain current descent length |
Algorithm Walkthrough
- Initialize
total_periodsto 0 to store the cumulative count of smooth descent periods. - Initialize
current_lengthto 1, representing the descent period length ending at the first day. - Iterate through the array from index 1 to n-1.
- For each index
i, check ifprices[i-1] - prices[i] == 1.
- If true, increment
current_lengthby 1. - Otherwise, reset
current_lengthto 1, as the descent period has broken.
- Add
current_lengthtototal_periodsat each iteration, because every day contributes as the end of multiple periods. - Return
total_periodsafter finishing the iteration.
Why it works: This algorithm works because the number of descent periods ending at each day is exactly equal to the length of the current descending sequence. Adding these counts ensures all subarrays are counted efficiently without explicitly enumerating them.
Python Solution
from typing import List
class Solution:
def getDescentPeriods(self, prices: List[int]) -> int:
total_periods = 1 # first day is always a period
current_length = 1
for i in range(1, len(prices)):
if prices[i-1] - prices[i] == 1:
current_length += 1
else:
current_length = 1
total_periods += current_length
return total_periods
In this Python implementation, we start by counting the first day as a valid period. As we iterate through the array, current_length tracks the length of the current smooth descent. Every time we move to the next day, we either extend the sequence if the difference is 1, or reset if it is not. Adding current_length to total_periods ensures all subarrays ending at that day are counted.
Go Solution
func getDescentPeriods(prices []int) int64 {
totalPeriods := int64(1) // first day is always a period
currentLength := 1
for i := 1; i < len(prices); i++ {
if prices[i-1] - prices[i] == 1 {
currentLength++
} else {
currentLength = 1
}
totalPeriods += int64(currentLength)
}
return totalPeriods
}
In Go, we need to use int64 to avoid integer overflow for large arrays and counts. The logic mirrors the Python implementation, iterating over the array and updating currentLength and totalPeriods accordingly.
Worked Examples
Example 1: [3,2,1,4]
| i | prices[i] | current_length | total_periods |
|---|---|---|---|
| 0 | 3 | 1 | 1 |
| 1 | 2 | 2 | 3 |
| 2 | 1 | 3 | 6 |
| 3 | 4 | 1 | 7 |
Output: 7
Example 2: [8,6,7,7]
| i | prices[i] | current_length | total_periods |
|---|---|---|---|
| 0 | 8 | 1 | 1 |
| 1 | 6 | 1 | 2 |
| 2 | 7 | 1 | 3 |
| 3 | 7 | 1 | 4 |
Output: 4
Example 3: [1]
| i | prices[i] | current_length | total_periods |
|---|---|---|---|
| 0 | 1 | 1 | 1 |
Output: 1
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n) | Single pass through the prices array |
| Space | O(1) | Only a few integer variables are used, no extra data structures |
The complexity is optimal for the input constraints. No additional memory is needed, and each element is processed once.
Test Cases
# Provided examples
assert Solution().getDescentPeriods([3,2,1,4]) == 7 # example 1
assert Solution().getDescentPeriods([8,6,7,7]) == 4 # example 2
assert Solution().getDescentPeriods([1]) == 1 # example 3
# Edge and stress tests
assert Solution().getDescentPeriods([1,2,3,4,5]) == 5 # increasing sequence
assert Solution().getDescentPeriods([5,4,3,2,1]) == 15 # decreasing by 1 all the way
assert Solution().getDescentPeriods([1,1,1,1]) == 4 # constant sequence
assert Solution().getDescentPeriods([100000]) == 1 # single large value
assert Solution().getDescentPeriods([2,1,2,1,2,1]) == 6 # alternating descents
| Test | Why |
|---|---|
[3,2,1,4] |
Validates a mixed sequence with a full descent |
[8,6,7,7] |
Validates that non-1 differences reset descent |
[1] |
Single-element array |
[1,2,3,4,5] |
Strictly increasing sequence |
[5,4,3,2,1] |
Continuous smooth descent of maximum length |
[1,1,1,1] |
Constant prices |
[100000] |
Maximum single element |
[2,1,2,1,2,1] |
Alternating descent sequences |
Edge Cases
One important edge case is an array of length 1. The algorithm correctly counts this as one smooth descent period. Another edge case is a strictly increasing array, which should count only single-day periods; the algorithm resets current_length each time, handling this correctly. A third edge case is a large array with a long descending sequence; using int64 in Go prevents integer overflow for large sums, and in Python the arbitrary-precision integers handle it natively. Arrays with repeating numbers also require correct resetting, which is managed by checking prices[i-1] - prices[i] == 1.