Facebook Pixel

3914. Minimum Operations to Make Array Non Decreasing

LeetCode ↗

Problem Description

You are given an integer array nums of length n.

In one operation, you may choose any subarray nums[l..r] and increase each element in that subarray by x, where x is any positive integer.

Return the minimum possible sum of the values of x across all operations required to make the array non-decreasing.

An array is non-decreasing if nums[i] <= nums[i + 1] for all 0 <= i < n - 1.

In simpler terms, you start with an array and want to transform it into a non-decreasing array. Your only tool is picking a contiguous segment (a subarray) and adding the same positive value x to every element in that segment. Each time you do this, the cost is x. You can perform as many operations as you want, choosing different subarrays and different values of x each time. The goal is to find the smallest total cost (the sum of all the x values used) needed so that, after all operations, no element is greater than the element immediately following it.

Note that the operation can only increase elements, never decrease them. This means that whenever there is a "drop" in the array (where nums[i] > nums[i + 1]), you must raise the later elements to close that gap. Since you can always extend a subarray to include everything from a certain point to the end of the array, lifting later elements never harms earlier comparisons — so the only real requirement is to cover each individual drop between adjacent elements.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

How We Pick the Algorithm

Why Greedy Algorithms?

This problem maps to Greedy Algorithms through a short path in the full flowchart.

Computemax/min?yesGreedysolution?yesGreedyAlgorithms

Choosing the locally optimal option at each step builds the globally optimal result.

Open in Flowchart

Intuition

The key observation is that our operation can only add value to elements, never remove it. So whenever we encounter a place where the array decreases — that is, where nums[i] > nums[i + 1] — we are forced to lift nums[i + 1] (and possibly elements after it) upward to repair that drop.

Let's think about how much we must lift at each position. Consider any adjacent pair nums[i] and nums[i + 1]:

  • If nums[i + 1] >= nums[i], this pair is already in the correct order, and no operation is needed here.
  • If nums[i + 1] < nums[i], there is a "drop" of size nums[i] - nums[i + 1]. To fix it, the element at position i + 1 must be raised by at least nums[i] - nums[i + 1] so that it reaches the level of nums[i].

Now, why can we treat each drop independently and simply add them all up? Because of a very helpful freedom: a subarray can always be chosen to extend all the way to the end of the array. When we raise nums[i + 1], we can raise every element from i + 1 to the last index by the same amount. Lifting later elements by extra amounts never breaks any earlier ordering — it only pushes the tail of the array higher, which keeps the right side "tall enough." This means fixing one drop never creates a new drop somewhere else.

Because every drop can be repaired without interfering with the others, the minimum total cost is just the sum of the sizes of all the drops. For each adjacent pair, the contribution is max(nums[i] - nums[i + 1], 0) — we pay for the gap when there is a drop, and pay nothing when the order is already fine.

Summing this quantity over all adjacent pairs gives exactly the minimum total x we must spend, which leads directly to the one-line greedy solution.

Pattern Learn more about Greedy patterns.

Solution Approach

Solution 1: Greedy

We can traverse the array from left to right and calculate the difference between each pair of adjacent elements. If the current element is smaller than the previous one, we need to increase the current element so that it is at least equal to the previous element. The amount to increase is the difference between the previous element and the current element.

Let's break down how this translates into code:

  1. Iterate over adjacent pairs. We walk through every consecutive pair (a, b) in the array, where a = nums[i] and b = nums[i + 1]. In Python, the pairwise(nums) helper conveniently yields these pairs one at a time: (nums[0], nums[1]), (nums[1], nums[2]), and so on.

  2. Measure each drop. For each pair (a, b), we compute max(a - b, 0):

    • When a > b, the array drops by a - b at this position, so this is the amount we must add to repair the gap.
    • When a <= b, the pair is already in non-decreasing order, so a - b is zero or negative, and max(a - b, 0) correctly contributes 0.
  3. Accumulate the total. We add up all these per-pair contributions with sum(...). As reasoned earlier, each drop can be fixed independently without affecting the others, so this sum is exactly the minimum total value of x required.

This gives the compact one-liner:

class Solution:
    def minOperations(self, nums: list[int]) -> int:
        return sum(max(a - b, 0) for a, b in pairwise(nums))

Complexity Analysis:

  • Time complexity: O(n), where n is the length of nums. We make a single pass over the array, processing each adjacent pair once.
  • Space complexity: O(1). We only keep a running sum; the generator expression produces pairs lazily without building any extra list.

Example Walkthrough

Let's trace through the solution with a small example: nums = [4, 1, 3, 2].

Our goal is to make the array non-decreasing by repeatedly picking a subarray and adding a positive value x to it, minimizing the total of all x values. According to the greedy approach, we only need to sum up the size of every "drop" between adjacent elements.

Step 1: Walk through adjacent pairs.

The pairwise(nums) helper produces these consecutive pairs:

  • (4, 1) → from index 0 to index 1
  • (1, 3) → from index 1 to index 2
  • (3, 2) → from index 2 to index 3

Step 2: Measure each drop with max(a - b, 0).

Pair (a, b)a - bmax(a - b, 0)Reasoning
(4, 1)33A drop of size 3: 1 must rise to at least 4.
(1, 3)-20Already non-decreasing (1 <= 3), no cost.
(3, 2)11A drop of size 1: 2 must rise to at least 3.

Step 3: Accumulate the total.

Summing the per-pair contributions: 3 + 0 + 1 = 4. So the minimum total cost is 4.

Verifying with actual operations.

Let's confirm this is achievable, using the trick that subarrays can always extend to the end of the array:

  1. Fix the drop at index 1 (4 > 1). Add x = 3 to the subarray nums[1..3]: [4, 1, 3, 2][4, 4, 6, 5]. Cost so far: 3.

    Notice we lifted everything from index 1 onward, so the tail stays "tall enough" and we don't disturb the 4 <= 4 ordering at the front.

  2. Fix the drop at index 3 (6 > 5). Add x = 1 to the subarray nums[3..3]: [4, 4, 6, 5][4, 4, 6, 6]. Cost so far: 3 + 1 = 4.

The final array [4, 4, 6, 6] is non-decreasing, and the total cost is 4 — exactly matching our computed answer. Each drop was repaired independently, and fixing one never reintroduced a drop elsewhere, which is precisely why simply summing the drops yields the minimum.

Solution Implementation

1from itertools import pairwise
2
3
4class Solution:
5    def minOperations(self, nums: list[int]) -> int:
6        # Track the total number of operations needed.
7        # For each adjacent pair (prev, curr), if prev > curr it means the
8        # previous element was raised higher than the current one. The extra
9        # height (prev - curr) represents operations that must be "spent"
10        # before reaching curr, since they cannot be reused for a lower value.
11        total_operations = 0
12
13        # pairwise(nums) yields consecutive pairs: (nums[0], nums[1]), (nums[1], nums[2]), ...
14        for prev, curr in pairwise(nums):
15            # Only count the positive difference; if curr >= prev, no extra
16            # operations are required because we can build up incrementally.
17            total_operations += max(prev - curr, 0)
18
19        return total_operations
20```
21
22A few notes on the changes:
23
241. **Explicit import**: Added `from itertools import pairwise` so the code is self-contained and runnable (available in Python 3.10+).
25
262. **Standardized naming**: Renamed the loop variables `a, b` to the more descriptive `prev, curr`, and replaced the inline generator expression's implicit accumulation with a clearly named `total_operations` accumulator.
27
283. **Comments**: Added explanations describing the intent of the algorithm—accumulating the positive "drops" between consecutive elements.
29
30If you prefer to keep the original concise one-liner style while still adding the import, here's an alternative:
31
32```python3
33from itertools import pairwise
34
35
36class Solution:
37    def minOperations(self, nums: list[int]) -> int:
38        # Sum the positive differences between each element and the next one,
39        # representing operations that cannot carry over to smaller values.
40        return sum(max(prev - curr, 0) for prev, curr in pairwise(nums))
41
1class Solution {
2    /**
3     * Calculates the minimum number of operations required.
4     * For each adjacent pair, if the previous element is greater than the current one,
5     * we accumulate the difference (the amount by which we must increase the current element
6     * to make the sequence non-decreasing up to that point).
7     *
8     * @param nums the input array of integers
9     * @return the total minimum number of operations as a long value
10     */
11    public long minOperations(int[] nums) {
12        // Accumulator for the total number of operations.
13        long totalOperations = 0;
14
15        // Iterate from the second element to the end of the array.
16        for (int i = 1; i < nums.length; ++i) {
17            // The previous element in the array.
18            int previous = nums[i - 1];
19
20            // The current element in the array.
21            int current = nums[i];
22
23            // If the previous element is larger than the current one,
24            // add the difference; otherwise add zero (no operation needed).
25            totalOperations += Math.max(previous - current, 0);
26        }
27
28        // Return the accumulated total number of operations.
29        return totalOperations;
30    }
31}
32
1class Solution {
2public:
3    long long minOperations(vector<int>& nums) {
4        // Total number of operations needed
5        long long totalOperations = 0;
6
7        // Iterate from the second element to the end
8        for (int i = 1; i < nums.size(); ++i) {
9            // If the previous element is greater than the current one,
10            // we need (nums[i - 1] - nums[i]) operations to raise the
11            // current element up to the previous level; otherwise 0.
12            totalOperations += max(nums[i - 1] - nums[i], 0);
13        }
14
15        // Return the accumulated number of operations
16        return totalOperations;
17    }
18};
19
1/**
2 * Calculates the minimum number of operations needed to make the array
3 * non-decreasing. For each adjacent pair, if the previous element is greater
4 * than the current one, the difference must be added to the current element
5 * (and all preceding adjustments accumulate into the running total).
6 *
7 * @param nums - The input array of numbers.
8 * @returns The total number of operations required.
9 */
10function minOperations(nums: number[]): number {
11    // Accumulator for the total number of operations.
12    let answer = 0;
13
14    // Iterate from the second element to the end of the array.
15    for (let i = 1; i < nums.length; ++i) {
16        // If the previous element is larger than the current one,
17        // add the positive difference; otherwise add zero.
18        answer += Math.max(nums[i - 1] - nums[i], 0);
19    }
20
21    // Return the accumulated number of operations.
22    return answer;
23}
24

Time and Space Complexity

Time Complexity: O(n), where n is the length of the array nums.

The function uses pairwise(nums), which generates n - 1 consecutive pairs (a, b) from the array. For each pair, it performs a constant-time computation max(a - b, 0). These values are then summed using a generator expression, which iterates exactly once over all n - 1 pairs. Since the work per pair is O(1) and there are n - 1 pairs, the total time complexity is O(n).

Space Complexity: O(1).

The pairwise function returns an iterator that produces pairs lazily, so it does not allocate additional space proportional to the input size. The sum function accumulates the result into a single running total, and only a constant number of variables (a, b, and the accumulator) are used at any time. Therefore, the extra space used is constant, giving a space complexity of O(1).

Pattern Learn more about how to find time and space complexity quickly.

Common Pitfalls

Pitfall 1: Adding Up All Differences Instead of Just the Drops

A very common mistake is to accumulate the absolute difference between every adjacent pair, or to track every change in value rather than only the decreases. The cost is determined exclusively by how much the array drops between consecutive elements — increases cost nothing.

Incorrect code:

class Solution:
    def minOperations(self, nums: list[int]) -> int:
        # WRONG: sums absolute differences, charging for "rises" too.
        return sum(abs(prev - curr) for prev, curr in pairwise(nums))

For nums = [1, 5, 2], this wrongly returns |1-5| + |5-2| = 4 + 3 = 7. The rise from 1 to 5 requires no operation at all; only the drop from 5 to 2 matters, so the correct answer is 3.

Fix: Clamp each contribution at zero with max(prev - curr, 0), so rises (where prev - curr is negative) add nothing:

class Solution:
    def minOperations(self, nums: list[int]) -> int:
        return sum(max(prev - curr, 0) for prev, curr in pairwise(nums))

Pitfall 2: Reversing the Subtraction Order

It is easy to confuse which element should be subtracted from which. The drop you must repair is prev - curr (the previous, larger element minus the current, smaller one). Writing curr - prev measures the rise, not the drop, and yields a completely wrong total.

Incorrect code:

class Solution:
    def minOperations(self, nums: list[int]) -> int:
        # WRONG: this measures increases, not the drops we must repair.
        return sum(max(curr - prev, 0) for prev, curr in pairwise(nums))

For nums = [3, 1, 2], this returns max(1-3,0) + max(2-1,0) = 0 + 1 = 1, but the actual answer is 2 (the drop from 3 to 1).

Fix: Always subtract the current element from the previous one: max(prev - curr, 0).


Pitfall 3: pairwise Availability and Empty/Single-Element Inputs

itertools.pairwise was introduced in Python 3.10. On older interpreters (including some judge environments), importing it raises an ImportError. Relying on it without a fallback can cause the solution to fail outright.

Fix: Provide a manual fallback, which also naturally handles arrays of length 0 or 1 (where there are no adjacent pairs, so the answer is 0):

try:
    from itertools import pairwise
except ImportError:
    def pairwise(iterable):
        it = iter(iterable)
        prev = next(it, None)
        for curr in it:
            yield prev, curr
            prev = curr


class Solution:
    def minOperations(self, nums: list[int]) -> int:
        return sum(max(prev - curr, 0) for prev, curr in pairwise(nums))

Equivalently, you can avoid pairwise entirely with index-based iteration:

class Solution:
    def minOperations(self, nums: list[int]) -> int:
        return sum(max(nums[i] - nums[i + 1], 0) for i in range(len(nums) - 1))

When len(nums) <= 1, range(len(nums) - 1) is empty, so the sum is correctly 0.


Pitfall 4: Overthinking It With Complex Simulation

Because the problem talks about "subarrays" and "operations," there is a temptation to actually simulate picking subarrays and applying increments, or to reach for a stack/DP solution. This adds needless complexity and risks O(n²) or worse behavior.

Fix: Recognize the key insight stated in the problem: since you can always extend any subarray all the way to the end of the array, lifting later elements never disturbs earlier comparisons. Each drop is therefore independent and can be repaired on its own, reducing the whole task to a single O(n) greedy pass summing the drops. Trust the greedy reduction rather than simulating the operations.

Ready to land your dream job?

Unlock your dream job with a 5-minute quiz for a personalized study roadmap!

Get My Roadmap
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Get a Personalized Study Roadmap:

Which data structure is used to implement recursion?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More