Smooth Descent Periods visualization

2110. Number of Smooth Descent Periods of a Stock

Difficulty: Medium
Topics: Array, Math, Dynamic Programming[citation:2]
Companies: Amazon, Google, Bloomberg
Acceptance Rate: 65.1%
Pro Tip: This problem is essentially about finding maximal runs of consecutive -1 differences and summing the number of subarrays contributed by each run[citation:7]. While dynamic programming is possible, the most intuitive and optimal solution uses a sliding window or two pointers approach[citation:2].

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]

Problem Link: View on LeetCode ↗

Key Insight

The solution relies on these crucial observations:

  1. Every single day is a valid smooth descent period by definition (length 1 periods)
  2. 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
  3. If we find a contiguous segment of length L where prices decrease by exactly 1 each day, it contributes L * (L + 1) / 2 smooth descent periods
  4. The array can be divided into multiple such segments separated by "breaks" where the difference is not exactly 1
  5. We can process the array in one pass using a sliding window or counter approach
Important: While the problem can be solved with dynamic programming, many find the sliding window approach more intuitive for this problem[citation:2]. The dynamic programming tag might be misleading as the optimal solution doesn't require storing a full DP array.

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

  1. Initialize ans = 1 (for the first day) and dp = 1 (current descent length)
  2. Iterate from day 1 to n-1:
    • If prices[i] == prices[i-1] - 1, increment dp (extend current descent)
    • Otherwise, reset dp = 1 (start new descent)
    • Add dp to ans (all subarrays ending at current day)
  3. 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].

Sliding Window / Counter Approach
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
Why This Works: At each day 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

  1. Initialize ans = 0 and i = 0
  2. While i < n:
    • Start new segment with length L = 1
    • Move i to next day
    • While still in smooth descent (prices[i-1] - prices[i] == 1), increment L and i
    • Add L * (L + 1) / 2 to answer
  3. 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].

Mathematical Formula Approach
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
Formula Insight: For a contiguous smooth descent segment of length L, the number of subarrays is 1 + 2 + 3 + ... + L = L(L+1)/2. This approach explicitly finds each segment and applies this formula[citation:5].

Approach 3: Dynamic Programming (Full DP Array)

Traditional dynamic programming approach storing DP values for each day[citation:5].

Algorithm Steps

  1. Initialize dp array of size n with all 1's (each day is a valid period)
  2. Initialize ans = 0
  3. For each day i from 0 to n-1:
    • If i > 0 and prices[i-1] - prices[i] == 1, add dp[i-1] to dp[i]
    • Add dp[i] to ans
  4. 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].

Dynamic Programming Approach
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 Insight: This approach makes the recurrence relation explicit: 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 0: 3
Day 1: 2
Day 2: 1
Day 3: 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]
Visualizing the Process: The 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
Tag Controversy: Some argue that the "Dynamic Programming" tag for this problem is misleading, as the optimal solution is more naturally a sliding window/two pointers approach[citation:2]. While DP is possible, it's not the most intuitive solution for this problem.

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)
Integer Overflow Warning: For large arrays with long smooth descents, the count can exceed 32-bit integer range. Always use 64-bit integers (long long in C++, long in Java, int in Python handles big integers automatically).

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.

About Algopush: Algopush provides comprehensive algorithm solutions and explanations for coding interview preparation. Visit algopush.com for more problem solutions and coding resources.