1526. Minimum Number of Increments on Subarrays to Form a Target Array
Problem Description
You start with an array initial
that has the same size as a given target
array, where all elements in initial
are zeros.
In each operation, you can:
- Select any contiguous subarray from
initial
- Increment every element in that subarray by 1
Your goal is to transform the initial
array into the target
array using the minimum number of operations.
For example, if target = [1, 2, 3, 2, 1]
:
- Starting with
initial = [0, 0, 0, 0, 0]
- You could select subarray from index 0 to 4 and increment:
[1, 1, 1, 1, 1]
- Then select subarray from index 1 to 3 and increment:
[1, 2, 2, 2, 1]
- Then select subarray from index 2 to 2 and increment:
[1, 2, 3, 2, 1]
- Total operations: 3
The key insight is that when you look at consecutive elements in the target array:
- If
target[i] <= target[i-1]
, you don't need extra operations since you can reuse operations from previous elements - If
target[i] > target[i-1]
, you needtarget[i] - target[i-1]
additional operations to reach the higher value
The solution calculates the minimum operations as: target[0]
(initial operations to reach first element) plus the sum of all positive differences between consecutive elements.
Intuition
Think of the target array as a mountain range where each value represents the height at that position. We need to build this mountain range from ground level (all zeros) using horizontal layers.
When we perform an operation on a subarray, we're essentially adding a horizontal layer of height 1 across that range. The key observation is that we can be greedy and extend our operations as far as possible to minimize the total count.
Consider walking through the array from left to right:
- At the first position, we need exactly
target[0]
operations to reach that height - Moving to the next position, two scenarios arise:
- If
target[i] <= target[i-1]
: The height decreased or stayed the same. We can reuse the operations from the previous position - no new operations needed - If
target[i] > target[i-1]
: The height increased. We needtarget[i] - target[i-1]
additional operations to build up fromtarget[i-1]
totarget[i]
- If
Visualize it like painting horizontal stripes on a wall. When the wall gets taller, you need extra stripes for the new section. When it gets shorter, the stripes you already painted cover that height.
For example, with target = [3, 1, 2]
:
- Position 0: Need 3 operations to reach height 3
- Position 1: Height drops to 1, covered by existing operations
- Position 2: Height rises to 2, need 1 extra operation (2 - 1 = 1)
- Total: 3 + 0 + 1 = 4 operations
This greedy approach works because extending an operation to cover more positions doesn't cost extra, so we always extend operations as far as the heights allow.
Learn more about Stack, Greedy, Dynamic Programming and Monotonic Stack patterns.
Solution Approach
The solution uses a dynamic programming approach that can be optimized to use constant space.
Dynamic Programming Formulation:
Define f[i]
as the minimum number of operations needed to form target[0..i]
from the initial array.
Base case: f[0] = target[0]
- we need exactly target[0]
operations to reach the first element's value.
Transition: For each position i
from 1 to n-1:
- If
target[i] <= target[i-1]
: Thenf[i] = f[i-1]
(no additional operations needed) - If
target[i] > target[i-1]
: Thenf[i] = f[i-1] + (target[i] - target[i-1])
(need extra operations for the increase)
Space Optimization:
Since f[i]
only depends on f[i-1]
, we don't need to store the entire array. We can maintain just the running sum of operations.
Implementation:
The code implements this elegantly in one line:
return target[0] + sum(max(0, b - a) for a, b in pairwise(target))
Breaking it down:
target[0]
- Initial operations for the first elementpairwise(target)
- Creates pairs of consecutive elements(target[i-1], target[i])
b - a
- Calculates the difference between consecutive elementsmax(0, b - a)
- Only counts positive differences (when height increases)sum(...)
- Adds up all the additional operations needed
The max(0, b - a)
effectively implements our transition logic:
- When
b <= a
(height decreases or stays same): contributes 0 - When
b > a
(height increases): contributesb - a
Time and Space Complexity:
- Time:
O(n)
- single pass through the array - Space:
O(1)
- only using variables for the sum calculation
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a small example with target = [2, 3, 1, 4, 2]
to illustrate the solution approach.
Initial Setup:
- Start with
initial = [0, 0, 0, 0, 0]
- Need to transform it to
target = [2, 3, 1, 4, 2]
Step-by-step Analysis:
Position 0 (target = 2):
- Starting from 0, need 2 operations to reach height 2
- Operations so far: 2
Position 1 (target = 3):
- Previous height: 2, current height: 3
- Height increased by 1 (3 - 2 = 1)
- Need 1 additional operation
- Operations so far: 2 + 1 = 3
Position 2 (target = 1):
- Previous height: 3, current height: 1
- Height decreased (1 - 3 = -2)
- No additional operations needed (we can reuse existing operations)
- Operations so far: 3 + 0 = 3
Position 3 (target = 4):
- Previous height: 1, current height: 4
- Height increased by 3 (4 - 1 = 3)
- Need 3 additional operations
- Operations so far: 3 + 3 = 6
Position 4 (target = 2):
- Previous height: 4, current height: 2
- Height decreased (2 - 4 = -2)
- No additional operations needed
- Operations so far: 6 + 0 = 6
Calculation using the formula:
- Initial operations:
target[0] = 2
- Consecutive differences:
- (2, 3): max(0, 3-2) = 1
- (3, 1): max(0, 1-3) = 0
- (1, 4): max(0, 4-1) = 3
- (4, 2): max(0, 2-4) = 0
- Total: 2 + 1 + 0 + 3 + 0 = 6 operations
Visual representation of operations:
Operation 1: Select [0,4], increment → [1, 1, 1, 1, 1] Operation 2: Select [0,4], increment → [2, 2, 2, 2, 2] Operation 3: Select [1,1], increment → [2, 3, 2, 2, 2] Operation 4: Select [3,3], increment → [2, 3, 2, 3, 2] Operation 5: Select [3,3], increment → [2, 3, 2, 4, 2] Operation 6: Select [3,3], increment → [2, 3, 2, 5, 2] (wait, this goes beyond)
Actually, let me correct the operations:
Operation 1: Select [0,1] and [3,4], increment → [1, 1, 0, 1, 1] Operation 2: Select [0,1] and [3,4], increment → [2, 2, 0, 2, 2] Operation 3: Select [1,1], increment → [2, 3, 0, 2, 2] Operation 4: Select [3,3], increment → [2, 3, 0, 3, 2] Operation 5: Select [3,3], increment → [2, 3, 0, 4, 2] Operation 6: Select [2,2], increment → [2, 3, 1, 4, 2]
The key insight: we only need additional operations when the height increases from one position to the next.
Solution Implementation
1class Solution:
2 def minNumberOperations(self, target: List[int]) -> int:
3 """
4 Calculate minimum number of operations to build the target array.
5
6 The key insight: we only need to add operations when the height increases
7 from one element to the next. When height decreases, we can reuse
8 operations from the previous higher level.
9
10 Args:
11 target: List of positive integers representing target heights
12
13 Returns:
14 Minimum number of increment operations needed
15 """
16 # Start with the first element's height as initial operations needed
17 total_operations = target[0]
18
19 # For each consecutive pair, add the increase in height (if any)
20 for i in range(1, len(target)):
21 previous_height = target[i - 1]
22 current_height = target[i]
23
24 # Only add operations when height increases
25 # If height decreases or stays same, no new operations needed
26 height_increase = max(0, current_height - previous_height)
27 total_operations += height_increase
28
29 return total_operations
30
1class Solution {
2 public int minNumberOperations(int[] target) {
3 // Initialize the result with the first element's value
4 // This represents the initial operations needed to build the first position
5 int totalOperations = target[0];
6
7 // Iterate through the array starting from the second element
8 for (int i = 1; i < target.length; ++i) {
9 // If current element is greater than the previous element,
10 // we need additional operations equal to the difference
11 if (target[i] > target[i - 1]) {
12 totalOperations += target[i] - target[i - 1];
13 }
14 // Note: If target[i] <= target[i-1], no additional operations needed
15 // as we can reuse operations from building the previous element
16 }
17
18 // Return the total minimum number of operations required
19 return totalOperations;
20 }
21}
22
1class Solution {
2public:
3 int minNumberOperations(vector<int>& target) {
4 // Initialize total operations with the first element's value
5 // (operations needed to build the first element from 0)
6 int totalOperations = target[0];
7
8 // Iterate through the array starting from the second element
9 for (int i = 1; i < target.size(); ++i) {
10 // If current element is greater than previous element,
11 // we need additional operations equal to the difference
12 if (target[i] > target[i - 1]) {
13 totalOperations += target[i] - target[i - 1];
14 }
15 // If current element is less than or equal to previous,
16 // no additional operations needed (covered by previous operations)
17 }
18
19 return totalOperations;
20 }
21};
22
1/**
2 * Calculates the minimum number of operations to build the target array from an empty array.
3 * Each operation increments a contiguous subarray by 1.
4 *
5 * The algorithm works by tracking the "cost" of building each peak and valley.
6 * When we encounter an increase from previous element, we need additional operations
7 * equal to the difference.
8 *
9 * @param target - The target array to build
10 * @returns The minimum number of operations required
11 */
12function minNumberOperations(target: number[]): number {
13 // Initialize with the first element's value (operations needed to build first element)
14 let totalOperations: number = target[0];
15
16 // Iterate through the array starting from the second element
17 for (let i = 1; i < target.length; ++i) {
18 // If current element is higher than previous, we need additional operations
19 // The difference represents new "layers" that need to be added
20 if (target[i] > target[i - 1]) {
21 totalOperations += target[i] - target[i - 1];
22 }
23 // If current element is lower or equal, no additional operations needed
24 // (covered by previous operations that extend to this position)
25 }
26
27 return totalOperations;
28}
29
Time and Space Complexity
Time Complexity: O(n)
where n
is the length of the target array.
The algorithm iterates through the array once using pairwise(target)
, which generates consecutive pairs of elements. The pairwise
function creates n-1
pairs for an array of length n
. For each pair (a, b)
, we compute max(0, b - a)
in constant time O(1)
. The sum()
function then accumulates these n-1
values, resulting in a total time complexity of O(n-1) = O(n)
.
Space Complexity: O(1)
auxiliary space.
The pairwise
function returns an iterator that generates pairs on-the-fly without creating a new list to store all pairs at once. The only additional space used is for:
- The iterator object itself:
O(1)
- Variables to store intermediate calculations (
a
,b
, and the running sum):O(1)
Therefore, the algorithm uses constant auxiliary space beyond the input array, giving us O(1)
space complexity.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Misunderstanding the Problem as Requiring Individual Increments
The Mistake: Many people initially think they need to track each individual increment operation separately, leading to overly complex solutions that try to simulate the actual building process step by step.
# INCORRECT approach - trying to simulate each operation
def minNumberOperations_wrong(target):
operations = 0
current = [0] * len(target)
while current != target:
# Find a range to increment
for i in range(len(target)):
if current[i] < target[i]:
# Increment this position and adjacent ones
j = i
while j < len(target) and current[j] < target[j]:
current[j] += 1
j += 1
operations += 1
break
return operations
Why It's Wrong: This approach is inefficient (O(n * max(target))) and unnecessarily complex. The problem doesn't require you to simulate the actual operations - just count how many are needed.
The Fix: Recognize that you can calculate the minimum operations mathematically without simulation. Focus on the pattern: operations are only added when moving from a lower height to a higher height.
Pitfall 2: Incorrectly Handling Decreasing Heights
The Mistake: Some might think that when the height decreases, you need to subtract operations or handle it specially:
# INCORRECT - wrong interpretation of decreasing heights
def minNumberOperations_wrong2(target):
operations = target[0]
for i in range(1, len(target)):
# WRONG: thinking we need to handle decreases differently
diff = target[i] - target[i-1]
operations += abs(diff) # This counts decreases as additions!
return operations
Why It's Wrong:
When height decreases, we don't need ANY new operations. The operations that built the previous taller element already cover the current shorter element. Using abs(diff)
would incorrectly add operations for decreases.
The Fix:
Only add the positive differences: max(0, target[i] - target[i-1])
. This ensures decreases contribute 0 additional operations.
Pitfall 3: Off-by-One Errors with Array Indexing
The Mistake: Forgetting to handle the first element properly or iterating incorrectly:
# INCORRECT - missing the first element
def minNumberOperations_wrong3(target):
operations = 0 # WRONG: should start with target[0]
for i in range(1, len(target)):
operations += max(0, target[i] - target[i-1])
return operations
Why It's Wrong:
The first element requires exactly target[0]
operations to build from 0. Starting with operations = 0
misses these initial operations.
The Fix:
Always initialize with operations = target[0]
to account for building the first element from zero.
Pitfall 4: Edge Case - Empty or Single Element Arrays
The Mistake: Not handling edge cases properly:
# INCORRECT - doesn't handle edge cases
def minNumberOperations_wrong4(target):
# Crashes on empty array
operations = target[0]
# No check for single element
for i in range(1, len(target)):
operations += max(0, target[i] - target[i-1])
return operations
The Fix: Add proper edge case handling:
def minNumberOperations_correct(target):
if not target:
return 0
if len(target) == 1:
return target[0]
operations = target[0]
for i in range(1, len(target)):
operations += max(0, target[i] - target[i-1])
return operations
A person thinks of a number between 1 and 1000. You may ask any number questions to them, provided that the question can be answered with either "yes" or "no".
What is the minimum number of questions you needed to ask so that you are guaranteed to know the number that the person is thinking?
Recommended Readings
Stack Intro Imagine you have a pile of books on your desk If you want to add a new book you place it on top If you want to read a book you take it from the top And if you simply want to see which book is on the top you
Greedy Introduction div class responsive iframe iframe src https www youtube com embed WTslqPbj7I title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture web share allowfullscreen iframe div When do we use greedy Greedy algorithms tend to solve optimization problems Typically they will ask you to calculate the max min of some value Commonly you may see this phrased in the problem as max min longest shortest largest smallest etc These keywords can be identified by just scanning
What is Dynamic Programming Prerequisite DFS problems dfs_intro Backtracking problems backtracking Memoization problems memoization_intro Pruning problems backtracking_pruning Dynamic programming is an algorithmic optimization technique that breaks down a complicated problem into smaller overlapping sub problems in a recursive manner and uses solutions to the sub problems to construct a solution
Want a Structured Path to Master System Design Too? Don’t Miss This!