Facebook Pixel

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 least nums[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 index i to j
  • f[i][j] represents the minimum wasted space for the first i elements using j segments (resize operations)
  • The problem transforms into partitioning the array into k+1 segments optimally
Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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:

  1. Consider all possible ways to partition the array
  2. For each partition point, calculate the cost (wasted space) of that segment
  3. 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 segment
  • mx 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 since k resizes create k+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 into j segments
  • Try every possible position h for where the (j-1)-th segment ends
  • The last segment covers elements from index h to i-1 (in 0-indexed terms)
  • Total cost = cost of first h elements in j-1 segments + cost of segment [h, i-1]
  • Formula: f[i][j] = min(f[h][j-1] + g[h][i-1]) for all valid h

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 Evaluator

Example 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 requires O(n^2) time, where we iterate through all possible subarrays from index i to j.
  • The main dynamic programming solution has three nested loops:
    • The outer loop iterates through positions i from 1 to n: O(n)
    • The middle loop iterates through the number of resizing operations j from 1 to k: O(k)
    • The inner loop iterates through all possible previous positions h from 0 to i-1: O(n)

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 size n × 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
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

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

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

Load More