2551. Put Marbles in Bags


Problem Description

In this problem, you are tasked with distributing a set of marbles, each with a certain weight, into k bags. The distribution must follow certain rules:

  • Contiguity: The marbles in each bag must be a contiguous subsegment of the array. This means if marbles at indices i and j are in the same bag, all marbles between them must also be in that bag.
  • Non-Empty Bags: Each of the k bags must contain at least one marble.
  • Cost of a Bag: The cost of a bag that contains marbles from index i to j (inclusive) is the sum of the weights at the start and end of the bag, that is weights[i] + weights[j].

Your goal is to find the way to distribute the marbles so that you can calculate the maximum and minimum possible scores, where the score is defined as the sum of the costs of all k bags. The result to be returned is the difference between the maximum and minimum scores possible under the distribution rules.

Intuition

To find the maximum and minimum scores, we need to think about what kind of distributions will increase or decrease the cost of a bag.

For the maximum score, you want to maximize the cost of each bag. You can do this by pairing the heaviest marbles together because their sum will be higher. Conversely, for the minimum score, you would pair the lightest marbles together to minimize the sum.

The intuition behind the solution is grounded on the understanding that the cost of a bag can only involve the first and the last marble in it due to the contiguity requirement. Thus, to minimize or maximize the score, you should look at possible pairings of marbles.

A solution approach involves the following steps:

  1. Precompute the potential costs of all possible bags by pairing each marble with the next one, as you will always have to pick at least one boundary marble from each bag.
  2. Sort these potential costs. The smallest potential costs will be at the beginning of the sorted array, and the largest will be towards the end.
  3. To get the minimum score, sum up the smallest k-1 potential costs, which correspond to the minimum possible cost for each of the bags except one.
  4. To get the maximum score, sum up the largest k-1 potential costs, which correspond to the maximum possible cost for each of the bags except one.
  5. The difference between the maximum and minimum scores will provide the result.

The Python solution provided uses the precomputed potential costs and sums the required segments of it to find the maximum and minimum scores, and then computes their difference.

Solution Approach

The implementation of the solution in Python involves the following steps:

  1. Pairwise Costs: Compute the potential costs of making a bag by pairing each marble with its immediate successor. This is done using the expression a + b for a, b in pairwise(weights), which computes the cost for all possible contiguous pairs of marbles and represents the possible costs of bags if they were to contain only two marbles.

  2. Sorting Costs: Once we have all the potential costs, sort them in ascending order using sorted(). This gives us an array where the smallest potential costs are at the start of the array and the largest potential costs are at the end.

  3. Calculating Minimum Score: To obtain the minimum score, sum up the first k-1 costs from the sorted array with sum(arr[: k - 1]). Each of these costs represents the cost of one of the bags in the minimum score distribution, excluding the last bag, because k-1 bags will always have neighboring marbles from the sorted pairwise costs as boundaries, and the last bag will include all remaining marbles which might or might not follow the pairing rule.

  4. Calculating Maximum Score: To get the maximum score, sum up the last k-1 costs from the sorted array with sum(arr[len(arr) - k + 1:]). Each of these costs represents the cost of one of the bags in the maximum score distribution, excluding the last bag, similar to the minimum score calculation but from the other end due to the sorted order.

  5. Computing the Difference: Finally, the difference between the maximum and the minimum scores is computed by return sum(arr[len(arr) - k + 1 :]) - sum(arr[: k - 1]), which subtracts the minimum score from the maximum score to provide the desired output.

This solution uses a greedy approach that ensures the maximum and minimum possible costs by making use of sorted subsegments of potential costs. The data structures used are simple arrays, and the sorting of the pairwise costs array is the central algorithmic pattern that enables the calculation of max and min scores efficiently.

Example Walkthrough

Let's take a small example to illustrate the solution approach:

Suppose we have an array of marble weights [4, 2, 1, 3] and we want to distribute them into k = 2 bags following the given rules.

Step 1: Pairwise Costs

First, we compute the potential costs for all pairwise marbles:

  • Bag with marbles at index 0 and 1: weights[0] + weights[1] = 4 + 2 = 6
  • Bag with marbles at index 1 and 2: weights[1] + weights[2] = 2 + 1 = 3
  • Bag with marbles at index 2 and 3: weights[2] + weights[3] = 1 + 3 = 4

So the pairwise costs are [6, 3, 4].

Step 2: Sorting Costs

We sort these costs in ascending order: [3, 4, 6].

Step 3: Calculating Minimum Score

To get the minimum score, we need to consider k-1 = 1 smallest pairwise cost, so we sum up the first k-1 costs: 3.

Step 4: Calculating Maximum Score

To get the maximum score, we consider the k-1 largest pairwise costs from the sorted list, so the last k-1 cost: 6.

Step 5: Computing the Difference

The difference between the maximum and minimum scores is 6 - 3 = 3. Therefore, the difference between the highest and the lowest possible scores with the given distribution rules is 3.

In terms of the actual marble distributions:

  • The minimum score distribution would be having one bag with marbles at indices 1 and 2, and the other bag getting the rest (4+2+1+3 = 10, but the last bag's cost is just 4+3=7). Total minimum score: 3 + 7 = 10.
  • The maximum score distribution would be one bag with marbles at indices 0 and 1, and another bag with the rest (2+1+3 = 6, but the last bag's cost is just 2+3=5). Total maximum score: 6 + 5 = 11.

This example clearly illustrates the steps outlined in the solution approach, showing how the precomputed pairwise costs, when sorted and summed appropriately, can yield the minimum and maximum scores, and thus the difference between them.

Python Solution

1from itertools import pairwise  # Import pairwise from itertools for Python versions >= 3.10
2
3class Solution:
4    def putMarbles(self, weights, k):
5        # Generate all possible pairwise sums of weights
6        pairwise_sums = sorted(a + b for a, b in pairwise(weights))
7      
8        # Calculate the sum of the largest k pairwise sums
9        sum_of_largest_k = sum(pairwise_sums[-k:])
10      
11        # Calculate the sum of the smallest k-1 pairwise sums
12        sum_of_smallest_k_minus_1 = sum(pairwise_sums[:k-1])
13      
14        # Return the difference between the sum of the largest k and smallest k-1 pairwise sums
15        return sum_of_largest_k - sum_of_smallest_k_minus_1

It's important to note that the pairwise function is available in the itertools module for Python versions 3.10 and later. If you are using an earlier version of Python, the pairwise function won’t be available, and you would need to define it manually:

1from itertools import tee
2
3def pairwise(iterable):
4    # pairwise('ABCDEFG') --> AB BC CD DE EF FG
5    a, b = tee(iterable)
6    next(b, None)
7    return zip(a, b)
8

Java Solution

1import java.util.Arrays;
2
3class Solution {
4    // Method to calculate the sum based on the given weights and integer k
5    public long putMarbles(int[] weights, int k) {
6        // Get the number of weights
7        int numWeights = weights.length;
8      
9        // Create an array to store the sum of adjacent weights
10        int[] adjacentSums = new int[numWeights - 1];
11      
12        // Calculate the sum of adjacent weights and store in the array
13        for (int i = 0; i < numWeights - 1; ++i) {
14            adjacentSums[i] = weights[i] + weights[i + 1];
15        }
16      
17        // Sort the array of adjacent sums
18        Arrays.sort(adjacentSums);
19
20        // Initialize answer to 0
21        long answer = 0;
22      
23        // Calculate the sum of the largest k - 1 elements
24        // and subtract the sum of the smallest k - 1 elements from it
25        for (int i = 0; i < k - 1; ++i) {
26            answer -= adjacentSums[i];
27            answer += adjacentSums[numWeights - 2 - i];
28        }
29      
30        // Return the final answer
31        return answer;
32    }
33}
34

C++ Solution

1#include <vector>
2#include <algorithm> // Include necessary headers
3
4class Solution {
5public:
6    long long putMarbles(std::vector<int>& weights, int k) {
7        // Finding the size of the input vector weights.
8        int numWeights = weights.size();
9
10        // Create a new vector to store the sum of adjacent weights.
11        std::vector<int> adjacentSum(numWeights - 1);
12
13        // Calculate the sum of adjacent weights and fill the adjacentSum vector.
14        for (int i = 0; i < numWeights - 1; ++i) {
15            adjacentSum[i] = weights[i] + weights[i + 1];
16        }
17
18        // Sort the adjacentSum vector in non-decreasing order.
19        std::sort(adjacentSum.begin(), adjacentSum.end());
20
21        // Initialize ans to store the final result.
22        long long ans = 0;
23
24        // Calculate the answer by picking k - 1 smallest elements and k - 1 largest elements from sorted adjacentSum.
25        for (int i = 0; i < k - 1; ++i) {
26            ans -= adjacentSum[i]; // Subtract the k - 1 smallest elements.
27            ans += adjacentSum[numWeights - 2 - i]; // Add the k - 1 largest elements.
28        }
29
30        // Return the final computed result.
31        return ans;
32    }
33};
34

Typescript Solution

1/**
2 * Calculate the difference between the sum of the heaviest and lightest marbles after k turns.
3 *
4 * @param {number[]} weights - An array of integers representing the weights of the marbles.
5 * @param {number} k - The number of turns.
6 * @return {number} The difference in weight between the selected heaviest and lightest marbles.
7 */
8function putMarbles(weights: number[], k: number): number {
9    // Number of weights provided.
10    const numWeights = weights.length;
11
12    // Will hold the sum of pairs of weights.
13    const pairSums: number[] = [];
14
15    // Calculate the sum of each pair of adjacent weights.
16    for (let i = 0; i < numWeights - 1; ++i) {
17        pairSums.push(weights[i] + weights[i + 1]);
18    }
19
20    // Sort the pair sums in ascending order.
21    pairSums.sort((a, b) => a - b);
22
23    // This will hold the computed result.
24    let difference = 0;
25
26    // Sum the difference between the heaviest and lightest pair sums for k - 1 turns.
27    for (let i = 0; i < k - 1; ++i) {
28        // The heaviest pair is at the back of the sorted array.
29        // As the index in JavaScript is 0-based, the (n-i-2) gives us the element just before the last i elements.
30        difference += pairSums[numWeights - i - 2] - pairSums[i];
31    }
32
33    // Return the calculated difference.
34    return difference;
35}
36

Time and Space Complexity

Time Complexity

The time complexity of the provided code can be broken down as follows:

  1. sorted(a + b for a, b in pairwise(weights)):
    • First, the pairwise function creates an iterator over adjacent pairs in the list weights. This is done in O(N) time, where N is the number of elements in weights.
    • The generator expression a + b for a, b in pairwise(weights) computes the sum of each pair, resulting in a total of O(N) operations since it processes each pair once.
    • The sorted function then sorts the sums, which has a time complexity of O(N log N), where N is the number of elements produced by pairwise, which is N - 1. However, we simplify this to O(N log N) for the complexity analysis.

Combining these steps, the time complexity is primarily dominated by the sorting step, resulting in an overall time complexity of O(N log N).

  1. sum(arr[len(arr) - k + 1 :]) and sum(arr[: k - 1]):
    • Both sum(...) operations are performed on slices of the sorted list arr. The number of elements being summed in each case depends on k, but in the worst case, it will sum up to N - 1 elements (the length of arr), which is O(N).

The combination of sorting and summing operations results in a total time complexity of O(N log N), with the sorting step being the most significant factor.

Space Complexity

The space complexity of the code can be examined as follows:

  1. The sorted array arr is a new list created from the sums of pairwise elements, which contains N - 1 elements. Hence, this requires O(N) space.

  2. The slices made in the sum operations do not require additional space beyond the list arr, as slicing in Python does not create a new list but rather a view of the existing list.

Therefore, the overall space complexity of the code is O(N) due to the space required for the sorted list arr.


Got a question? Ask the Teaching Assistant anything you don't understand.

Still not clear? Ask in the Forum,  Discord or Submit the part you don't understand to our editors.


TA 👨‍🏫