Monotonic Stack: Steps to Make Array Non-decreasing

Problem

Given an integer array A, in one step, remove all elements A[i] where A[i-1] > A[i]. Return the number of steps performed until A becomes a non-decreasing array. See examples at LeetCode 2289.

Solution

The naive approach executes steps one by one. Store integers in a Linked List. At each step, find all integers that are smaller than its left neighbor and remove them. However, the time complexity is O(n^2) because each step may remove just one integer. For example, [1000, 1, 2, 3, ...., 999].

We need to look for key observations. First, it is easy to know which integers needs to be removed. All integers that is not in the final non-decreasing array needs to go. Moreover, the remaining integers separate the original array into subarrays and each each subarray can go through the removal steps independently. For example, [5, 4, 3, 2, 11, 10, 8, 9 11] has two subarrays to remove, [4, 3, 2] and [10, 8, 9]. This observation means that we can start refresh whenever we see an integer than cannot be removed.

Let f[i] be the order of step when A[i] is removed. If there is a longest decreasing subarray ends at A[j], say, A[i] > A[i+1] > A[i+2] > ... > A[j] and A[i] <= A[j+1], then f[i+1]=f[i+2]=...=f[j] and f[j+1]=f[i+1]+1. This observation of longest deceasing subarray can be applied recursively, after removal of A[i+1:j]. In order to handle the "removal" recursively, we use a stack. In order to calculate f[i], we store the index of the array in the stack.

 1func totalSteps(a []int) int {
 2  n := len(a)
 3  f := make([]int, n)
 4  stack := []int{}
 5  maxStep := 0
 6  for i, v := range a {
 7    step := 0
 8    // pop from stack if needed, to make sure a[i] can be appended to the decreasing stack.
 9    for {
10      m := len(stack)
11      if m == 0 || v < a[stack[m-1]] {
12        break
13      }
14      step = max(step, f[stack[m-1]]+1)
15      stack = stack[:m-1]
16    }
17
18    if len(stack) > 0 {
19      f[i] = max(1, step) // a[i] needs to be removed, so f[i] is at least 1.
20      maxStep = max(maxStep, f[i])
21    } else {
22      f[i] = 0 // a[i] does not need to be removed because there is no larger integer on its left side.
23    }
24
25    // push to stack
26    stack = append(stack, i)
27  }
28  return maxStep
29}

Appendix: A series of posts on monotonicity.

I'm currently open to engineer opportunities. If you liked this post and would like to have me on your team, reach out on X, LinkedIn or GitHub.