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
t
must 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 indexi
toj
f[i][j]
represents the minimum wasted space for the firsti
elements usingj
segments (resize operations)- The problem transforms into partitioning the array into
k+1
segments 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
:
s
tracks the sum of elements in the segmentmx
tracks 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
k
by 1 at the beginning sincek
resizes createk+1
segments
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
i
elements intoj
segments - Try every possible position
h
for where the(j-1)
-th segment ends - The last segment covers elements from index
h
toi-1
(in 0-indexed terms) - Total cost = cost of first
h
elements inj-1
segments + 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]
45
1class 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}
51
1class 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};
45
1/**
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}
66
Time and Space Complexity
Time Complexity: O(n^2 × k)
The time complexity is determined by three nested operations:
- The preprocessing step that computes the
g
matrix requiresO(n^2)
time, where we iterate through all possible subarrays from indexi
toj
. - The main dynamic programming solution has three nested loops:
- The outer loop iterates through positions
i
from1
ton
:O(n)
- The middle loop iterates through the number of resizing operations
j
from1
tok
:O(k)
- The inner loop iterates through all possible previous positions
h
from0
toi-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
g
matrix of sizen × n
storing the wasted space for all subarrays:O(n^2)
- The
f
DP 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
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
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 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!