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.