You are given an 0-indexed integer array weights, where weights[i] represents the weight of the i-th marble, and an integer k.
Your task is to divide the marbles into k bags such that:
i and j, then all marbles with indices between i and j (inclusive) must also be included in that same bag.i to j (inclusive) is defined as weights[i] + weights[j].The total score of a distribution is the sum of the costs of all k bags.
Return the difference between the maximum and minimum possible scores among all valid distributions.
Example 1:
Input: weights = [5,7,9,5], k = 2
Output: 4Explanation:
The distribution [5,7],[9,5] results in maximal score of (5 + 7) + (9 + 5) = 26.
The distribution [5],[7,9,5] results in minimal score of (5 + 5) + (7 + 5) = 22.
The difference between maximum and minimum scores is 4.
Example 2:
Input: weights = [1,4,2,5,2], k = 3
Output: 3Explanation:
The distribution [1,4,2],[5],[2] results in maximal score of (1 + 2) + (5 + 5) + (2 + 2) = 17.
The distribution [1],[4],[2,5,2] results in minimal score of (1 + 1) + (4 + 4) + (2 + 2) = 14.
The difference between maximum and minimum scores is 3.
Constraints:
1 <= k <= weights.length <= 100,0001 <= weights[i] <= 1,000,000,000Before attempting this problem, you should be comfortable with:
We need to partition marbles into k bags and find the difference between maximum and minimum possible scores. Each bag's cost is the sum of its first and last marble weights. Using memoization, we can explore all possible partition points. At each position, we decide whether to make a cut (starting a new bag) or continue the current bag, tracking both max and min scores simultaneously.
dfs(i, k) that returns [maxScore, minScore] for partitioning marbles from index i with k cuts remaining.k == 0, no more cuts needed, return [0, 0].weights[i] + weights[i + 1] to the score and recurse with k - 1.k.maxScore - minScore from the initial call.class Solution:
def putMarbles(self, weights: List[int], k: int) -> int:
n = len(weights)
cache = {}
def dfs(i, k):
if (i, k) in cache:
return cache[(i, k)]
if k == 0:
return [0, 0]
if i == n - 1 or n - i - 1 < k:
return [-float("inf"), float("inf")]
res = [0, float("inf")] # [maxScore, minScore]
# make partition
cur = dfs(i + 1, k - 1)
res[0] = max(res[0], weights[i] + weights[i + 1] + cur[0])
res[1] = min(res[1], weights[i] + weights[i + 1] + cur[1])
# skip
cur = dfs(i + 1, k)
res[0] = max(res[0], cur[0])
res[1] = min(res[1], cur[1])
cache[(i, k)] = res
return res
ans = dfs(0, k - 1)
return ans[0] - ans[1]Where is the number of marbles, and is the number of bags.
The key observation is that the first and last marbles always contribute to the total score regardless of how we partition. What matters is where we place the k-1 dividers. Each divider at position i adds weights[i] + weights[i+1] to the score. To maximize the score, pick the k-1 largest such sums. To minimize, pick the k-1 smallest. The difference between these gives our answer.
k == 1, return 0 (only one bag, no choices to make).weights[i] + weights[i+1] for each valid i.k-1 values for the minimum score.k-1 values for the maximum score.maxScore - minScore.class Solution:
def putMarbles(self, weights: List[int], k: int) -> int:
if k == 1:
return 0
splits = []
for i in range(len(weights) - 1):
splits.append(weights[i] + weights[i + 1])
splits.sort()
i = k - 1
max_score = sum(splits[-i:])
min_score = sum(splits[:i])
return max_score - min_scoreWhere is the number of marbles, and is the number of bags.
Instead of sorting all adjacent pair sums, we can use two heaps to efficiently track just the k-1 largest and k-1 smallest values. A min-heap keeps the k-1 largest sums (evicting smaller ones), while a max-heap keeps the k-1 smallest sums (evicting larger ones). This is more efficient when k is small relative to n.
k == 1, return 0.k-1.weights[i] + weights[i+1]:k-1, remove the smallest.k-1, remove the largest.maxScore - minScore.class Solution:
def putMarbles(self, weights: List[int], k: int) -> int:
if k == 1:
return 0
max_heap = []
min_heap = []
for i in range(len(weights) - 1):
split = weights[i] + weights[i + 1]
if len(max_heap) < k - 1:
heapq.heappush(max_heap, split)
else:
heapq.heappushpop(max_heap, split)
if len(min_heap) < k - 1:
heapq.heappush(min_heap, -split)
else:
heapq.heappushpop(min_heap, -split)
max_score = sum(max_heap)
min_score = -sum(min_heap)
return max_score - min_scoreWhere is the number of marbles, and is the number of bags.
Each bag's score is the sum of its first and last marble weights. A common mistake is thinking only endpoints matter globally. The key insight is that internal partition points contribute weights[i] + weights[i+1] to the total score, while the first and last marbles of the entire array always contribute and cancel out in the difference.
With k bags, we need exactly k-1 partition points (splits), not k. Selecting k splits creates k+1 bags. This off-by-one error leads to selecting too many or too few adjacent pair sums when computing max and min scores.
When k=1, there are no partition choices since all marbles go into one bag. The max and min scores are identical, so the answer is 0. Forgetting this case and attempting to select 0 elements from the splits array causes errors.
Adjacent pair sums can be up to 2 * 10^9, and summing k-1 of them can overflow 32-bit integers. Using 64-bit integers (long in Java, int64 in Go) prevents overflow issues when computing max and min scores.