1167. Minimum Cost to Connect Sticks


Problem Description

In this problem, we are given an array sticks, where each element represents the length of a stick. We can connect any two sticks to create a longer stick, but the cost of connecting two sticks is equal to the sum of their lengths. Our goal is to combine all the sticks into one by repeatedly connecting two sticks at a time, while minimizing the total cost of combining them.

To visualize, imagine starting with several separate sticks of different lengths. You can choose any two sticks, combine them, and you will get a new stick whose length is the sum of the two sticks you combined. The challenge lies in finding the sequence of connections that results in the smallest possible sum of all the intermediate costs.

For example, if we have sticks of lengths [2, 4, 3], we could:

  • Combine sticks of lengths 2 and 3 (costing 5), to get [5, 4].
  • Then combine sticks of lengths 5 and 4 (costing 9), to get [9].

The total cost would be 5 + 9 = 14. We are asked to find the strategy that gives us the least cost to combine all sticks into one.

Intuition

The intuition behind the solution is to always combine the two shortest sticks together. This approach is efficient because by combining the smaller sticks first, we prevent larger numbers from being added repeatedly in subsequent combinations, which would happen if we combined longer sticks early on.

To implement this strategy, we can use a data structure called a priority queue or a min-heap. A priority queue allows us to efficiently retrieve and remove the smallest element, while also efficiently adding new elements.

Here’s how we arrive at our solution step by step:

  1. First, we add all the sticks lengths to a min-heap. A min-heap is a binary tree where the parent node is always less than its child nodes. This property will be useful because we always want to combine the two smallest sticks.

  2. Then, we perform the following until there is only one stick left in the heap:

    • Take out the two smallest sticks from the min-heap (this operation is efficient due to the min-heap's properties).
    • Calculate the cost of connecting these two sticks (simply the sum of the two lengths).
    • Add this cost to the running total cost.
    • Insert the resulting new stick back into the min-heap.
  3. This process is repeated until only one stick remains in the min-heap. The running total cost at this point represents the minimum cost to combine all sticks into one, which is what we return.

Using the priority queue ensures that we are always combining the smallest sticks, and the efficient insertions and deletions provided by the min-heap make it optimal for such repetitive operations as required by our approach.

Learn more about Greedy and Heap (Priority Queue) patterns.

Not Sure What to Study? Take the 2-min Quiz to Find Your Missing Piece:

Which of the following problems can be solved with backtracking (select multiple)

Solution Approach

The solution approach for combining the sticks in a cost-effective manner involves the use of a min-heap, which is a type of priority queue. Let's discuss how this data structure is applied in the given Python code to solve the problem.

  1. Heapify the list of sticks: The first operation is to convert the list of stick lengths into a min-heap using the heapify function. In Python, the standard library heapq module provides the functionality for a min-heap. By heapifying the array, we create a data structure that allows us to efficiently retrieve the smallest elements.
1heapify(sticks)
  1. Initialize a variable to keep track of the total cost: We start by setting the total cost ans to 0. This variable will accumulate the cost of connecting the sticks.
1ans = 0
  1. Iterate until one stick remains: We run a loop until there is only one stick left in the heap. Since we always remove two sticks and add one new stick on each iteration, the loop stops when we cannot perform this operation anymore, which is when the length of the sticks array gets reduced to 1.
1while len(sticks) > 1:
  1. Pop the two smallest sticks and add the cost: Inside the loop, we use the heappop function to pop the two smallest elements in the min-heap. The sum of these two sticks represents the cost to connect them, which we add to our total cost ans. Then, a new stick with this sum length is formed.
1z = heappop(sticks) + heappop(sticks)
2ans += z
  1. Push the new stick into the heap: We have a new stick that needs to go into our pile so that we can continue the process. We push the new stick into the min-heap using heappush. The heap property will automatically be maintained, ensuring that this new stick goes into the correct place in the priority queue.
1heappush(sticks, z)
  1. Return the total cost: After the while loop terminates (when only one stick is left), the ans contains the minimum cost to connect all sticks together, which is what we return as the solution.
1return ans

Through the use of a min-heap, this algorithm ensures that at every step, the cost is minimized by always combining the shortest sticks available. The operations inside the loop – popping two elements and pushing one element – all have a logarithmic time complexity relative to the number of elements in the heap, making this approach efficient.

The reference solution approach appropriately cites "Priority queue," which is the concept employed throughout the solution to maintain the efficiency of operations for what would otherwise be a more computationally expensive problem.

Discover Your Strengths and Weaknesses: Take Our 2-Minute Quiz to Tailor Your Study Plan:

Which two pointer technique does Quick Sort use?

Example Walkthrough

Let's use a small example to understand how the solution approach is applied. Suppose we have an array of stick lengths [2, 4, 3, 1]. Here's how we apply the steps of the solution:

  1. Heapify the list of sticks: We start by converting our array [2, 4, 3, 1] into a min-heap. The heapify function arranges the sticks so that the smallest ones can be accessed quickly. Our min-heap will look something like this: [1, 2, 3, 4], where 1 is at the root as it's the smallest.

  2. Initialize the total cost: We'll create a variable ans to keep track of the total cost, initially setting it to 0.

  3. Iterate until one stick remains: We know that the number of sticks will reduce by one with each iteration because we're combining two sticks at a time. We stop when we have one stick left.

  4. Pop the two smallest sticks and add the cost: Now we'll look at our min-heap [1, 2, 3, 4], and pop out the two smallest elements, which are 1 and 2. We combine them at a cost of 1 + 2 = 3, and add that to our running total ans. Now, ans = 3.

  5. Push the new stick into the heap: After combining the sticks with lengths 1 and 2, we get a new stick of length 3 which we push back into our min-heap. The min-heap will reorder itself and look like [3, 3, 4].

  6. Repeat the process: Again, we pop out two smallest sticks from [3, 3, 4], which are both of length 3, combine them at a cost of 3 + 3 = 6, and add that to our ans making it 3 + 6 = 9. We push the new stick of length 6 back into the heap, which now looks like [4, 6].

  7. Final iteration: We pop out the remaining two sticks, combine them at a cost of 4 + 6 = 10, and add that to our total cost, making it ans = 9 + 10 = 19. The min-heap is now [10], but since there's only one stick left, we're done.

  8. Return the total cost: The variable ans now contains the value 19, which is the minimum cost to connect all the sticks in our original array [2, 4, 3, 1].

The sequence of costs incurred at each step were [3, 6, 10], and the sum of these costs, 19, is the least possible total cost to combine the sticks into one. Through the use of heap operations, we've been able to efficiently combine the sticks in a cost-effective manner.

Solution Implementation

1from heapq import heapify, heappop, heappush
2
3class Solution:
4    def connectSticks(self, sticks: List[int]) -> int:
5        # First, convert the list of sticks into a min-heap
6        heapify(sticks)
7      
8        # Initialize total cost to 0
9        total_cost = 0
10
11        # Continue combining sticks until there's only one stick remaining
12        while len(sticks) > 1:
13            # Pop the two smallest sticks from the heap
14            stick1 = heappop(sticks)
15            stick2 = heappop(sticks)
16          
17            # The cost to combine these two sticks
18            combined_cost = stick1 + stick2
19          
20            # Add the combined_cost to the total cost
21            total_cost += combined_cost
22          
23            # Push the combined stick back into the heap
24            heappush(sticks, combined_cost)
25     
26        # Return the total cost incurred to combine all sticks
27        return total_cost
28
1class Solution {
2    public int connectSticks(int[] sticks) {
3        // Initialize a minimum heap (priority queue) to store the sticks in ascending order
4        PriorityQueue<Integer> minHeap = new PriorityQueue<>();
5      
6        // Add all the sticks to the heap
7        for (int stick : sticks) {
8            minHeap.offer(stick);
9        }
10      
11        // Initialize the total cost to connect all the sticks
12        int totalCost = 0;
13      
14        // Continue combining sticks until only one stick remains
15        while (minHeap.size() > 1) {
16            // Retrieve and remove the two smallest sticks
17            int stick1 = minHeap.poll();
18            int stick2 = minHeap.poll();
19          
20            // Calculate the cost of connecting the two smallest sticks
21            int cost = stick1 + stick2;
22          
23            // Add the cost to the total cost
24            totalCost += cost;
25          
26            // Add the combined stick back to the heap
27            minHeap.offer(cost);
28        }
29      
30        // Return the total cost to connect all the sticks
31        return totalCost;
32    }
33}
34
1class Solution {
2public:
3    int connectSticks(vector<int>& sticks) {
4        // Use a min heap to store and retrieve sticks with the smallest lengths first
5        priority_queue<int, vector<int>, greater<int>> min_heap;
6
7        // Insert all the sticks into the min heap
8        for (auto& stickLength : sticks) {
9            min_heap.push(stickLength);
10        }
11
12        int totalCost = 0;  // Initialize total cost to 0
13
14        // Continue this process until only one stick remains in the min heap
15        while (min_heap.size() > 1) {
16            // Extract the two smallest sticks from the min heap
17            int stick1 = min_heap.top();
18            min_heap.pop();
19            int stick2 = min_heap.top();
20            min_heap.pop();
21
22            // Combine the two sticks
23            int newStick = stick1 + stick2;
24
25            // Add the cost of combining the two sticks
26            totalCost += newStick;
27
28            // Put the combined stick back into the min heap
29            min_heap.push(newStick);
30        }
31
32        // Return the total cost to connect all the sticks
33        return totalCost;
34    }
35};
36
1// Definition of our comparison function type.
2type CompareFunction<T> = (lhs: T, rhs: T) => number;
3
4// The data (heap) structure with a null placeholder as the first element.
5let heapData: Array<number | null> = [null];
6
7// The comparison function, defaulting to a min-heap behavior.
8let compare: CompareFunction<number> = (lhs, rhs) => (lhs < rhs ? -1 : lhs > rhs ? 1 : 0);
9
10// Initializes the heap with optional data and comparison function.
11function initializeHeap(data?: number[], customCompare?: CompareFunction<number>): void {
12    if (customCompare) {
13        compare = customCompare;
14    }
15    heapData = [null, ...(data || [])];
16    for (let i = size(); i > 0; i--) {
17        heapify(i);
18    }
19}
20
21// Returns the size of the heap.
22function size(): number {
23    return heapData.length - 1;
24}
25
26// Pushes a new value into the heap.
27function push(value: number): void {
28    heapData.push(value);
29    let i = size();
30    while (i > 1 && compare(heapData[i]!, heapData[i >> 1]!) < 0) {
31        swap(i, i >> 1);
32        i >>= 1;
33    }
34}
35
36// Pops the top value from the heap.
37function pop(): number {
38    swap(1, size());
39    const top = heapData.pop()!;
40    heapify(1);
41    return top;
42}
43
44// Returns the top value in the heap without removing it.
45function top(): number {
46    return heapData[1]!;
47}
48
49// Heapifies the data at the specified index.
50function heapify(i: number): void {
51    let min = i;
52    const n = heapData.length;
53    const l = i * 2;
54    const r = i * 2 + 1;
55    if (l < n && compare(heapData[l]!, heapData[min]!) < 0) min = l;
56    if (r < n && compare(heapData[r]!, heapData[min]!) < 0) min = r;
57    if (min !== i) {
58        swap(i, min);
59        heapify(min);
60    }
61}
62
63// Clears the heap data.
64function clear(): void {
65    heapData = [null];
66}
67
68// Swaps two values in the heap data.
69function swap(i: number, j: number): void {
70    [heapData[i], heapData[j]] = [heapData[j], heapData[i]];
71}
72
73// Main function that calculates the minimum cost to connect the sticks.
74function connectSticks(sticks: number[]): number {
75    initializeHeap(sticks);
76    let totalCost = 0;
77    while (size() > 1) {
78        const x = pop();
79        const y = pop();
80        totalCost += x + y;
81        push(x + y);
82    }
83    return totalCost;
84}
85
Not Sure What to Study? Take the 2-min Quiz:

What is the worst case running time for finding an element in a binary search tree(not necessarily balanced) of size n?

Time and Space Complexity

Time Complexity

The given Python code implements a min-heap to connect sticks with minimum cost. The operation heapify(sticks) has a complexity of O(n) where n is the number of sticks. The while loop runs until there is only one stick left, which happens after n-1 iterations because in each iteration, two sticks are removed and one is added back to the heap.

Inside the loop, two operations of heappop() are performed with a complexity of O(log n) each, to remove the two smallest sticks, and one operation of heappush(), also with a complexity of O(log n), to add the combined stick back to the heap. Considering that there are n-1 iterations, and each iteration has a constant number of heap operations, the total time complexity inside the loop is O((n-1) * log n), which simplifies to O(n log n).

Therefore, the overall time complexity of the code is O(n) + O(n log n), which we can simplify to O(n log n) since O(n) is dominated by O(n log n) for large n.

Space Complexity

The space complexity of the code comes from the storage used for the heap. The heapify() function is performed in-place, and no additional space is used besides the sticks list, which means that the space complexity is O(n) where n is the initial number of sticks in the input list. The integer ans and temporary variable z use constant space.

Thus, the space complexity of the algorithm is O(n).

Learn more about how to find time and space complexity quickly using problem constraints.

Fast Track Your Learning with Our Quick Skills Quiz:

Which of the following problems can be solved with backtracking (select multiple)


Recommended Readings


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 👨‍🏫