LeetCode 3013 - Divide an Array Into Subarrays With Minimum Cost II
The problem asks us to divide a given array nums into k contiguous subarrays in a very specific way. Each subarray has a cost defined as its first element. The objective is to minimize the total cost, which is the sum of the first elements of all k subarrays.
Difficulty: 🔴 Hard
Topics: Array, Hash Table, Sliding Window, Heap (Priority Queue)
Solution
Problem Understanding
The problem asks us to divide a given array nums into k contiguous subarrays in a very specific way. Each subarray has a cost defined as its first element. The objective is to minimize the total cost, which is the sum of the first elements of all k subarrays.
The challenge is constrained by a dist parameter: the difference between the starting index of the second subarray and the starting index of the kth subarray must be less than or equal to dist. This means that while we can pick subarray boundaries freely for cost minimization, the last subarray cannot start too far from the second subarray's start.
The input array length n can be up to 10^5 and elements can be large, up to 10^9. The constraints on k and dist indicate that we always have at least three subarrays and dist is large enough to allow multiple valid divisions but not unrestricted.
Edge cases include arrays where all elements are the same, arrays where dist is minimal or maximal, and arrays with strictly increasing or decreasing numbers.
The expected output is the minimum possible sum of the first elements of k contiguous subarrays that respect the dist constraint.
Approaches
The brute-force approach would be to generate all possible ways to partition the array into k contiguous subarrays, check if the dist constraint holds, and calculate the total cost for each valid partition. This is correct but infeasible because the number of ways to partition an array into k contiguous subarrays grows combinatorially. For n = 10^5 and k = 50, this is astronomically large.
The optimal approach leverages dynamic programming combined with a monotonic queue (or deque) to maintain the minimum DP values efficiently within the dist range. The key insight is that the total cost of a partition ending at index i depends only on the minimum cost of a partition ending in the previous dist range plus nums[i] as the first element of the current subarray. This allows us to calculate the DP state in O(n * k) using a sliding window minimum.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(n^(k)) | O(n) | Try all partitions and pick minimum cost satisfying dist |
| Optimal | O(n * k) | O(n * k) | DP + sliding window minimum optimization for dist constraint |
Algorithm Walkthrough
- Define a DP table
dp[i][j]whereirepresents the number of subarrays formed andjrepresents the ending index of the last subarray.dp[i][j]stores the minimum total cost to partition the firstj+1elements intoisubarrays. - Initialize the first row of DP (
i = 1) withdp[1][j] = nums[0]for all validj, because the first subarray always starts at index 0, so the cost isnums[0]. - For each subarray count
ifrom 2 tok, we iterate through all ending indicesj. For eachj, we want to add a new subarray ending atj. Its first element will benums[start]wherestartis somewhere betweenj-distandjdue to thedistconstraint. - Use a deque to maintain the minimum DP value of the previous subarray count within the valid
distrange. This deque allows O(1) retrieval of the minimum for each new subarray placement. - Update
dp[i][j]as the minimum value from the deque plusnums[j](cost of the new subarray starting atj). - After filling the DP table, the minimum total cost is the minimum value in the last row
dp[k][*].
Why it works: The DP table ensures that we only consider valid contiguous subarray divisions and the deque ensures that we efficiently maintain the minimum total cost for previous partitions within the allowed dist window. This guarantees that all constraints are satisfied and the cost is minimized.
Python Solution
from collections import deque
from typing import List
class Solution:
def minimumCost(self, nums: List[int], k: int, dist: int) -> int:
n = len(nums)
dp = [float('inf')] * n
dp[0] = nums[0]
for subarray_count in range(1, k):
new_dp = [float('inf')] * n
dq = deque()
for j in range(n):
if j >= 1:
while dq and dq[0] < j - dist:
dq.popleft()
while dq and dp[dq[-1]] >= dp[j-1]:
dq.pop()
dq.append(j-1)
if dq:
new_dp[j] = dp[dq[0]] + nums[j]
dp = new_dp
return min(dp)
The implementation first initializes dp for one subarray, then iterates k-1 times to form subsequent subarrays. For each new subarray, a deque maintains the minimum previous DP values that are within the allowed dist window. This efficiently enforces the constraint while computing the minimum total cost.
Go Solution
package main
func minimumCost(nums []int, k int, dist int) int64 {
n := len(nums)
dp := make([]int64, n)
for i := 0; i < n; i++ {
if i == 0 {
dp[i] = int64(nums[0])
} else {
dp[i] = 1<<60
}
}
for subarrayCount := 1; subarrayCount < k; subarrayCount++ {
newDP := make([]int64, n)
for i := 0; i < n; i++ {
newDP[i] = 1<<60
}
dq := []int{}
for j := 0; j < n; j++ {
if j >= 1 {
for len(dq) > 0 && dq[0] < j-dist {
dq = dq[1:]
}
for len(dq) > 0 && dp[dq[len(dq)-1]] >= dp[j-1] {
dq = dq[:len(dq)-1]
}
dq = append(dq, j-1)
}
if len(dq) > 0 {
newDP[j] = dp[dq[0]] + int64(nums[j])
}
}
dp = newDP
}
minCost := int64(1 << 60)
for _, val := range dp {
if val < minCost {
minCost = val
}
}
return minCost
}
The Go implementation mirrors the Python version with slice-based deque operations. Note that we use int64 for large sums and explicitly initialize newDP with a large sentinel value.
Worked Examples
Example 1: nums = [1,3,2,6,4,2], k = 3, dist = 3
| Subarray Count | DP array values | Explanation |
|---|---|---|
| 1 | [1,1,1,1,1,1] | Only first subarray, cost = nums[0] |
| 2 | [inf,2,3,3,4,4] | Second subarray starts at j, min previous dp in dist range used |
| 3 | [inf,inf,5,5,5,5] | Third subarray starts at j, min previous dp in dist range used |
| Result | 5 | min of last DP row |
Example 2: nums = [10,1,2,2,2,1], k = 4, dist = 3
Follow similar steps, maintaining deque and DP updates. Result = 15.
Example 3: nums = [10,8,18,9], k = 3, dist = 1
Result = 36.
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n * k) | For each of the k subarrays, iterate n elements with deque operations amortized O(1) |
| Space | O(n) | Only maintain current and next DP arrays and deque of at most dist elements |
The DP with deque ensures that each index is added and removed at most once, giving amortized constant time per element.
Test Cases
# Provided examples
assert Solution().minimumCost([1,3,2,6,4,2], 3, 3) == 5
assert Solution().minimumCost([10,1,2,2,2,1], 4, 3) == 15
assert Solution().minimumCost([10,8,18,9], 3, 1) == 36
# Edge cases
assert Solution().minimumCost([1,1,1,1], 3, 2) == 3 # All elements same
assert Solution().minimumCost([5,4,3,2,1], 3, 3) == 5 # Decreasing array
assert