Facebook Pixel

1354. Construct Target Array With Multiple Sums

Problem Description

You start with an array arr containing n ones: [1, 1, 1, ..., 1]. Your goal is to transform this array into a given target array of n integers.

The transformation follows these rules:

  1. Calculate x as the sum of all elements currently in your array
  2. Choose any index i (where 0 <= i < n) and replace the value at that index with x
  3. Repeat this process as many times as needed

You need to determine if it's possible to transform the initial array of ones into the target array using these operations. Return true if the transformation is possible, otherwise return false.

For example, if you start with [1, 1, 1]:

  • Sum is 3, you could replace index 0 to get [3, 1, 1]
  • Sum is 5, you could replace index 1 to get [3, 5, 1]
  • Sum is 9, you could replace index 2 to get [3, 5, 9]

The solution works backwards from the target array. Since each operation replaces an element with the sum of all elements, the largest element in the current array must have been the most recently modified. By reversing this process, we can check if we can reach the initial array of all ones. The algorithm uses a max heap to efficiently track the largest element and simulates the reverse operations until either we reach all ones (return true) or determine it's impossible (return false).

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

Intuition

The key insight is that working forward from the initial array [1, 1, 1, ...] to the target array is difficult because at each step, we have multiple choices for which index to update, and it's unclear which choice leads to the target.

However, if we think about the problem in reverse, starting from the target array and working backwards to [1, 1, 1, ...], the problem becomes much clearer. Why? Because in any array state, the largest element must have been the most recently created element. This is because when we replace an element with the sum of all elements, the new value is always larger than any individual element that existed before.

Consider how an element becomes the largest: if we have an array and perform the operation, the element we just updated becomes the sum of all elements, which is guaranteed to be the largest. So when looking at any array state, we can confidently say the largest element was produced in the most recent operation.

Working backwards, if the current largest element is mx and it was created by summing all elements, then before this operation:

  • The element at this position had some smaller value
  • The sum of all other elements was t = current_sum - mx
  • The original value at this position was mx - t

We can repeatedly "undo" operations by:

  1. Finding the largest element mx
  2. Computing what it was before: mx - t where t is the sum of all other elements
  3. Replacing mx with this previous value

If at any point we get invalid values (negative or zero), we know the transformation is impossible. We continue this reverse process until all elements become 1, indicating we've successfully traced back to the initial state.

The modulo operation mx % t in the solution is an optimization - instead of repeatedly subtracting t from mx until we get a valid value, we can directly compute the remainder, which gives us the value before multiple operations were applied consecutively to the same index.

Learn more about Heap (Priority Queue) patterns.

Solution Approach

The solution implements the reverse construction approach using a max heap (priority queue) to efficiently track and extract the largest element at each step.

Data Structures Used:

  • Max Heap: Python's heapq provides a min heap by default, so we negate all values to simulate a max heap
  • Variable s: Tracks the current sum of all elements in the array

Implementation Steps:

  1. Initialization:

    • Calculate the initial sum s = sum(target)
    • Create a max heap by negating all elements: pq = [-x for x in target]
    • Convert the list to a heap structure: heapify(pq)
  2. Main Loop (while -pq[0] > 1):

    • Continue while the largest element is greater than 1
    • Extract the largest element: mx = -heappop(pq)
    • Calculate the sum of remaining elements: t = s - mx
  3. Validation Checks:

    • If t == 0: This means all other elements sum to 0, which is impossible since we started with positive integers
    • If mx - t < 1: The previous value would be less than 1, which violates our starting condition
    • Return False if either condition is met
  4. Calculate Previous Value:

    • Instead of repeatedly subtracting t from mx, use modulo: x = mx % t
    • Special case: If mx % t == 0, set x = t (this handles the case where mx is exactly divisible by t)
    • This optimization handles cases where the same index was updated multiple times consecutively
  5. Update State:

    • Push the calculated previous value back: heappush(pq, -x)
    • Update the sum: s = s - mx + x (remove mx, add x)
  6. Termination:

    • If the loop completes (all elements become 1), return True
    • The array has been successfully traced back to [1, 1, 1, ...]

Time Complexity: O(n log n) for initial heapification, and each operation involves heap operations which are O(log n)

Space Complexity: O(n) for storing the heap

The elegance of this solution lies in recognizing that the largest element uniquely identifies the last operation, allowing us to deterministically work backwards and verify if the transformation is possible.

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 trace through the algorithm with target = [9, 3, 5].

Initial Setup:

  • Start with array of ones: [1, 1, 1]
  • Can we reach [9, 3, 5]? Let's work backwards.
  • Sum s = 9 + 3 + 5 = 17
  • Max heap (negated): [-9, -3, -5]

Step 1: Process largest element (9)

  • Extract max: mx = 9
  • Sum of others: t = s - mx = 17 - 9 = 8
  • Previous value: mx % t = 9 % 8 = 1
  • Update array state: [1, 3, 5]
  • Update sum: s = 17 - 9 + 1 = 9
  • Heap becomes: [-5, -3, -1]

Step 2: Process largest element (5)

  • Extract max: mx = 5
  • Sum of others: t = s - mx = 9 - 5 = 4
  • Previous value: mx % t = 5 % 4 = 1
  • Update array state: [1, 3, 1]
  • Update sum: s = 9 - 5 + 1 = 5
  • Heap becomes: [-3, -1, -1]

Step 3: Process largest element (3)

  • Extract max: mx = 3
  • Sum of others: t = s - mx = 5 - 3 = 2
  • Previous value: mx % t = 3 % 2 = 1
  • Update array state: [1, 1, 1]
  • Update sum: s = 5 - 3 + 1 = 3
  • Heap becomes: [-1, -1, -1]

Result: All elements are now 1, so we return true.

Forward Verification: Working forward from [1, 1, 1]:

  1. Sum = 3, replace index 2: [1, 1, 3]
  2. Sum = 5, replace index 1: [1, 5, 3]
  3. Sum = 9, replace index 1: [1, 9, 3]
  4. Sum = 13, replace index 0: [13, 9, 3]
  5. Sum = 25, replace index 0: [25, 9, 3]
  6. Continue... eventually reaching [9, 3, 5]

The backward approach efficiently validates that the transformation is possible without needing to explore all forward paths.

Solution Implementation

1from typing import List
2from heapq import heapify, heappop, heappush
3
4class Solution:
5    def isPossible(self, target: List[int]) -> bool:
6        """
7        Determine if we can transform an array of all 1s to the target array.
8        In each operation, we can replace one element with the sum of all elements.
9      
10        Working backwards: the largest element must have been formed by summing all others.
11        So we reverse the operation by subtracting the sum of others from the largest.
12        """
13        # Calculate total sum of all elements
14        total_sum = sum(target)
15      
16        # Create max heap (negate values since Python has min heap by default)
17        max_heap = [-num for num in target]
18        heapify(max_heap)
19      
20        # Keep reducing the largest element until all elements become 1
21        while -max_heap[0] > 1:
22            # Get the current maximum element
23            current_max = -heappop(max_heap)
24          
25            # Calculate sum of all other elements
26            sum_of_others = total_sum - current_max
27          
28            # Edge cases where transformation is impossible
29            if sum_of_others == 0 or current_max - sum_of_others < 1:
30                return False
31          
32            # Calculate the previous value before this element became current_max
33            # Use modulo for optimization when current_max >> sum_of_others
34            # If remainder is 0, the previous value was sum_of_others
35            previous_value = (current_max % sum_of_others) or sum_of_others
36          
37            # Push the previous value back to heap
38            heappush(max_heap, -previous_value)
39          
40            # Update total sum
41            total_sum = total_sum - current_max + previous_value
42      
43        # All elements are now 1, transformation is possible
44        return True
45
1class Solution {
2    public boolean isPossible(int[] target) {
3        // Use max heap to always process the largest element
4        PriorityQueue<Long> maxHeap = new PriorityQueue<>(Collections.reverseOrder());
5        long totalSum = 0;
6      
7        // Initialize heap with all target values and calculate total sum
8        for (int value : target) {
9            totalSum += value;
10            maxHeap.offer((long) value);
11        }
12      
13        // Work backwards from target array to see if we can reach all 1s
14        while (maxHeap.peek() > 1) {
15            // Get the largest element in current array
16            long maxElement = maxHeap.poll();
17          
18            // Calculate sum of remaining elements
19            long remainingSum = totalSum - maxElement;
20          
21            // Edge cases where transformation is impossible
22            // If remaining sum is 0 or if we can't form a positive value
23            if (remainingSum == 0 || maxElement - remainingSum < 1) {
24                return false;
25            }
26          
27            // Calculate the value before the last operation(s)
28            // Using modulo to handle multiple operations at once
29            long previousValue = maxElement % remainingSum;
30          
31            // If modulo is 0, the previous value should be remainingSum
32            if (previousValue == 0) {
33                previousValue = remainingSum;
34            }
35          
36            // Add the calculated previous value back to heap
37            maxHeap.offer(previousValue);
38          
39            // Update total sum by replacing maxElement with previousValue
40            totalSum = totalSum - maxElement + previousValue;
41        }
42      
43        // If we reached here, all elements are 1
44        return true;
45    }
46}
47
1class Solution {
2public:
3    bool isPossible(vector<int>& target) {
4        // Use max heap to always get the largest element
5        priority_queue<int> maxHeap;
6        long long totalSum = 0;
7      
8        // Initialize heap with all target values and calculate total sum
9        for (int i = 0; i < target.size(); i++) {
10            totalSum += target[i];
11            maxHeap.push(target[i]);
12        }
13      
14        // Work backwards from target array to [1,1,...,1]
15        // In each step, we reverse the operation that created the largest element
16        while (maxHeap.top() != 1) {
17            int largestElement = maxHeap.top();
18            maxHeap.pop();
19          
20            // Calculate sum of remaining elements (excluding the largest)
21            long long remainingSum = totalSum - largestElement;
22          
23            // Check if backward operation is valid
24            // remainingSum must be at least 1, and the previous value must be positive
25            if (remainingSum < 1 || largestElement - remainingSum < 1) {
26                return false;
27            }
28          
29            // Calculate the previous value before it became largestElement
30            // Previous value = largestElement - remainingSum (for single step)
31            // For multiple steps: use modulo to skip repeated subtractions
32            int previousValue = largestElement % remainingSum;
33          
34            // Handle edge case: when previousValue becomes 0, it means 
35            // largestElement is a multiple of remainingSum
36            // The actual previous value should be remainingSum in this case
37            if (previousValue == 0) {
38                previousValue = remainingSum;
39            }
40          
41            // Add the calculated previous value back to heap
42            maxHeap.push(previousValue);
43          
44            // Update total sum by replacing largestElement with previousValue
45            totalSum = totalSum - largestElement + previousValue;
46        }
47      
48        // If we successfully reduced all elements to 1, return true
49        return true;
50    }
51};
52
1/**
2 * Determines if it's possible to transform an array of all 1s into the target array
3 * using the operation: replace any element with the sum of all elements
4 * @param target - The target array to check
5 * @returns true if transformation is possible, false otherwise
6 */
7function isPossible(target: number[]): boolean {
8    // Create a max priority queue to always process the largest element
9    const maxHeap = new MaxPriorityQueue<number>();
10  
11    // Calculate the initial sum of all elements
12    let totalSum = 0;
13  
14    // Add all elements to the max heap and calculate total sum
15    for (const element of target) {
16        totalSum += element;
17        maxHeap.enqueue(element);
18    }
19  
20    // Work backwards from target array to array of all 1s
21    while (maxHeap.front() > 1) {
22        // Get the largest element from the heap
23        const largestElement = maxHeap.dequeue();
24      
25        // Calculate the sum of all other elements
26        const sumOfOthers = totalSum - largestElement;
27      
28        // Check if transformation is impossible
29        // sumOfOthers < 1: means other elements sum to less than 1
30        // largestElement - sumOfOthers < 1: means we can't reduce the largest element
31        if (sumOfOthers < 1 || largestElement - sumOfOthers < 1) {
32            return false;
33        }
34      
35        // Calculate the previous value before transformation
36        // Use modulo to optimize multiple subtractions
37        // If largestElement % sumOfOthers is 0, use sumOfOthers instead
38        const previousValue = largestElement % sumOfOthers || sumOfOthers;
39      
40        // Add the calculated previous value back to the heap
41        maxHeap.enqueue(previousValue);
42      
43        // Update the total sum
44        totalSum = totalSum - largestElement + previousValue;
45    }
46  
47    // If we successfully reduced all elements to 1, return true
48    return true;
49}
50

Time and Space Complexity

Time Complexity: O(n log n + k log n) where n is the length of the array target and k is the number of operations needed to reduce all elements to 1.

  • Initial heapification takes O(n) time
  • Each iteration involves:
    • heappop(): O(log n)
    • heappush(): O(log n)
  • The number of iterations k depends on the values in the target array. In the worst case, when we have large values that need to be reduced step by step, k can be proportional to log(max(target)) due to the modulo operation optimization
  • For practical purposes and simplified analysis, this is often expressed as O(n log n) when considering typical constraints

Space Complexity: O(n)

  • The priority queue pq stores all n elements from the target array, requiring O(n) space
  • Additional variables (s, mx, t, x) use O(1) space
  • Total space complexity is O(n)

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

Common Pitfalls

1. Integer Overflow with Repeated Subtraction

The Pitfall: A naive implementation might use a while loop to repeatedly subtract sum_of_others from current_max:

# WRONG - This causes Time Limit Exceeded!
while current_max > sum_of_others:
    current_max -= sum_of_others
previous_value = current_max

This approach fails catastrophically when current_max is significantly larger than sum_of_others. For example, with target = [1, 1000000000], you'd need nearly a billion iterations!

The Solution: Use the modulo operator for instant calculation:

# CORRECT - O(1) operation
previous_value = current_max % sum_of_others
if previous_value == 0:
    previous_value = sum_of_others

2. Incorrect Handling of Modulo Zero Case

The Pitfall: When current_max % sum_of_others == 0, using 0 as the previous value is incorrect:

# WRONG - This will push 0 to the heap
previous_value = current_max % sum_of_others  # Could be 0!
heappush(max_heap, -previous_value)

This breaks the invariant that all values must be ≥ 1 since we started with an array of ones.

The Solution: When the modulo result is 0, the previous value should be sum_of_others:

# CORRECT - Handle the edge case
previous_value = (current_max % sum_of_others) or sum_of_others

3. Missing Edge Case: Sum of Others is Zero

The Pitfall: Forgetting to check if sum_of_others == 0 leads to division by zero error:

# WRONG - Division by zero when sum_of_others = 0
previous_value = current_max % sum_of_others  # ZeroDivisionError!

The Solution: Always validate before the modulo operation:

# CORRECT - Check for zero first
if sum_of_others == 0:
    return False
previous_value = (current_max % sum_of_others) or sum_of_others

4. Not Checking if Previous Value Would Be Less Than 1

The Pitfall: Even after modulo optimization, forgetting to verify that the calculated previous value is valid:

# WRONG - Missing validation
previous_value = (current_max % sum_of_others) or sum_of_others
# What if current_max < sum_of_others + 1?

The Solution: Add a validation check before proceeding:

# CORRECT - Ensure we can reach a valid state
if current_max - sum_of_others < 1:
    return False

5. Using Min Heap Instead of Max Heap

The Pitfall: Python's heapq is a min heap, so forgetting to negate values will process the smallest element instead of the largest:

# WRONG - This processes minimum elements
max_heap = target.copy()
heapify(max_heap)
current_max = heappop(max_heap)  # Actually gets minimum!

The Solution: Negate all values to simulate a max heap:

# CORRECT - Negate to create max heap behavior
max_heap = [-num for num in target]
heapify(max_heap)
current_max = -heappop(max_heap)  # Gets maximum
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

What's the relationship between a tree and a graph?


Recommended Readings

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

Load More