Facebook Pixel

2919. Minimum Increment Operations to Make Array Beautiful

Problem Description

You have a 0-indexed integer array nums of length n and an integer k.

You can perform increment operations on the array where each operation allows you to:

  • Select any index i from 0 to n-1
  • Increase nums[i] by 1

You can perform any number of these operations (including zero).

An array is considered beautiful if every subarray of size 3 or more has its maximum element greater than or equal to k.

Your task is to find the minimum number of increment operations needed to make the array beautiful.

For example, if you have a subarray [a, b, c], then max(a, b, c) >= k must be true. This condition must hold for all possible subarrays of size 3 or more in the array.

The solution uses dynamic programming with three variables f, g, and h to track the minimum operations needed when considering the last three positions. For each element x in the array:

  • f represents the minimum operations when the element two positions back was last modified to be >= k
  • g represents the minimum operations when the element one position back was last modified to be >= k
  • h represents the minimum operations when the current element is modified to be >= k

The transitions are:

  • f' = g (shift the window)
  • g' = h (shift the window)
  • h' = min(f, g, h) + max(k - x, 0) (cost to make current element >= k)

The answer is min(f, g, h) after processing all elements, representing the minimum operations needed regardless of which of the last three positions was the last one modified.

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

Intuition

The key insight is that for any subarray of size 3 or more to have a maximum element >= k, we need at least one element in every consecutive group of 3 elements to be >= k.

Think about it this way: if we have elements at positions [i, i+1, i+2], at least one of them must be >= k. Similarly, for positions [i+1, i+2, i+3], at least one must be >= k. This pattern continues throughout the array.

This means we can't have 3 consecutive elements all less than k. So the problem reduces to: ensure that in every window of 3 consecutive elements, at least one element is >= k.

Now, how do we track this efficiently? We can use dynamic programming where we keep track of which position in each window of 3 was the last one we made >= k. This leads to three states:

  1. The element 2 positions back was the last one made >= k
  2. The element 1 position back was the last one made >= k
  3. The current element is made >= k

As we move through the array, we slide this window forward. For each new element, we have a choice:

  • Either make the current element >= k (which costs max(k - x, 0) operations)
  • Or rely on one of the previous two elements already being >= k

The minimum cost to make the current element >= k is min(f, g, h) + max(k - x, 0) because we can start from any of the three previous states and add the cost to increment the current element.

The sliding window update f' = g, g' = h simply shifts our frame of reference as we move to the next position in the array.

This approach ensures we consider all valid ways to make the array beautiful while tracking only the essential information needed to make optimal decisions.

Learn more about Dynamic Programming patterns.

Solution Approach

The solution uses dynamic programming with space optimization. Instead of using a full DP array, we only track three variables representing the last three positions.

State Definition:

  • f: minimum operations when the element at position i-2 was the last one made >= k
  • g: minimum operations when the element at position i-1 was the last one made >= k
  • h: minimum operations when the element at position i is made >= k

Initialization: We start with f = g = h = 0 since we haven't processed any elements yet.

Transition: For each element x in the array, we update our states:

f' = g
g' = h
h' = min(f, g, h) + max(k - x, 0)

The transitions work as follows:

  1. f' = g: The state where i-2 was last modified becomes the state where i-3 was last modified (sliding the window)
  2. g' = h: The state where i-1 was last modified becomes the state where i-2 was last modified
  3. h' = min(f, g, h) + max(k - x, 0): To make the current element >= k, we take the minimum cost from any previous state and add the cost to increment the current element

The cost max(k - x, 0) represents:

  • If x >= k: no operations needed (cost = 0)
  • If x < k: need k - x operations to make it equal to k

Implementation:

class Solution:
    def minIncrementOperations(self, nums: List[int], k: int) -> int:
        f = g = h = 0
        for x in nums:
            f, g, h = g, h, min(f, g, h) + max(k - x, 0)
        return min(f, g, h)

The parallel assignment f, g, h = g, h, min(f, g, h) + max(k - x, 0) ensures all updates happen simultaneously using the old values.

Final Result: After processing all elements, we return min(f, g, h) because the last element that was made >= k could be at any of the last three positions, and we want the minimum operations overall.

This approach has:

  • Time Complexity: O(n) - single pass through the array
  • Space Complexity: O(1) - only three variables regardless of input size

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through the solution with nums = [2, 3, 0, 0, 2] and k = 4.

We need to ensure every subarray of size 3 or more has at least one element >= 4.

Initial state: f = 0, g = 0, h = 0

Step 1: Process nums[0] = 2

  • Calculate new h: min(0, 0, 0) + max(4 - 2, 0) = 0 + 2 = 2
  • Update: f = 0, g = 0, h = 2
  • Meaning: If we make nums[0] >= 4, it costs 2 operations

Step 2: Process nums[1] = 3

  • Calculate new h: min(0, 0, 2) + max(4 - 3, 0) = 0 + 1 = 1
  • Update: f = 0, g = 2, h = 1
  • Meaning: Best option is to make nums[1] >= 4 (costs 1 operation)

Step 3: Process nums[2] = 0

  • Calculate new h: min(0, 2, 1) + max(4 - 0, 0) = 0 + 4 = 4
  • Update: f = 2, g = 1, h = 4
  • Meaning:
    • f = 2: If nums[0] was made >= 4 (costs 2)
    • g = 1: If nums[1] was made >= 4 (costs 1)
    • h = 4: If we make nums[2] >= 4 (costs 4)

Step 4: Process nums[3] = 0

  • Calculate new h: min(2, 1, 4) + max(4 - 0, 0) = 1 + 4 = 5
  • Update: f = 1, g = 4, h = 5
  • Meaning: Best is to have nums[1] >= 4 and make nums[3] >= 4

Step 5: Process nums[4] = 2

  • Calculate new h: min(1, 4, 5) + max(4 - 2, 0) = 1 + 2 = 3
  • Update: f = 4, g = 5, h = 3
  • Final states:
    • f = 4: If nums[2] was last made >= 4
    • g = 5: If nums[3] was last made >= 4
    • h = 3: If nums[4] is made >= 4

Answer: min(4, 5, 3) = 3

This means we need 3 increment operations total. One optimal solution is:

  • Increment nums[1] by 1 (3 → 4)
  • Increment nums[4] by 2 (2 → 4)

After these operations, nums = [2, 4, 0, 0, 4], and every subarray of size 3+ has at least one element >= 4:

  • [2, 4, 0] has 4 >= 4 ✓
  • [4, 0, 0] has 4 >= 4 ✓
  • [0, 0, 4] has 4 >= 4 ✓
  • [2, 4, 0, 0] has 4 >= 4 ✓
  • [4, 0, 0, 4] has 4 >= 4 ✓
  • [2, 4, 0, 0, 4] has 4 >= 4 ✓

Solution Implementation

1class Solution:
2    def minIncrementOperations(self, nums: List[int], k: int) -> int:
3        # Dynamic programming with space optimization
4        # dp_prev2: minimum operations needed when last operation was 2 positions back
5        # dp_prev1: minimum operations needed when last operation was 1 position back  
6        # dp_current: minimum operations needed when current position is operated on
7        dp_prev2 = 0
8        dp_prev1 = 0
9        dp_current = 0
10      
11        # Iterate through each element in the array
12        for num in nums:
13            # Calculate minimum operations needed at current position
14            # We can either operate on current position (taking min from previous 3 states)
15            # or skip it (but must ensure at least one of previous 3 was operated on)
16            increment_needed = max(k - num, 0)  # How much to increment current element
17          
18            # Update states: shift the sliding window
19            # The new current state is the minimum of all previous valid states plus current cost
20            dp_prev2, dp_prev1, dp_current = dp_prev1, dp_current, min(dp_prev2, dp_prev1, dp_current) + increment_needed
21      
22        # Return minimum among the last three states
23        # This ensures at least one of the last 3 elements meets the requirement
24        return min(dp_prev2, dp_prev1, dp_current)
25
1class Solution {
2    public long minIncrementOperations(int[] nums, int k) {
3        // Dynamic programming with space optimization
4        // We only need to track the minimum cost for the last 3 positions
5        // since we must have at least one element >= k in every 3 consecutive elements
6      
7        // dpPrev2: minimum cost to make array valid up to position i-2
8        long dpPrev2 = 0;
9        // dpPrev1: minimum cost to make array valid up to position i-1
10        long dpPrev1 = 0;
11        // dpCurrent: minimum cost to make array valid up to position i
12        long dpCurrent = 0;
13      
14        // Process each element in the array
15        for (int currentValue : nums) {
16            // Calculate the minimum cost if we make the current element >= k
17            // We can come from any of the previous 3 positions (i-3, i-2, i-1)
18            // since making current element >= k satisfies the constraint for
19            // the windows [i-2, i-1, i], [i-1, i, i+1], and [i, i+1, i+2]
20            long minPreviousCost = Math.min(Math.min(dpPrev2, dpPrev1), dpCurrent);
21          
22            // Cost to increase current element to at least k (0 if already >= k)
23            long incrementCost = Math.max(k - currentValue, 0);
24          
25            // Total cost for making current position valid
26            long newDpCurrent = minPreviousCost + incrementCost;
27          
28            // Shift the dp values for the next iteration
29            dpPrev2 = dpPrev1;
30            dpPrev1 = dpCurrent;
31            dpCurrent = newDpCurrent;
32        }
33      
34        // The answer is the minimum among the last 3 positions
35        // Any of them could be the last position where we made an element >= k
36        return Math.min(Math.min(dpPrev2, dpPrev1), dpCurrent);
37    }
38}
39
1class Solution {
2public:
3    long long minIncrementOperations(vector<int>& nums, int k) {
4        // Dynamic programming with space optimization
5        // We maintain the minimum cost for the last 3 positions
6        // dp[i-2], dp[i-1], dp[i] are represented by prev2, prev1, current respectively
7      
8        long long prev2 = 0;  // Minimum cost at position i-2
9        long long prev1 = 0;  // Minimum cost at position i-1  
10        long long current = 0; // Minimum cost at position i
11      
12        // Process each element in the array
13        for (int num : nums) {
14            // Calculate the minimum cost for making current element >= k
15            // We can choose to make any of the last 3 elements (including current) >= k
16            // So we take the minimum of the previous 3 states
17            long long nextCost = min({prev2, prev1, current}) + max(k - num, 0);
18          
19            // Shift the sliding window of states
20            prev2 = prev1;
21            prev1 = current;
22            current = nextCost;
23        }
24      
25        // Return the minimum cost among the last 3 positions
26        // Any of these could be the last element made >= k
27        return min({prev2, prev1, current});
28    }
29};
30
1/**
2 * Finds the minimum number of increment operations needed to make the array beautiful.
3 * An array is beautiful if the maximum of every three consecutive elements is at least k.
4 * 
5 * @param nums - The input array of numbers
6 * @param k - The minimum value that the maximum of every three consecutive elements should have
7 * @returns The minimum number of increment operations needed
8 */
9function minIncrementOperations(nums: number[], k: number): number {
10    // Dynamic programming states representing the minimum cost when:
11    // previousPreviousMin: minimum cost when the element two positions back was last modified
12    // previousMin: minimum cost when the previous element was last modified  
13    // currentMin: minimum cost when the current element is being modified
14    let previousPreviousMin: number = 0;
15    let previousMin: number = 0;
16    let currentMin: number = 0;
17  
18    // Iterate through each number in the array
19    for (const currentNum of nums) {
20        // Calculate the increment needed for current number to reach k
21        const incrementNeeded: number = Math.max(k - currentNum, 0);
22      
23        // Update states by shifting positions:
24        // - Previous previous becomes previous
25        // - Previous becomes current
26        // - Current is calculated as minimum of all previous states plus increment needed
27        const newCurrentMin: number = Math.min(previousPreviousMin, previousMin, currentMin) + incrementNeeded;
28      
29        // Shift the states for next iteration
30        previousPreviousMin = previousMin;
31        previousMin = currentMin;
32        currentMin = newCurrentMin;
33    }
34  
35    // Return the minimum cost among all three final states
36    return Math.min(previousPreviousMin, previousMin, currentMin);
37}
38

Time and Space Complexity

The time complexity is O(n), where n is the length of the array nums. This is because the algorithm iterates through the array exactly once with a single for loop, and within each iteration, it performs only constant-time operations (comparisons, additions, and variable assignments).

The space complexity is O(1). The algorithm uses only three variables (f, g, and h) to maintain the dynamic programming state, regardless of the input size. These variables are reused and updated throughout the iteration, requiring only constant extra space beyond the input array.

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

Common Pitfalls

Pitfall 1: Misunderstanding the State Representation

The Problem: Many developers mistakenly interpret the states f, g, h as representing whether we've modified elements at positions i-2, i-1, and i respectively. This leads to incorrect implementations where they try to track all combinations of which elements have been modified.

Wrong Approach:

# INCORRECT: Trying to track all modification combinations
for num in nums:
    if num >= k:
        # Element already satisfies condition
        f, g, h = g, h, h  # Wrong logic
    else:
        # Need to increment
        f, g, h = g, h, min(f, g) + (k - num)  # Missing cases

The Correct Understanding: The states represent the minimum operations needed when the last element that was made >= k is at that relative position. This is about tracking which position was the most recent to be modified to satisfy the constraint, not about tracking all modifications.

Solution:

# CORRECT: States represent "last modified position"
for num in nums:
    increment_needed = max(k - num, 0)
    f, g, h = g, h, min(f, g, h) + increment_needed

Pitfall 2: Incorrect Handling of Array Start

The Problem: Some implementations try to handle the first few elements specially, thinking that subarrays of size < 3 don't need to satisfy the constraint. This leads to complex initialization logic that's often wrong.

Wrong Approach:

# INCORRECT: Special handling for first elements
def minIncrementOperations(nums, k):
    n = len(nums)
    if n < 3:
        return 0  # Wrong! Still need to prepare for future elements
  
    # Complex initialization for first 3 elements
    dp = [0] * 3
    for i in range(3):
        dp[i] = max(k - nums[i], 0)
    # ... rest of the logic

The Correct Understanding: The algorithm naturally handles all cases with the same logic. Starting with f = g = h = 0 works because:

  • For the first element: we're considering making it >= k (cost stored in h)
  • For the second element: we can make either the first or second >= k
  • For the third element and beyond: full constraint applies

Solution:

# CORRECT: Simple, uniform initialization
f = g = h = 0
for num in nums:
    f, g, h = g, h, min(f, g, h) + max(k - num, 0)
return min(f, g, h)

Pitfall 3: Forgetting the Sliding Window Nature

The Problem: Developers sometimes forget that we need to maintain a sliding window of size 3, leading to implementations that don't properly shift the states or that try to look back more than 3 positions.

Wrong Approach:

# INCORRECT: Not properly shifting the window
for num in nums:
    new_h = min(f, g, h) + max(k - num, 0)
    f = f  # Wrong! Should shift
    g = g  # Wrong! Should shift
    h = new_h

The Correct Understanding: The window slides with each iteration:

  • What was at position i-1 becomes position i-2
  • What was at position i becomes position i-1
  • We calculate a new value for position i

Solution:

# CORRECT: Proper sliding window with simultaneous update
for num in nums:
    f, g, h = g, h, min(f, g, h) + max(k - num, 0)
    # This parallel assignment ensures all updates use old values

Pitfall 4: Sequential vs Parallel Assignment

The Problem: Using sequential assignment instead of parallel assignment corrupts the state values.

Wrong Approach:

# INCORRECT: Sequential assignment uses updated values
for num in nums:
    f = g  # Now f has the new value
    g = h  # Now g has the new value
    h = min(f, g, h) + max(k - num, 0)  # Uses NEW f and g, not old!

Solution:

# CORRECT: Parallel assignment preserves old values
for num in nums:
    f, g, h = g, h, min(f, g, h) + max(k - num, 0)
    # All right-hand side expressions are evaluated before any assignment

Key Takeaway

The elegance of this solution lies in its simplicity - just three variables sliding through the array. The main pitfall is overcomplicating it by misunderstanding what the states represent or how they transition. Remember: each state tracks the minimum cost when that relative position was the last one modified to be >= k.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which of the tree traversal order can be used to obtain elements in a binary search tree in sorted order?


Recommended Readings

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

Load More