2110. Number of Smooth Descent Periods of a Stock
Problem Description
You are given an integer array prices
where prices[i]
represents the stock price on day i
.
A smooth descent period is defined as one or more consecutive days where the price drops by exactly 1 each day compared to the previous day. The first day of any period doesn't need to follow this rule (it can start from any price).
For example:
- If prices are
[3, 2, 1]
, this forms one smooth descent period of length 3 (price drops from 3→2→1, each by exactly 1) - If prices are
[5, 4, 3, 2]
, this forms one smooth descent period of length 4 - If prices are
[5, 3, 2]
, the segment[5, 3]
doesn't form a smooth descent (drops by 2, not 1), but[3, 2]
forms a smooth descent period of length 2
Your task is to count the total number of smooth descent periods in the array. This includes all possible subarrays that satisfy the smooth descent condition.
For a smooth descent period of length n
:
- It contains
n
single-day periods - It contains
n-1
two-day periods - It contains
n-2
three-day periods - And so on...
The total count for a period of length n
is n + (n-1) + (n-2) + ... + 1 = n*(n+1)/2
.
Return the total count of all smooth descent periods in the given price array.
Intuition
The key insight is that we need to find all contiguous segments where prices decrease by exactly 1 each day, then count all possible subarrays within each segment.
When we find a smooth descent segment of length n
, we need to count how many valid periods it contains. Let's think about this with an example: if we have prices [5, 4, 3, 2]
(length 4), the valid periods are:
- Length 1:
[5]
,[4]
,[3]
,[2]
→ 4 periods - Length 2:
[5,4]
,[4,3]
,[3,2]
→ 3 periods - Length 3:
[5,4,3]
,[4,3,2]
→ 2 periods - Length 4:
[5,4,3,2]
→ 1 period
Total: 4 + 3 + 2 + 1 = 10
periods
This follows the pattern n + (n-1) + (n-2) + ... + 1
, which equals n*(n+1)/2
using the arithmetic series formula.
So our strategy becomes:
- Find each maximal smooth descent segment (where prices drop by exactly 1 consecutively)
- For each segment of length
cnt
, addcnt*(cnt+1)/2
to our answer - Move to the next segment and repeat
We use two pointers to efficiently identify these segments: pointer i
marks the start of a segment, and we advance pointer j
as long as the smooth descent condition holds (prices[j-1] - prices[j] == 1
). Once the condition breaks, we've found a complete segment from index i
to j-1
, calculate its contribution, then move i
to j
to start looking for the next segment.
This approach ensures we count every valid smooth descent period exactly once while traversing the array only once, giving us an efficient O(n)
solution.
Learn more about Math and Dynamic Programming patterns.
Solution Approach
We implement the solution using a two-pointer technique to identify and count smooth descent periods efficiently.
Algorithm Steps:
-
Initialize variables:
ans = 0
to store the total count of smooth descent periodsi = 0
as the starting pointer for the current segmentn = len(prices)
to store the array length
-
Main loop - iterate through the array:
while i < n:
For each position
i
, we need to find the longest smooth descent period starting from this position. -
Find the end of current smooth descent segment:
j = i + 1 while j < n and prices[j - 1] - prices[j] == 1: j += 1
- Start
j
ati + 1
(the next position) - Keep advancing
j
as long as the price drops by exactly 1 from the previous day - Stop when we reach the end of array or the smooth descent condition breaks
- Start
-
Calculate contribution of current segment:
cnt = j - i ans += (1 + cnt) * cnt // 2
cnt = j - i
gives us the length of the current smooth descent segment- Using the arithmetic series formula, a segment of length
cnt
contributes(1 + cnt) * cnt // 2
smooth descent periods - This is equivalent to
1 + 2 + 3 + ... + cnt
-
Move to the next segment:
i = j
Update
i
toj
to start searching for the next smooth descent segment. -
Return the result: After processing all segments, return
ans
which contains the total count.
Time Complexity: O(n)
where n
is the length of the prices array. Each element is visited at most twice (once by pointer i
and once by pointer j
).
Space Complexity: O(1)
as we only use a constant amount of extra space for variables.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the solution with prices = [8, 6, 5, 4, 3, 5, 4]
.
Initial State:
ans = 0
,i = 0
,n = 7
Iteration 1: Starting at i = 0
(price = 8)
- Set
j = 1
- Check:
prices[0] - prices[1] = 8 - 6 = 2
≠ 1 - Smooth descent breaks immediately
- Segment length:
cnt = j - i = 1 - 0 = 1
- Contribution:
(1 + 1) * 1 // 2 = 1
- Update:
ans = 0 + 1 = 1
,i = 1
Iteration 2: Starting at i = 1
(price = 6)
- Set
j = 2
- Check:
prices[1] - prices[2] = 6 - 5 = 1
✓ (continue) - Advance
j = 3
- Check:
prices[2] - prices[3] = 5 - 4 = 1
✓ (continue) - Advance
j = 4
- Check:
prices[3] - prices[4] = 4 - 3 = 1
✓ (continue) - Advance
j = 5
- Check:
prices[4] - prices[5] = 3 - 5 = -2
≠ 1 (stop) - Found segment
[6, 5, 4, 3]
from index 1 to 4 - Segment length:
cnt = 5 - 1 = 4
- Contribution:
(1 + 4) * 4 // 2 = 10
- This counts:
[6]
,[5]
,[4]
,[3]
(4 singles) - Plus:
[6,5]
,[5,4]
,[4,3]
(3 pairs) - Plus:
[6,5,4]
,[5,4,3]
(2 triples) - Plus:
[6,5,4,3]
(1 quadruple) - Total: 4 + 3 + 2 + 1 = 10
- This counts:
- Update:
ans = 1 + 10 = 11
,i = 5
Iteration 3: Starting at i = 5
(price = 5)
- Set
j = 6
- Check:
prices[5] - prices[6] = 5 - 4 = 1
✓ (continue) - Advance
j = 7
- We've reached the end of the array
- Found segment
[5, 4]
from index 5 to 6 - Segment length:
cnt = 7 - 5 = 2
- Contribution:
(1 + 2) * 2 // 2 = 3
- This counts:
[5]
,[4]
(2 singles) and[5,4]
(1 pair)
- This counts:
- Update:
ans = 11 + 3 = 14
,i = 7
End: i = 7 ≥ n
, loop terminates
Final Answer: 14 smooth descent periods total
Solution Implementation
1from typing import List
2
3class Solution:
4 def getDescentPeriods(self, prices: List[int]) -> int:
5 """
6 Count the total number of smooth descent periods in the prices array.
7 A smooth descent period is a subarray where each element is exactly 1 less than the previous.
8
9 Args:
10 prices: List of integers representing prices
11
12 Returns:
13 Total count of all smooth descent periods (including single elements)
14 """
15 total_periods = 0
16 current_index = 0
17 array_length = len(prices)
18
19 # Process the array by finding consecutive descent sequences
20 while current_index < array_length:
21 # Find the end of current descent sequence
22 next_index = current_index + 1
23
24 # Extend the sequence while the descent condition is met
25 # (previous element minus current element equals 1)
26 while next_index < array_length and prices[next_index - 1] - prices[next_index] == 1:
27 next_index += 1
28
29 # Calculate the length of the current descent sequence
30 sequence_length = next_index - current_index
31
32 # Add the number of subarrays in this sequence
33 # For a sequence of length n, there are n*(n+1)/2 subarrays
34 total_periods += (1 + sequence_length) * sequence_length // 2
35
36 # Move to the next unprocessed element
37 current_index = next_index
38
39 return total_periods
40
1class Solution {
2 public long getDescentPeriods(int[] prices) {
3 long totalDescentPeriods = 0;
4 int arrayLength = prices.length;
5
6 // Iterate through the array, finding consecutive descent sequences
7 int startIndex = 0;
8 while (startIndex < arrayLength) {
9 // Find the end of current descent sequence
10 int endIndex = startIndex + 1;
11
12 // Extend the sequence while consecutive elements decrease by exactly 1
13 while (endIndex < arrayLength && prices[endIndex - 1] - prices[endIndex] == 1) {
14 endIndex++;
15 }
16
17 // Calculate the length of the current descent sequence
18 int sequenceLength = endIndex - startIndex;
19
20 // Add the number of subarrays in this sequence
21 // For a sequence of length n, there are n*(n+1)/2 subarrays
22 // This includes all possible contiguous subarrays within the sequence
23 totalDescentPeriods += (1L + sequenceLength) * sequenceLength / 2;
24
25 // Move to the next potential sequence
26 startIndex = endIndex;
27 }
28
29 return totalDescentPeriods;
30 }
31}
32
1class Solution {
2public:
3 long long getDescentPeriods(vector<int>& prices) {
4 long long totalPeriods = 0;
5 int n = prices.size();
6
7 // Process each descent sequence
8 for (int startIdx = 0, endIdx = 0; startIdx < n; startIdx = endIdx) {
9 // Initialize end pointer to next position
10 endIdx = startIdx + 1;
11
12 // Extend the descent sequence while consecutive elements decrease by exactly 1
13 while (endIdx < n && prices[endIdx - 1] - prices[endIdx] == 1) {
14 ++endIdx;
15 }
16
17 // Calculate the length of current descent sequence
18 int sequenceLength = endIdx - startIdx;
19
20 // Add all possible subarrays in this descent sequence
21 // For a sequence of length k, there are k*(k+1)/2 subarrays
22 totalPeriods += (1LL + sequenceLength) * sequenceLength / 2;
23 }
24
25 return totalPeriods;
26 }
27};
28
1/**
2 * Counts the number of smooth descent periods in a price array.
3 * A smooth descent period is a subarray where each element is exactly 1 less than the previous element.
4 *
5 * @param prices - Array of prices to analyze
6 * @returns Total count of all smooth descent periods (including single elements)
7 */
8function getDescentPeriods(prices: number[]): number {
9 let totalDescentPeriods: number = 0;
10 const arrayLength: number = prices.length;
11
12 // Iterate through the array, finding consecutive descent sequences
13 let startIndex: number = 0;
14 while (startIndex < arrayLength) {
15 // Find the end of current descent sequence
16 let endIndex: number = startIndex + 1;
17
18 // Extend the sequence while consecutive elements decrease by exactly 1
19 while (endIndex < arrayLength && prices[endIndex - 1] - prices[endIndex] === 1) {
20 endIndex++;
21 }
22
23 // Calculate the length of the current descent sequence
24 const sequenceLength: number = endIndex - startIndex;
25
26 // Add the number of subarrays in this sequence using the formula: n * (n + 1) / 2
27 // This counts all possible contiguous subarrays within the descent sequence
28 totalDescentPeriods += Math.floor(((1 + sequenceLength) * sequenceLength) / 2);
29
30 // Move to the next potential sequence
31 startIndex = endIndex;
32 }
33
34 return totalDescentPeriods;
35}
36
Time and Space Complexity
Time Complexity: O(n)
, where n
is the length of the array prices
.
The algorithm uses a two-pointer approach with variables i
and j
. The outer while
loop iterates through the array with pointer i
, and the inner while
loop advances pointer j
to find consecutive descent periods (where each price is exactly 1 less than the previous price). Although there are nested loops, each element in the array is visited at most twice - once by pointer i
and once by pointer j
. Since i
is always set to j
after processing each descent period, both pointers traverse the array only once in total. Therefore, the overall time complexity is linear: O(n)
.
Space Complexity: O(1)
The algorithm only uses a constant amount of extra space regardless of the input size. The variables used are:
ans
: stores the running count of descent periodsi
andj
: two pointers for traversing the arrayn
: stores the length of the arraycnt
: stores the length of the current descent period
All these variables require constant space, making the space complexity O(1)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Off-by-One Error in Counting Segments
Pitfall: A common mistake is incorrectly calculating the contribution of each smooth descent segment. Developers might use the wrong formula or miscount the length.
Example of incorrect implementation:
# WRONG: Using wrong formula ans += cnt * (cnt - 1) // 2 # This misses single-element periods # WRONG: Miscounting the segment length cnt = j - i - 1 # This would undercount by 1
Solution: Remember that a segment from index i
to j-1
(where j
is the first index that breaks the pattern) has length j - i
. The number of subarrays in a segment of length n
is n*(n+1)/2
.
2. Incorrect Boundary Check in While Loop
Pitfall: Forgetting to check array bounds or using the wrong comparison operator when extending the smooth descent segment.
Example of incorrect implementation:
# WRONG: Missing boundary check while prices[j - 1] - prices[j] == 1: # Can cause IndexError j += 1 # WRONG: Using wrong indices while j < n and prices[j] - prices[j + 1] == 1: # IndexError when j = n-1 j += 1
Solution: Always check j < n
before accessing prices[j]
, and use prices[j-1] - prices[j] == 1
to compare consecutive elements correctly.
3. Handling Single-Element Arrays
Pitfall: Not properly handling edge cases like single-element arrays or forgetting that single elements count as smooth descent periods of length 1.
Example scenario:
prices = [5] # Should return 1, not 0
Solution: The algorithm naturally handles this case since when i = 0
and the array has one element, j
becomes 1, cnt = 1
, and the formula (1+1)*1//2 = 1
gives the correct answer.
4. Integer Overflow in Formula Calculation
Pitfall: In languages with fixed integer sizes, calculating (1 + cnt) * cnt
might cause overflow for very large values of cnt
.
Solution: In Python, this isn't an issue due to arbitrary precision integers. In other languages, consider using appropriate data types or reformulating the calculation:
# Alternative formulation to avoid overflow in other languages ans += cnt // 2 * (cnt + 1) if cnt % 2 == 0 else (cnt + 1) // 2 * cnt
5. Resetting the Loop Counter Incorrectly
Pitfall: After processing a segment, incorrectly updating the starting position for the next iteration.
Example of incorrect implementation:
# WRONG: Skipping potential segments i = j + 1 # This would skip the element at index j # WRONG: Creating infinite loop i = i + 1 # This might reprocess parts of segments
Solution: Set i = j
after processing each segment. This ensures we start the next search from the first element that broke the previous smooth descent pattern, which could potentially be the start of a new smooth descent period.
Which two pointer techniques do you use to check if a string is a palindrome?
Recommended Readings
Math for Technical Interviews How much math do I need to know for technical interviews The short answer is about high school level math Computer science is often associated with math and some universities even place their computer science department under the math faculty However the reality is that you
What is Dynamic Programming Prerequisite DFS problems dfs_intro Backtracking problems backtracking Memoization problems memoization_intro Pruning problems backtracking_pruning Dynamic programming is an algorithmic optimization technique that breaks down a complicated problem into smaller overlapping sub problems in a recursive manner and uses solutions to the sub problems to construct a solution
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Don’t Miss This!