330. Patching Array
Problem Description
You are given a sorted integer array nums
and an integer n
. Your task is to add/patch the minimum number of elements to the array so that any number in the range [1, n]
inclusive can be formed by summing some elements in the array.
The goal is to find the minimum number of patches (additions) required to achieve this coverage.
For example, if you have nums = [1, 3]
and n = 6
:
- Initially, you can form: 1 (using 1), 3 (using 3), 4 (using 1+3)
- You cannot form 2, 5, or 6
- By adding 2 to the array, you get
[1, 2, 3]
- Now you can form all numbers from 1 to 6: 1 (using 1), 2 (using 2), 3 (using 3), 4 (using 1+3), 5 (using 2+3), 6 (using 1+2+3)
- Therefore, the minimum number of patches needed is 1
The solution uses a greedy approach where we track the smallest number x
that cannot be formed yet. If we can form all numbers in [1, x-1]
, and we add a number y
where y <= x
, then we can form all numbers in [1, x+y-1]
. The algorithm greedily chooses to add x
itself when needed to maximize the range of numbers we can form.
Intuition
Let's think about this problem step by step. If we can form all numbers from [1, x-1]
, what happens when we add a new number to our collection?
Suppose we add a number y
to our array. Since we can already form all numbers from [1, x-1]
, we can now also form y, y+1, y+2, ..., y+(x-1)
by adding y
to each of the numbers we could form before. This extends our range to [1, x+y-1]
.
The key insight is: when we need to form the number x
but can't, what's the best number to add?
If we add a number greater than x
, say x+k
, we still can't form x
itself. We'd only be able to form numbers starting from x+k
.
If we add a number less than or equal to x
, we can form x
(either directly if we add x
, or by combining the added number with existing sums). The question becomes: which number maximizes our new range?
- Adding
x
gives us range[1, x + (x-1)] = [1, 2x-1]
- Adding any
y < x
gives us range[1, x + y - 1]
wherex + y - 1 < 2x - 1
Therefore, greedily adding x
itself maximizes our coverage whenever we encounter a gap.
Now, how do we use the existing sorted array? As we iterate through nums
, if the current number nums[i]
is less than or equal to our current gap x
, we should use it (it's free!). This extends our range to [1, x + nums[i] - 1]
. If nums[i]
is too large (greater than x
), we have no choice but to patch the array with x
to fill the gap.
This greedy strategy ensures we add the minimum number of patches because each patch maximally extends our range, and we only patch when absolutely necessary.
Solution Approach
We implement the greedy strategy using two key variables:
x
: tracks the smallest positive integer that cannot be represented (initially 1)i
: index to traverse through the sorted arraynums
ans
: counter for the number of patches added
The algorithm works as follows:
-
Initialize state: Start with
x = 1
(the first number we need to form),i = 0
(array index), andans = 0
(patch count). -
Main loop: Continue while
x <= n
(we need to cover all numbers up ton
):Case 1: Can use existing array element
- If
i < len(nums)
andnums[i] <= x
:- We can use
nums[i]
to extend our range - Since we can form
[1, x-1]
, addingnums[i]
lets us form[1, x + nums[i] - 1]
- Update:
x = x + nums[i]
(new smallest uncovered number) - Move to next array element:
i += 1
- We can use
Case 2: Need to patch
- If
nums[i] > x
or we've exhausted the array:- We must patch the array with number
x
- This extends our range from
[1, x-1]
to[1, 2x-1]
- Update:
x = 2 * x
(orx <<= 1
using bit shift) - Increment patch count:
ans += 1
- We must patch the array with number
- If
-
Return result: Once
x > n
, we've covered all numbers in[1, n]
, so returnans
.
Example walkthrough with nums = [1, 5, 10]
, n = 20
:
- Initially:
x = 1
, can't form any number nums[0] = 1 <= x
: use it,x = 1 + 1 = 2
, range[1, 1]
nums[1] = 5 > x = 2
: patch with 2,x = 2 * 2 = 4
, range[1, 3]
, patches = 1nums[1] = 5 > x = 4
: patch with 4,x = 4 * 2 = 8
, range[1, 7]
, patches = 2nums[1] = 5 <= x = 8
: use it,x = 8 + 5 = 13
, range[1, 12]
nums[2] = 10 <= x = 13
: use it,x = 13 + 10 = 23
, range[1, 22]
- Now
x = 23 > n = 20
, done with 2 patches
The time complexity is O(m + log n)
where m
is the length of nums
, since we traverse the array once and potentially patch O(log n)
times (each patch doubles our range).
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 detailed example with nums = [1, 3]
and n = 6
.
Goal: Ensure we can form all numbers from 1 to 6 using elements from the array (with patches if needed).
Initial State:
x = 1
(smallest number we can't form yet)i = 0
(array index)ans = 0
(patches added)
Step 1: x = 1
, checking nums[0] = 1
- Since
nums[0] = 1 <= x = 1
, we can use this element - Currently can form:
[0]
(empty), adding 1 gives us:[1]
- Update:
x = 1 + 1 = 2
(next number to check) - Move to next element:
i = 1
- Range covered:
[1, 1]
Step 2: x = 2
, checking nums[1] = 3
- Since
nums[1] = 3 > x = 2
, we cannot use this element yet (it would leave a gap at 2) - Must patch with 2 to fill the gap
- Currently can form
[1]
, adding 2 gives us:[1, 2, 3]
(1+2=3) - Update:
x = 2 * 2 = 4
(doubling our range) - Increment patches:
ans = 1
- Range covered:
[1, 3]
Step 3: x = 4
, checking nums[1] = 3
- Since
nums[1] = 3 <= x = 4
, we can now use this element - Currently can form
[1, 2, 3]
, adding element 3 gives us:[1, 2, 3, 4, 5, 6]
- Previous: 1, 2, 3
- New: 3+1=4, 3+2=5, 3+3=6
- Update:
x = 4 + 3 = 7
- Move to next element:
i = 2
(now out of array bounds) - Range covered:
[1, 6]
Step 4: x = 7
, but 7 > n = 6
- We've covered all numbers up to 6, so we're done!
Result: Minimum patches needed = 1 (we added the number 2)
Verification: With array [1, 2, 3]
:
- 1 = 1
- 2 = 2
- 3 = 3
- 4 = 1 + 3
- 5 = 2 + 3
- 6 = 1 + 2 + 3
The key insight: We greedily patch with the smallest uncovered number to maximize the range extension each time.
Solution Implementation
1from typing import List
2
3
4class Solution:
5 def minPatches(self, nums: List[int], n: int) -> int:
6 # max_reachable represents the maximum sum we can form with current numbers
7 # All numbers in range [1, max_reachable) can be formed
8 max_reachable = 1
9
10 # patches_count tracks the number of patches (additions) needed
11 patches_count = 0
12
13 # index to traverse through the nums array
14 index = 0
15
16 # Continue until we can form all numbers up to n
17 while max_reachable <= n:
18 # If current number in nums can extend our range without gaps
19 if index < len(nums) and nums[index] <= max_reachable:
20 # Add current number to extend the reachable range
21 max_reachable += nums[index]
22 index += 1
23 else:
24 # Need to patch: add max_reachable itself to the array
25 # This doubles our reachable range
26 patches_count += 1
27 max_reachable *= 2 # Equivalent to max_reachable <<= 1
28
29 return patches_count
30
1class Solution {
2 public int minPatches(int[] nums, int n) {
3 // maxReachableSum represents the maximum sum we can form using current elements
4 // We can form all sums in range [1, maxReachableSum - 1]
5 long maxReachableSum = 1;
6
7 // Count of patches (new numbers) we need to add
8 int patchCount = 0;
9
10 // Index to traverse the nums array
11 int index = 0;
12
13 // Continue until we can form all sums up to n
14 while (maxReachableSum <= n) {
15 // Check if we can use the current element from nums array
16 if (index < nums.length && nums[index] <= maxReachableSum) {
17 // If current element is within our reachable range,
18 // we can extend our range by adding this element
19 maxReachableSum += nums[index];
20 index++;
21 } else {
22 // If current element is too large or we've exhausted the array,
23 // we need to add a patch (the optimal patch is maxReachableSum itself)
24 patchCount++;
25
26 // Adding maxReachableSum as a patch doubles our reachable range
27 // This is equivalent to: maxReachableSum = maxReachableSum * 2
28 maxReachableSum <<= 1;
29 }
30 }
31
32 return patchCount;
33 }
34}
35
1class Solution {
2public:
3 int minPatches(vector<int>& nums, int n) {
4 // maxReachable represents the maximum sum we can form with current elements
5 // We need to cover all numbers from 1 to maxReachable - 1
6 long long maxReachable = 1;
7
8 // Count of patches (numbers) we need to add
9 int patchCount = 0;
10
11 // Index for traversing the nums array
12 int index = 0;
13
14 // Continue until we can form all sums from 1 to n
15 while (maxReachable <= n) {
16 // Case 1: Current number in array can extend our range
17 // If nums[index] <= maxReachable, we can use it to extend our range
18 if (index < nums.size() && nums[index] <= maxReachable) {
19 // Add current number to extend the reachable range
20 maxReachable += nums[index];
21 index++;
22 }
23 // Case 2: Need to patch - add the number equal to maxReachable
24 // This optimally doubles our reachable range
25 else {
26 patchCount++;
27 // Adding maxReachable as a patch doubles the range we can cover
28 // This is equivalent to: maxReachable = maxReachable * 2
29 maxReachable <<= 1;
30 }
31 }
32
33 return patchCount;
34 }
35};
36
1/**
2 * Calculates the minimum number of patches required to form every integer in range [1, n]
3 * @param nums - Sorted array of positive integers
4 * @param n - Target upper bound of the range
5 * @returns Minimum number of patches needed
6 */
7function minPatches(nums: number[], n: number): number {
8 // The smallest number that cannot be formed with current elements
9 let smallestMissing: number = 1;
10
11 // Count of patches added
12 let patchCount: number = 0;
13
14 // Index for traversing the nums array
15 let index: number = 0;
16
17 // Continue until we can form all numbers up to n
18 while (smallestMissing <= n) {
19 if (index < nums.length && nums[index] <= smallestMissing) {
20 // If current number can help extend our range, use it
21 // We can now form all numbers up to (smallestMissing + nums[index] - 1)
22 smallestMissing += nums[index];
23 index++;
24 } else {
25 // Need to patch: add smallestMissing itself
26 // This doubles our reachable range
27 patchCount++;
28 smallestMissing *= 2;
29 }
30 }
31
32 return patchCount;
33}
34
Time and Space Complexity
The time complexity is O(m + log n)
, where m
is the length of the array nums
and n
is the target value.
- The loop continues while
x <= n
. In each iteration, either:- We consume an element from
nums
(happens at mostm
times), or - We patch by doubling
x
(viax <<= 1
), which meansx
grows exponentially
- We consume an element from
- When patching,
x
doubles each time, so it takes at mostlog n
patches to reachn
- Therefore, the total iterations is bounded by
m + log n
The space complexity is O(1)
.
- The algorithm only uses a constant amount of extra space for variables
x
,ans
, andi
- No additional data structures are created that depend on the input size
Common Pitfalls
1. Off-by-One Error with Range Coverage
A common mistake is misunderstanding what max_reachable
represents. Many developers incorrectly assume it represents the maximum number we can form, but it actually represents the first number we CANNOT form yet.
Incorrect interpretation:
# WRONG: Thinking max_reachable is the largest number we can form
while max_reachable < n: # This would stop too early!
if index < len(nums) and nums[index] <= max_reachable:
max_reachable += nums[index]
index += 1
else:
patches_count += 1
max_reachable *= 2
Why it fails: If n = 5
and we can form [1, 5]
, max_reachable
would be 6. The condition max_reachable < n
would be false (6 < 5), causing early termination even though we haven't verified we can form 5.
Correct approach: Use max_reachable <= n
because when max_reachable = n + 1
, we know we can form all numbers in [1, n]
.
2. Integer Overflow in Large Cases
When dealing with large values of n
(like n = 2^31 - 1
), the multiplication max_reachable *= 2
can cause integer overflow in languages with fixed integer sizes.
Problematic scenario:
# When max_reachable is very large max_reachable *= 2 # Could overflow in some languages
Solution: In Python, integers have arbitrary precision so overflow isn't an issue. However, for optimization or when porting to other languages, consider:
- Using bit shift:
max_reachable <<= 1
(slightly faster) - Adding overflow checks in languages like C++ or Java
- Using long/int64 data types when appropriate
3. Forgetting to Check Array Bounds
Another pitfall is accessing nums[index]
without first checking if index < len(nums)
.
Incorrect code:
# WRONG: Checking nums[index] before bounds check while max_reachable <= n: if nums[index] <= max_reachable: # IndexError when index >= len(nums) max_reachable += nums[index] index += 1 else: patches_count += 1 max_reachable *= 2
Correct approach: Always check index < len(nums)
before accessing nums[index]
:
if index < len(nums) and nums[index] <= max_reachable:
# Safe to access nums[index]
4. Misunderstanding the Greedy Choice
Some might think we should always use array elements when possible, even if they're much larger than needed.
Example mistake: With nums = [1, 100]
and n = 50
, after using 1 (covering [1,1]), someone might think:
- "Since 100 is in the array, let's wait to use it"
- This leads to unnecessary patches
Correct understanding: We only use an array element when nums[index] <= max_reachable
. If nums[index] > max_reachable
, using it would create gaps in our coverage. The optimal strategy is to patch with max_reachable
itself to maximize range extension.
Consider the classic dynamic programming of fibonacci numbers, what is the recurrence relation?
Recommended Readings
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
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Donβt Miss This!