Facebook Pixel

3091. Apply Operations to Make Sum of Array Greater Than or Equal to k

MediumGreedyMathEnumeration
Leetcode Link

Problem Description

You start with an array containing a single element: nums = [1].

You can perform two types of operations on this array any number of times (including zero times):

  1. Increment operation: Choose any element in the array and increase its value by 1
  2. Duplicate operation: Choose any element in the array and add a copy of it to the end of the array

Your goal is to make the sum of all elements in the array greater than or equal to a given positive integer k using the minimum number of operations.

For example, if k = 5:

  • Starting with [1]
  • You could increment the element to get [2] (1 operation)
  • Then duplicate it to get [2, 2] (2 operations total)
  • Then increment one element to get [3, 2] (3 operations total)
  • The sum is now 5, which meets the requirement

The solution approach recognizes that it's optimal to perform all increment operations first on the initial element, then use duplicate operations to multiply the effect. If we increment a times, we'll have a value of a + 1. To reach a sum of at least k, we need ⌈k/(a+1)⌉ copies of this value, which requires ⌈k/(a+1)⌉ - 1 duplicate operations. The solution enumerates all possible values of a from 0 to k-1 and finds the combination that minimizes the total number of operations a + b.

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

Intuition

The key insight is understanding the interaction between the two operations. When we increment an element, we're increasing its value. When we duplicate an element, we're adding another copy of that value to our sum.

Think about it this way: if we have an element with value x and we duplicate it to have n copies, our total sum becomes n * x. This multiplication effect is powerful - it means duplicating larger values gives us more benefit than duplicating smaller values.

This leads to an important realization: we should maximize the value of our element before we start duplicating it. Since we start with [1], we should first use increment operations to increase this value, then use duplicate operations to multiply the effect.

Consider the order of operations:

  • If we increment after duplicating, we'd need to increment multiple elements (more operations)
  • If we increment before duplicating, we only need to increment once, and the duplication multiplies this enhanced value

So our strategy becomes:

  1. First, increment the initial element a times to get value a + 1
  2. Then, duplicate this value enough times to reach or exceed k

To reach a sum of at least k with copies of value a + 1, we need ⌈k/(a+1)⌉ total copies. Since we already have one copy, we need ⌈k/(a+1)⌉ - 1 duplicate operations.

The total operations would be: a (increments) + ⌈k/(a+1)⌉ - 1 (duplicates).

Now we face a trade-off: more increments mean fewer duplicates needed, but also more operations spent on incrementing. Less increments mean more duplicates needed. The optimal point lies somewhere in between, which is why we enumerate all possibilities from 0 to k-1 increments and find the minimum total operations.

Learn more about Greedy and Math patterns.

Solution Approach

The implementation follows a straightforward enumeration strategy based on our intuition about performing increments first, then duplications.

We iterate through all possible numbers of increment operations from 0 to k-1. For each value of a (number of increments):

  1. Calculate the resulting value after increments: After a increment operations on our initial value of 1, we get x = a + 1.

  2. Calculate required duplications: To reach a sum of at least k with value x, we need ⌈k/x⌉ total copies. The ceiling division is implemented as (k + x - 1) // x, which is a common technique to avoid floating-point operations. Since we already have one copy, the number of duplicate operations needed is b = (k + x - 1) // x - 1.

  3. Track the minimum: For each combination, we calculate the total operations as a + b and keep track of the minimum across all possibilities.

The algorithm structure:

ans = k  # Initialize with worst case (all increments, no duplicates)
for a in range(k):  # Try 0 to k-1 increments
    x = a + 1  # Value after a increments
    b = (k + x - 1) // x - 1  # Number of duplicates needed
    ans = min(ans, a + b)  # Update minimum

Why do we only check up to k-1 increments? If we increment k-1 times, we get value k, which already meets our requirement with just one element (no duplicates needed). Incrementing more would be wasteful since we'd exceed k with a single element while using more operations.

The time complexity is O(k) since we iterate through k possible values, and the space complexity is O(1) as we only use a constant amount of extra space.

Ready to land your dream job?

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

Start Evaluator

Example Walkthrough

Let's walk through finding the minimum operations needed when k = 11.

We start with nums = [1] and need to reach a sum ≥ 11.

We'll try different numbers of increment operations (a) and calculate the corresponding duplicate operations (b) needed:

Case 1: a = 0 (no increments)

  • Value after increments: x = 0 + 1 = 1
  • Copies needed: ⌈11/1⌉ = 11 copies
  • Duplicate operations: b = 11 - 1 = 10
  • Total operations: 0 + 10 = 10
  • Array progression: [1] → [1,1] → [1,1,1] → ... → [1,1,1,1,1,1,1,1,1,1,1]

Case 2: a = 1 (one increment)

  • Value after increments: x = 1 + 1 = 2
  • Copies needed: ⌈11/2⌉ = 6 copies
  • Duplicate operations: b = 6 - 1 = 5
  • Total operations: 1 + 5 = 6
  • Array progression: [1] → [2] → [2,2] → [2,2,2] → ... → [2,2,2,2,2,2] (sum = 12)

Case 3: a = 2 (two increments)

  • Value after increments: x = 2 + 1 = 3
  • Copies needed: ⌈11/3⌉ = 4 copies
  • Duplicate operations: b = 4 - 1 = 3
  • Total operations: 2 + 3 = 5
  • Array progression: [1] → [2] → [3] → [3,3] → [3,3,3] → [3,3,3,3] (sum = 12)

Case 4: a = 3 (three increments)

  • Value after increments: x = 3 + 1 = 4
  • Copies needed: ⌈11/4⌉ = 3 copies
  • Duplicate operations: b = 3 - 1 = 2
  • Total operations: 3 + 2 = 5
  • Array progression: [1] → [2] → [3] → [4] → [4,4] → [4,4,4] (sum = 12)

Case 5: a = 4 (four increments)

  • Value after increments: x = 4 + 1 = 5
  • Copies needed: ⌈11/5⌉ = 3 copies
  • Duplicate operations: b = 3 - 1 = 2
  • Total operations: 4 + 2 = 6

Case 6: a = 5 (five increments)

  • Value after increments: x = 5 + 1 = 6
  • Copies needed: ⌈11/6⌉ = 2 copies
  • Duplicate operations: b = 2 - 1 = 1
  • Total operations: 5 + 1 = 6

As we continue this pattern, the total operations start increasing again. The minimum occurs at both a = 2 and a = 3, each requiring 5 total operations.

The optimal solution is to either:

  • Increment twice to get [3], then duplicate 3 times to get [3,3,3,3]
  • OR increment three times to get [4], then duplicate 2 times to get [4,4,4]

Both achieve a sum ≥ 11 with the minimum of 5 operations.

Solution Implementation

1class Solution:
2    def minOperations(self, k: int) -> int:
3        # Initialize the answer with k (worst case: increment from 1 to k)
4        min_operations = k
5      
6        # Try different numbers of increment operations
7        for num_increments in range(k):
8            # Calculate the value after performing num_increments operations
9            # Starting from 1, after num_increments we have value (num_increments + 1)
10            current_value = num_increments + 1
11          
12            # Calculate how many duplication operations are needed
13            # We need to reach at least k, so we need k/current_value copies
14            # Using ceiling division: (k + current_value - 1) // current_value
15            # Subtract 1 because we already have one copy
16            num_duplications = (k + current_value - 1) // current_value - 1
17          
18            # Total operations = increment operations + duplication operations
19            total_operations = num_increments + num_duplications
20          
21            # Update minimum operations
22            min_operations = min(min_operations, total_operations)
23      
24        return min_operations
25
1class Solution {
2    public int minOperations(int k) {
3        // Initialize minimum operations to k (worst case: increment 1 to k without any duplication)
4        int minOps = k;
5      
6        // Try different numbers of increment operations
7        for (int incrementOps = 0; incrementOps < k; ++incrementOps) {
8            // After 'incrementOps' increments, we have value (1 + incrementOps)
9            int currentValue = incrementOps + 1;
10          
11            // Calculate minimum duplication operations needed to reach at least k
12            // Using ceiling division: (k + currentValue - 1) / currentValue gives us
13            // the minimum number of copies needed, then subtract 1 for duplication operations
14            int duplicationOps = (k + currentValue - 1) / currentValue - 1;
15          
16            // Update minimum with total operations (increments + duplications)
17            minOps = Math.min(minOps, incrementOps + duplicationOps);
18        }
19      
20        return minOps;
21    }
22}
23
1class Solution {
2public:
3    int minOperations(int k) {
4        // Initialize the minimum operations to k (worst case scenario)
5        int minOps = k;
6      
7        // Try different numbers of increment operations
8        for (int incrementOps = 0; incrementOps < k; ++incrementOps) {
9            // After 'incrementOps' increments, we have value (incrementOps + 1)
10            int currentValue = incrementOps + 1;
11          
12            // Calculate how many duplication operations are needed
13            // to reach at least k with current value
14            // Using ceiling division: (k + currentValue - 1) / currentValue
15            // Then subtract 1 since we already have one instance
16            int duplicationOps = (k + currentValue - 1) / currentValue - 1;
17          
18            // Total operations = increment operations + duplication operations
19            int totalOps = incrementOps + duplicationOps;
20          
21            // Update minimum if we found a better solution
22            minOps = min(minOps, totalOps);
23        }
24      
25        return minOps;
26    }
27};
28
1/**
2 * Calculates the minimum number of operations to reach a target value k
3 * by finding the optimal combination of additions and multiplications
4 * 
5 * @param k - The target value to reach
6 * @returns The minimum number of operations required
7 */
8function minOperations(k: number): number {
9    // Initialize the answer with k (worst case: all additions)
10    let minOperationsCount: number = k;
11  
12    // Try different numbers of addition operations
13    for (let additionOps: number = 0; additionOps < k; ++additionOps) {
14        // Current value after 'additionOps' additions (starting from 1)
15        const currentValue: number = additionOps + 1;
16      
17        // Calculate required multiplication operations to reach k
18        // ceil(k / currentValue) gives the multiplier needed, subtract 1 for operations count
19        const multiplicationOps: number = Math.ceil(k / currentValue) - 1;
20      
21        // Total operations = additions + multiplications
22        const totalOps: number = additionOps + multiplicationOps;
23      
24        // Update minimum if we found a better solution
25        minOperationsCount = Math.min(minOperationsCount, totalOps);
26    }
27  
28    return minOperationsCount;
29}
30

Time and Space Complexity

The time complexity is O(k), where k is the input positive integer. The algorithm iterates through a loop from 0 to k-1 (total of k iterations). Within each iteration, it performs constant time operations: calculating x = a + 1, computing b = (k + x - 1) // x - 1, and updating the minimum value with min(ans, a + b). Since the loop runs k times and each iteration takes O(1) time, the overall time complexity is O(k).

The space complexity is O(1). The algorithm only uses a fixed number of variables (ans, a, x, and b) regardless of the input size k. No additional data structures that grow with the input are created, so the space usage remains constant.

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

Common Pitfalls

1. Off-by-One Error in Duplication Count

A common mistake is incorrectly calculating the number of duplication operations needed. Since we start with one element already in the array, we need ⌈k/x⌉ - 1 duplications, not ⌈k/x⌉.

Incorrect Implementation:

# Wrong: Forgetting to subtract 1 for the initial element
num_duplications = (k + current_value - 1) // current_value

Correct Implementation:

# Correct: Subtract 1 because we already have one copy
num_duplications = (k + current_value - 1) // current_value - 1

2. Incorrect Loop Bounds

Another pitfall is setting incorrect bounds for the increment operations loop. Some might mistakenly iterate from 0 to k (inclusive) or start from 1 instead of 0.

Incorrect Implementation:

# Wrong: Including k increments is unnecessary
for num_increments in range(k + 1):
    # This checks one extra unnecessary case

Correct Implementation:

# Correct: 0 to k-1 increments covers all useful cases
for num_increments in range(k):
    # After k-1 increments, we have value k (no duplicates needed)

3. Integer Division vs Ceiling Division

Using floor division when ceiling division is needed will produce incorrect results for cases where k is not perfectly divisible by the current value.

Incorrect Implementation:

# Wrong: Using floor division
num_duplications = k // current_value - 1
# For k=5, current_value=2: gives 5//2 - 1 = 2 - 1 = 1 (incorrect)

Correct Implementation:

# Correct: Using ceiling division
num_duplications = (k + current_value - 1) // current_value - 1
# For k=5, current_value=2: gives (5+2-1)//2 - 1 = 6//2 - 1 = 3 - 1 = 2 (correct)

4. Edge Case: k = 1

When k = 1, the array already satisfies the condition (sum = 1 ≥ 1), so no operations are needed. The solution handles this correctly by initializing with the worst case and checking from 0 increments, but explicitly handling this case can improve clarity.

Enhanced Implementation:

def minOperations(self, k: int) -> int:
    if k == 1:
        return 0
  
    min_operations = k
    for num_increments in range(k):
        # ... rest of the logic
Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

Is the following code DFS or BFS?

void search(Node root) {
  if (!root) return;
  visit(root);
  root.visited = true;
  for (Node node in root.adjacent) {
    if (!node.visited) {
      search(node);
    }
  }
}

Recommended Readings

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

Load More