1959. Minimum Total Space Wasted With K Resizing Operations
Problem Description
You need to design a dynamic array that changes size over time. Given a 0-indexed integer array nums where nums[i] represents the number of elements that must be stored at time i, and an integer k representing the maximum number of times you can resize the array.
At any time t, your array must have a size size_t that is at least nums[t] to accommodate all elements. The space wasted at time t is calculated as size_t - nums[t] (the difference between the array's capacity and actual usage). The total space wasted is the sum of space wasted across all time points from 0 to nums.length - 1.
Your task is to find the minimum total space wasted when you can resize the array at most k times.
Key points:
- The array can start at any size (this doesn't count as a resize operation)
- Each resize can change the array to any size
- The array size at time
tmust be at leastnums[t] - You want to minimize the total wasted space across all time points
Example understanding:
If nums = [10, 20, 15] and k = 1, you could:
- Start with size 20 for times 0-1 (wasting 10 at time 0, 0 at time 1)
- Resize to 15 for time 2 (wasting 0 at time 2)
- Total waste = 10 + 0 + 0 = 10
The solution uses dynamic programming where:
g[i][j]stores the wasted space if we use a single array size for the segment from indexitojf[i][j]represents the minimum wasted space for the firstielements usingjsegments (resize operations)- The problem transforms into partitioning the array into
k+1segments optimally
Intuition
The key insight is to recognize that resizing the array k times essentially divides the timeline into k+1 segments. Each segment will use a fixed array size throughout its duration.
Think about it this way: if we can resize k times, we're creating k+1 continuous periods where the array maintains the same size. For example, with k=2 resizes, we get 3 segments: [start → first resize], [first resize → second resize], [second resize → end].
For each segment, the optimal array size is the maximum value within that segment - we must accommodate the peak demand. This means:
- If a segment covers times
[i, j], we need array size =max(nums[i], nums[i+1], ..., nums[j]) - The wasted space for this segment =
max_value × segment_length - sum_of_elements
This transforms our problem into: How do we optimally partition the array into k+1 segments to minimize total waste?
This is a classic dynamic programming pattern where we need to:
- Consider all possible ways to partition the array
- For each partition point, calculate the cost (wasted space) of that segment
- Build up the solution by combining optimal solutions to smaller subproblems
The pre-computation of g[i][j] (waste for segment [i,j]) allows us to quickly look up the cost of any segment. Then, f[i][j] builds the optimal solution by trying all possible positions for the last segment boundary and taking the minimum.
The recurrence relation f[i][j] = min(f[h][j-1] + g[h][i-1]) essentially says: "To optimally divide the first i elements into j segments, try making the last segment start at position h and combine it with the optimal way to divide the first h elements into j-1 segments."
Learn more about Dynamic Programming patterns.
Solution Approach
The solution uses dynamic programming with pre-computed segment costs. Let's walk through the implementation step by step:
Step 1: Pre-compute segment costs with array g[i][j]
First, we calculate the wasted space for every possible segment [i, j] in the array:
g = [[0] * n for _ in range(n)]
for i in range(n):
s = mx = 0
for j in range(i, n):
s += nums[j]
mx = max(mx, nums[j])
g[i][j] = mx * (j - i + 1) - s
For each segment starting at i and ending at j:
stracks the sum of elements in the segmentmxtracks the maximum value (this determines the required array size)- Wasted space =
mx × segment_length - sum_of_elements - This gives us
g[i][j] = mx × (j - i + 1) - s
Step 2: Dynamic Programming with array f[i][j]
We define f[i][j] as the minimum wasted space when dividing the first i elements into j segments:
f = [[inf] * (k + 1) for _ in range(n + 1)]
f[0][0] = 0
- Initialize all values to infinity except
f[0][0] = 0(no elements, no segments, no waste) - Note: We increment
kby 1 at the beginning sincekresizes createk+1segments
Step 3: Fill the DP table
for i in range(1, n + 1):
for j in range(1, k + 1):
for h in range(i):
f[i][j] = min(f[i][j], f[h][j - 1] + g[h][i - 1])
The recurrence relation works as follows:
- To divide the first
ielements intojsegments - Try every possible position
hfor where the(j-1)-th segment ends - The last segment covers elements from index
htoi-1(in 0-indexed terms) - Total cost = cost of first
helements inj-1segments + cost of segment[h, i-1] - Formula:
f[i][j] = min(f[h][j-1] + g[h][i-1])for all validh
Step 4: Return the result
return f[-1][-1]
The answer is f[n][k], which represents the minimum wasted space when dividing all n elements into k segments (after incrementing k).
Time Complexity: O(n² × k) where n is the length of nums
- Pre-computing
g:O(n²) - Filling DP table:
O(n² × k)
Space Complexity: O(n² + n × k) for storing arrays g and f
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 nums = [10, 20, 30] and k = 1 (one resize allowed).
Step 1: Pre-compute segment costs g[i][j]
We calculate the wasted space for every possible segment:
-
Segment [0,0]:
nums = [10]- max = 10, sum = 10, length = 1
- waste = 10 × 1 - 10 = 0
g[0][0] = 0
-
Segment [0,1]:
nums = [10, 20]- max = 20, sum = 30, length = 2
- waste = 20 × 2 - 30 = 10
g[0][1] = 10
-
Segment [0,2]:
nums = [10, 20, 30]- max = 30, sum = 60, length = 3
- waste = 30 × 3 - 60 = 30
g[0][2] = 30
-
Segment [1,1]:
nums = [20]- max = 20, sum = 20, length = 1
- waste = 20 × 1 - 20 = 0
g[1][1] = 0
-
Segment [1,2]:
nums = [20, 30]- max = 30, sum = 50, length = 2
- waste = 30 × 2 - 50 = 10
g[1][2] = 10
-
Segment [2,2]:
nums = [30]- max = 30, sum = 30, length = 1
- waste = 30 × 1 - 30 = 0
g[2][2] = 0
Step 2: Dynamic Programming with f[i][j]
Since k = 1 resize means k+1 = 2 segments total. We build f[i][j] where i = number of elements, j = number of segments.
Initialize:
f[0][0] = 0(no elements, no segments)- All others = infinity
Fill the table:
For f[1][1] (1 element, 1 segment):
- h = 0:
f[0][0] + g[0][0] = 0 + 0 = 0 f[1][1] = 0
For f[2][1] (2 elements, 1 segment):
- h = 0:
f[0][0] + g[0][1] = 0 + 10 = 10 f[2][1] = 10
For f[3][1] (3 elements, 1 segment):
- h = 0:
f[0][0] + g[0][2] = 0 + 30 = 30 f[3][1] = 30
For f[2][2] (2 elements, 2 segments):
- h = 0:
f[0][1] + g[0][1] = ∞ + 10 = ∞ - h = 1:
f[1][1] + g[1][1] = 0 + 0 = 0 f[2][2] = 0
For f[3][2] (3 elements, 2 segments):
- h = 0:
f[0][1] + g[0][2] = ∞ + 30 = ∞ - h = 1:
f[1][1] + g[1][2] = 0 + 10 = 10 - h = 2:
f[2][2] + g[2][2] = 0 + 0 = 0 f[3][2] = 0
Result: f[3][2] = 0
This means with 1 resize (2 segments), the minimum waste is 0. The optimal strategy is:
- Use size 10 for time 0 (segment 1: [10])
- Resize to 20 for time 1 (segment 2: [20])
- Resize to 30 for time 2 (segment 2: [30])
Wait, that's 2 resizes. Let me recalculate with the correct interpretation:
Actually, with k=1 resize creating 2 segments:
- Segment 1: times 0-1, use size 20 (waste = 10 at time 0, 0 at time 1)
- Segment 2: time 2, use size 30 (waste = 0 at time 2)
- Total waste = 10
Or:
- Segment 1: time 0, use size 10 (waste = 0)
- Segment 2: times 1-2, use size 30 (waste = 10 at time 1, 0 at time 2)
- Total waste = 10
The minimum is indeed 0 if we can split it perfectly into [10], [20], [30] with 2 resizes, but with only 1 resize (2 segments), the minimum waste is 10.
Solution Implementation
1class Solution:
2 def minSpaceWastedKResizing(self, nums: List[int], k: int) -> int:
3 # We can resize k times, so we have k+1 segments total
4 k += 1
5 n = len(nums)
6
7 # Precompute waste for each possible segment [i, j]
8 # waste[i][j] = wasted space if we resize segment from index i to j
9 waste = [[0] * n for _ in range(n)]
10
11 for i in range(n):
12 segment_sum = 0
13 max_in_segment = 0
14
15 for j in range(i, n):
16 # Update sum and max for segment [i, j]
17 segment_sum += nums[j]
18 max_in_segment = max(max_in_segment, nums[j])
19
20 # Waste = (max_value * segment_length) - sum_of_elements
21 # This is the space wasted when all elements are resized to max
22 segment_length = j - i + 1
23 waste[i][j] = max_in_segment * segment_length - segment_sum
24
25 # dp[i][j] = minimum waste for first i elements using j segments
26 dp = [[float('inf')] * (k + 1) for _ in range(n + 1)]
27
28 # Base case: 0 elements with 0 segments has 0 waste
29 dp[0][0] = 0
30
31 # Fill the DP table
32 for i in range(1, n + 1): # Consider first i elements
33 for j in range(1, min(i, k) + 1): # Use j segments (can't use more segments than elements)
34 # Try all possible positions for the last segment to start
35 for last_segment_start in range(i):
36 # Last segment covers [last_segment_start, i-1] (0-indexed)
37 # Previous segments cover [0, last_segment_start-1]
38 dp[i][j] = min(
39 dp[i][j],
40 dp[last_segment_start][j - 1] + waste[last_segment_start][i - 1]
41 )
42
43 # Return minimum waste for all n elements using at most k+1 segments
44 return dp[n][k]
451class Solution {
2 public int minSpaceWastedKResizing(int[] nums, int k) {
3 // We can resize k times, meaning we can have k+1 subarrays total
4 k++;
5 int n = nums.length;
6
7 // Precompute wasted space for each possible subarray [i, j]
8 // wastedSpace[i][j] = space wasted if elements from index i to j are in same subarray
9 int[][] wastedSpace = new int[n][n];
10 for (int i = 0; i < n; ++i) {
11 int sum = 0;
12 int maxElement = 0;
13 for (int j = i; j < n; ++j) {
14 sum += nums[j];
15 maxElement = Math.max(maxElement, nums[j]);
16 // Wasted space = (max element * number of elements) - sum of elements
17 wastedSpace[i][j] = maxElement * (j - i + 1) - sum;
18 }
19 }
20
21 // dp[i][j] = minimum wasted space for first i elements using j subarrays
22 int[][] dp = new int[n + 1][k + 1];
23 int INF = 0x3f3f3f3f; // Large value representing infinity
24
25 // Initialize all values to infinity
26 for (int i = 0; i < dp.length; ++i) {
27 Arrays.fill(dp[i], INF);
28 }
29
30 // Base case: 0 elements with 0 subarrays = 0 wasted space
31 dp[0][0] = 0;
32
33 // Fill the DP table
34 for (int i = 1; i <= n; ++i) { // For each number of elements
35 for (int j = 1; j <= k; ++j) { // For each number of subarrays
36 // Try all possible positions for the last subarray to start
37 for (int lastSubarrayStart = 0; lastSubarrayStart < i; ++lastSubarrayStart) {
38 // Minimum of current value and:
39 // wasted space for first lastSubarrayStart elements with j-1 subarrays
40 // + wasted space for elements from lastSubarrayStart to i-1 in one subarray
41 dp[i][j] = Math.min(dp[i][j],
42 dp[lastSubarrayStart][j - 1] + wastedSpace[lastSubarrayStart][i - 1]);
43 }
44 }
45 }
46
47 // Return minimum wasted space for all n elements with k subarrays
48 return dp[n][k];
49 }
50}
511class Solution {
2public:
3 int minSpaceWastedKResizing(vector<int>& nums, int k) {
4 // We can resize at most k times, which means we can have at most k+1 segments
5 k++;
6 int n = nums.size();
7
8 // waste[i][j] stores the wasted space if we put nums[i..j] in one segment
9 // The wasted space = (max_element * segment_length) - sum_of_elements
10 vector<vector<int>> waste(n, vector<int>(n));
11 for (int i = 0; i < n; ++i) {
12 int sum = 0;
13 int maxVal = 0;
14 for (int j = i; j < n; ++j) {
15 maxVal = max(maxVal, nums[j]);
16 sum += nums[j];
17 waste[i][j] = maxVal * (j - i + 1) - sum;
18 }
19 }
20
21 // dp[i][j] represents the minimum wasted space for the first i elements
22 // using exactly j segments
23 const int INF = 0x3f3f3f3f;
24 vector<vector<int>> dp(n + 1, vector<int>(k + 1, INF));
25
26 // Base case: 0 elements with 0 segments has 0 waste
27 dp[0][0] = 0;
28
29 // Fill the DP table
30 for (int i = 1; i <= n; ++i) { // For each number of elements
31 for (int j = 1; j <= k; ++j) { // For each number of segments
32 // Try all possible positions for the last segment
33 for (int prevEnd = 0; prevEnd < i; ++prevEnd) {
34 // Last segment covers elements from prevEnd to i-1 (0-indexed)
35 dp[i][j] = min(dp[i][j],
36 dp[prevEnd][j - 1] + waste[prevEnd][i - 1]);
37 }
38 }
39 }
40
41 // Return the minimum waste for all n elements using at most k segments
42 return dp[n][k];
43 }
44};
451/**
2 * Finds the minimum space wasted when resizing array into k+1 subarrays
3 * @param nums - The input array of numbers
4 * @param k - The number of resize operations allowed (will create k+1 subarrays)
5 * @returns The minimum total space wasted
6 */
7function minSpaceWastedKResizing(nums: number[], k: number): number {
8 // Increment k to represent the number of subarrays (k resizes = k+1 subarrays)
9 k += 1;
10 const arrayLength: number = nums.length;
11
12 // wastedSpace[i][j] stores the wasted space for subarray from index i to j
13 // Wasted space = (max element * subarray length) - sum of elements
14 const wastedSpace: number[][] = Array.from(
15 { length: arrayLength },
16 () => Array(arrayLength).fill(0)
17 );
18
19 // Precompute wasted space for all possible subarrays
20 for (let startIndex = 0; startIndex < arrayLength; startIndex++) {
21 let sum: number = 0;
22 let maxElement: number = 0;
23
24 for (let endIndex = startIndex; endIndex < arrayLength; endIndex++) {
25 // Update sum and max for current subarray
26 sum += nums[endIndex];
27 maxElement = Math.max(maxElement, nums[endIndex]);
28
29 // Calculate wasted space: (max * length) - sum
30 const subarrayLength: number = endIndex - startIndex + 1;
31 wastedSpace[startIndex][endIndex] = maxElement * subarrayLength - sum;
32 }
33 }
34
35 const INFINITY: number = Number.POSITIVE_INFINITY;
36
37 // dp[i][j] represents minimum wasted space for first i elements using j subarrays
38 const dp: number[][] = Array.from(
39 { length: arrayLength + 1 },
40 () => Array(k + 1).fill(INFINITY)
41 );
42
43 // Base case: 0 elements with 0 subarrays has 0 waste
44 dp[0][0] = 0;
45
46 // Fill DP table
47 for (let elementCount = 1; elementCount <= arrayLength; elementCount++) {
48 for (let subarrayCount = 1; subarrayCount <= k; subarrayCount++) {
49 // Try all possible positions for the last subarray
50 for (let lastSubarrayStart = 0; lastSubarrayStart < elementCount; lastSubarrayStart++) {
51 // Current state = previous state + waste of last subarray
52 const previousWaste: number = dp[lastSubarrayStart][subarrayCount - 1];
53 const currentSubarrayWaste: number = wastedSpace[lastSubarrayStart][elementCount - 1];
54
55 dp[elementCount][subarrayCount] = Math.min(
56 dp[elementCount][subarrayCount],
57 previousWaste + currentSubarrayWaste
58 );
59 }
60 }
61 }
62
63 // Return minimum waste for all n elements with k subarrays
64 return dp[arrayLength][k];
65}
66Time and Space Complexity
Time Complexity: O(n^2 × k)
The time complexity is determined by three nested operations:
- The preprocessing step that computes the
gmatrix requiresO(n^2)time, where we iterate through all possible subarrays from indexitoj. - The main dynamic programming solution has three nested loops:
- The outer loop iterates through positions
ifrom1ton:O(n) - The middle loop iterates through the number of resizing operations
jfrom1tok:O(k) - The inner loop iterates through all possible previous positions
hfrom0toi-1:O(n)
- The outer loop iterates through positions
The dominant operation is the triple nested loop in the DP solution, giving us O(n × k × n) = O(n^2 × k).
Space Complexity: O(n × (n + k))
The space complexity comes from:
- The
gmatrix of sizen × nstoring the wasted space for all subarrays:O(n^2) - The
fDP table of size(n+1) × (k+1):O(n × k)
The total space complexity is O(n^2 + n × k) = O(n × (n + k)), since we can factor out n from both terms.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Misunderstanding the relationship between resizes and segments
Pitfall: Many people confuse the number of resize operations with the number of segments. If you can resize k times, you actually create k+1 segments (initial size + k resizes).
Wrong approach:
# Incorrect: treating k as the number of segments directly
dp = [[float('inf')] * (k + 1) for _ in range(n + 1)]
# ... later using k directly in the DP
Correct approach:
# Correct: increment k at the beginning to represent k+1 segments
k += 1 # Now k represents the total number of segments
dp = [[float('inf')] * (k + 1) for _ in range(n + 1)]
2. Incorrect segment waste calculation
Pitfall: Forgetting to track the maximum element in each segment or incorrectly calculating the wasted space.
Wrong approach:
# Incorrect: using the last element as the size
for i in range(n):
for j in range(i, n):
waste[i][j] = nums[j] * (j - i + 1) - sum(nums[i:j+1])
Correct approach:
# Correct: track the maximum element in the segment
for i in range(n):
segment_sum = 0
max_in_segment = 0
for j in range(i, n):
segment_sum += nums[j]
max_in_segment = max(max_in_segment, nums[j])
waste[i][j] = max_in_segment * (j - i + 1) - segment_sum
3. Off-by-one errors in indexing
Pitfall: Mixing up 0-indexed array positions with 1-indexed DP states, leading to incorrect segment boundaries.
Wrong approach:
# Incorrect: confusing indices
for i in range(1, n + 1):
for j in range(1, k + 1):
for h in range(i):
# Wrong: using h and i directly without adjusting for 0-indexing
dp[i][j] = min(dp[i][j], dp[h][j - 1] + waste[h][i])
Correct approach:
# Correct: properly mapping DP indices to array indices
for i in range(1, n + 1): # i represents "first i elements"
for j in range(1, k + 1):
for h in range(i): # h represents "first h elements"
# waste array uses 0-indexed positions [h, i-1]
dp[i][j] = min(dp[i][j], dp[h][j - 1] + waste[h][i - 1])
4. Not handling the constraint that segments cannot exceed elements
Pitfall: Allowing more segments than elements, which is logically impossible.
Wrong approach:
# Incorrect: always iterating up to k segments
for i in range(1, n + 1):
for j in range(1, k + 1): # This could exceed i
# ...
Correct approach:
# Correct: ensuring we don't use more segments than elements
for i in range(1, n + 1):
for j in range(1, min(i, k) + 1): # Can't have more segments than elements
# ...
5. Inefficient repeated calculations
Pitfall: Recalculating segment sums and maximums repeatedly instead of pre-computing them.
Wrong approach:
# Inefficient: recalculating for every DP state
for i in range(1, n + 1):
for j in range(1, k + 1):
for h in range(i):
# Recalculating max and sum every time
segment_max = max(nums[h:i])
segment_sum = sum(nums[h:i])
waste = segment_max * (i - h) - segment_sum
dp[i][j] = min(dp[i][j], dp[h][j - 1] + waste)
Correct approach:
# Efficient: pre-compute all segment costs once
waste = [[0] * n for _ in range(n)]
# Pre-compute all waste values...
# Then use them in DP without recalculation
Which type of traversal does breadth first search do?
Recommended Readings
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
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 If you prefer videos here's a video that explains recursion in a fun and easy way 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 him
Want a Structured Path to Master System Design Too? Don’t Miss This!