Facebook Pixel

1167. Minimum Cost to Connect Sticks 🔒

Problem Description

You are given an array sticks where each element represents the length of a stick (positive integer).

You can connect any two sticks together to form a single stick. When you connect two sticks with lengths x and y, the resulting stick has length x + y, and you must pay a cost equal to x + y.

Your goal is to connect all the sticks together until only one stick remains. You need to find the minimum total cost to achieve this.

For example, if you have sticks with lengths [2, 4, 3]:

  • You could first connect sticks of length 2 and 3, paying cost 5, resulting in sticks [5, 4]
  • Then connect the remaining two sticks of length 5 and 4, paying cost 9
  • Total cost would be 5 + 9 = 14

The strategy uses a greedy approach with a min heap. By always connecting the two shortest sticks first, we minimize the cost at each step. The algorithm repeatedly:

  1. Extracts the two smallest sticks from the heap
  2. Connects them (calculating the cost)
  3. Adds the cost to the running total
  4. Inserts the new combined stick back into the heap
  5. Continues until only one stick remains
Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The key insight is that when we connect two sticks, the resulting stick will potentially be used in future connections. Since we pay the sum of lengths each time we connect, longer sticks that participate in multiple connections will contribute their length to the total cost multiple times.

Think of it this way: if we have a very long stick, we want to use it as few times as possible in our connections. Conversely, shorter sticks should be combined early because their smaller values will be added to the total cost multiple times as they form parts of larger sticks.

Consider why connecting the two shortest sticks is optimal at each step:

  • When we connect sticks of length a and b, we pay a + b
  • The resulting stick of length a + b might be used again in future connections
  • If we had instead connected a short stick with a long stick early, that long stick's value would be included in more subsequent operations, increasing the total cost

This naturally leads to a greedy strategy: always connect the two shortest available sticks. By doing this, we ensure that:

  1. Smaller values are "promoted" to larger sticks early
  2. Larger values participate in fewer connection operations
  3. The overall cost is minimized

A min heap (priority queue) is the perfect data structure for this approach because it allows us to efficiently:

  • Extract the two minimum elements in O(log n) time
  • Insert the newly formed stick back in O(log n) time
  • Always maintain access to the current shortest sticks

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

Solution Approach

The solution implements the greedy strategy using Python's built-in heapq module, which provides a min heap implementation.

Step 1: Initialize the Min Heap

heapify(sticks)

We transform the input array sticks into a min heap in-place. This operation takes O(n) time and ensures the smallest element is always at index 0.

Step 2: Initialize Cost Counter

ans = 0

We maintain a variable ans to track the cumulative cost of all connections.

Step 3: Connect Sticks Until One Remains

while len(sticks) > 1:

We continue the process as long as there are at least 2 sticks to connect.

Step 4: Extract and Connect Two Smallest Sticks

z = heappop(sticks) + heappop(sticks)

In each iteration:

  • heappop(sticks) removes and returns the smallest stick (first call)
  • heappop(sticks) removes and returns the second smallest stick (second call)
  • We add their lengths to get the new stick length z

Step 5: Accumulate Cost

ans += z

The cost of this connection (which equals the length of the new stick) is added to our total cost.

Step 6: Insert New Stick Back

heappush(sticks, z)

The newly formed stick is inserted back into the heap, maintaining the heap property.

Time Complexity: O(n log n) where n is the number of sticks. We perform approximately n-1 iterations, and each iteration involves two heappop operations and one heappush operation, each taking O(log n) time.

Space Complexity: O(1) extra space since we modify the input array in-place (not counting the space used by the input itself).

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 the solution with sticks = [1, 8, 3, 5]:

Initial State:

  • Array: [1, 8, 3, 5]
  • After heapify: [1, 3, 8, 5] (min heap structure)
  • Total cost: 0

Iteration 1:

  • Extract two smallest: 1 and 3
  • Connect them: 1 + 3 = 4
  • Add cost: total = 0 + 4 = 4
  • Insert back: heap becomes [4, 5, 8]

Iteration 2:

  • Extract two smallest: 4 and 5
  • Connect them: 4 + 5 = 9
  • Add cost: total = 4 + 9 = 13
  • Insert back: heap becomes [8, 9]

Iteration 3:

  • Extract two smallest: 8 and 9
  • Connect them: 8 + 9 = 17
  • Add cost: total = 13 + 17 = 30
  • Insert back: heap becomes [17]

Final Result:

  • Only one stick remains
  • Minimum total cost = 30

The greedy approach ensures we always combine the smallest sticks first, minimizing how many times larger values contribute to the total cost. Notice how the stick of length 8 (initially the largest) only participated in one connection operation, while smaller sticks like 1 and 3 had their values included multiple times through the combined sticks they formed.

Solution Implementation

1from typing import List
2from heapq import heapify, heappop, heappush
3
4class Solution:
5    def connectSticks(self, sticks: List[int]) -> int:
6        # Convert the list into a min-heap in-place
7        heapify(sticks)
8      
9        # Track the total cost of connecting all sticks
10        total_cost = 0
11      
12        # Keep combining sticks until only one remains
13        while len(sticks) > 1:
14            # Extract the two smallest sticks from the heap
15            first_stick = heappop(sticks)
16            second_stick = heappop(sticks)
17          
18            # Calculate the cost of combining these two sticks
19            combined_length = first_stick + second_stick
20          
21            # Add this combination cost to the total
22            total_cost += combined_length
23          
24            # Push the combined stick back into the heap
25            heappush(sticks, combined_length)
26      
27        return total_cost
28
1class Solution {
2    /**
3     * Connects sticks with minimum cost using a greedy approach.
4     * The strategy is to always combine the two smallest sticks first.
5     * 
6     * @param sticks Array of stick lengths to be connected
7     * @return The minimum total cost to connect all sticks into one
8     */
9    public int connectSticks(int[] sticks) {
10        // Min heap to store stick lengths, ensuring smallest elements are accessed first
11        PriorityQueue<Integer> minHeap = new PriorityQueue<>();
12      
13        // Add all stick lengths to the min heap
14        for (int stickLength : sticks) {
15            minHeap.offer(stickLength);
16        }
17      
18        // Track the total cost of connecting all sticks
19        int totalCost = 0;
20      
21        // Keep combining sticks until only one remains
22        while (minHeap.size() > 1) {
23            // Extract the two smallest sticks
24            int firstSmallest = minHeap.poll();
25            int secondSmallest = minHeap.poll();
26          
27            // Calculate the cost of combining these two sticks
28            int combinedLength = firstSmallest + secondSmallest;
29          
30            // Add the combination cost to the total
31            totalCost += combinedLength;
32          
33            // Put the combined stick back into the heap for further combinations
34            minHeap.offer(combinedLength);
35        }
36      
37        return totalCost;
38    }
39}
40
1class Solution {
2public:
3    int connectSticks(vector<int>& sticks) {
4        // Use a min-heap to always access the smallest sticks efficiently
5        // This ensures we minimize the cost by combining smallest sticks first
6        priority_queue<int, vector<int>, greater<int>> minHeap;
7      
8        // Add all sticks to the min-heap
9        for (const int& stickLength : sticks) {
10            minHeap.push(stickLength);
11        }
12      
13        // Track the total cost of connecting all sticks
14        int totalCost = 0;
15      
16        // Keep combining sticks until only one remains
17        while (minHeap.size() > 1) {
18            // Extract the two smallest sticks
19            int firstSmallest = minHeap.top();
20            minHeap.pop();
21          
22            int secondSmallest = minHeap.top();
23            minHeap.pop();
24          
25            // Calculate the cost of combining these two sticks
26            int combinedLength = firstSmallest + secondSmallest;
27          
28            // Add the cost to the total
29            totalCost += combinedLength;
30          
31            // Push the combined stick back to the heap for further combinations
32            minHeap.push(combinedLength);
33        }
34      
35        return totalCost;
36    }
37};
38
1// Comparison function type for generic heap operations
2type Compare<T> = (lhs: T, rhs: T) => number;
3
4// Heap data structure array with null at index 0 for easier indexing
5let heapData: Array<number | null> = [null];
6
7// Comparison function for heap operations
8let heapCompare: Compare<number> = (lhs: number, rhs: number) => 
9    (lhs < rhs ? -1 : lhs > rhs ? 1 : 0);
10
11// Less than comparison using heap indices
12function isLessThan(i: number, j: number): boolean {
13    return heapCompare(heapData[i]!, heapData[j]!) < 0;
14}
15
16// Swap two elements in the heap by their indices
17function swapElements(i: number, j: number): void {
18    [heapData[i], heapData[j]] = [heapData[j], heapData[i]];
19}
20
21// Initialize heap with given array of numbers
22function initializeHeap(sticks: number[]): void {
23    heapData = [null, ...sticks];
24    // Build heap from bottom up
25    for (let i = heapSize(); i > 0; i--) {
26        heapify(i);
27    }
28}
29
30// Get current size of the heap (excluding null at index 0)
31function heapSize(): number {
32    return heapData.length - 1;
33}
34
35// Add a new element to the heap and maintain heap property
36function heapPush(value: number): void {
37    heapData.push(value);
38    let currentIndex = heapSize();
39    // Bubble up: swap with parent while current is smaller
40    while (currentIndex >> 1 !== 0 && isLessThan(currentIndex, currentIndex >> 1)) {
41        swapElements(currentIndex, currentIndex >> 1);
42        currentIndex = currentIndex >> 1;
43    }
44}
45
46// Remove and return the minimum element from the heap
47function heapPop(): number {
48    // Move last element to root
49    swapElements(1, heapSize());
50    const minElement = heapData.pop();
51    // Restore heap property from root
52    heapify(1);
53    return minElement!;
54}
55
56// Restore heap property starting from index i (bubble down)
57function heapify(i: number): void {
58    while (true) {
59        let minIndex = i;
60        const leftChild = i * 2;
61        const rightChild = i * 2 + 1;
62        const heapLength = heapData.length;
63      
64        // Find minimum among node and its children
65        if (leftChild < heapLength && isLessThan(leftChild, minIndex)) {
66            minIndex = leftChild;
67        }
68        if (rightChild < heapLength && isLessThan(rightChild, minIndex)) {
69            minIndex = rightChild;
70        }
71      
72        // If minimum is not the current node, swap and continue
73        if (minIndex !== i) {
74            swapElements(i, minIndex);
75            i = minIndex;
76        } else {
77            break;
78        }
79    }
80}
81
82// Main function to connect sticks with minimum cost
83// Strategy: Always connect the two smallest sticks to minimize total cost
84function connectSticks(sticks: number[]): number {
85    // Initialize min heap with all sticks
86    initializeHeap(sticks);
87  
88    let totalCost = 0;
89  
90    // Keep connecting sticks until only one remains
91    while (heapSize() > 1) {
92        // Get two smallest sticks
93        const firstStick = heapPop();
94        const secondStick = heapPop();
95      
96        // Cost of connecting is sum of both sticks
97        const connectionCost = firstStick + secondStick;
98        totalCost += connectionCost;
99      
100        // Add the new combined stick back to heap
101        heapPush(connectionCost);
102    }
103  
104    return totalCost;
105}
106

Time and Space Complexity

The time complexity is O(n × log n), where n is the length of the array sticks.

  • The initial heapify(sticks) operation takes O(n) time to build a min-heap from the array.
  • The while loop runs n - 1 times (until only one stick remains).
  • In each iteration, we perform:
    • Two heappop() operations: O(log n) each
    • One heappush() operation: O(log n)
    • Total per iteration: O(log n)
  • Since we have n - 1 iterations, the loop contributes O(n × log n) time.
  • Overall time complexity: O(n) + O(n × log n) = O(n × log n)

The space complexity is O(n), where n is the length of the array sticks.

  • The heapify() operation modifies the input array in-place and uses O(1) extra space.
  • The heap operations (heappop and heappush) work on the existing array without requiring additional space proportional to the input size.
  • The space is dominated by the input array itself, which has size n.
  • Therefore, the space complexity is O(n).

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

Common Pitfalls

1. Edge Case: Single or Empty Stick Array

Pitfall: The code doesn't explicitly handle the case where the input contains only one stick or is empty. While the current implementation works correctly (returning 0 for a single stick), it's not immediately obvious why.

Why it happens: When len(sticks) == 1, the while loop never executes, and total_cost remains 0. This is correct since no connections are needed, but it's implicit rather than explicit.

Solution:

def connectSticks(self, sticks: List[int]) -> int:
    # Explicitly handle edge cases for clarity
    if len(sticks) <= 1:
        return 0
  
    heapify(sticks)
    total_cost = 0
    # ... rest of the code

2. Using Wrong Heap Type (Max Heap Instead of Min Heap)

Pitfall: Attempting to use a max heap or forgetting that Python's heapq is a min heap by default. Some might try to negate values to simulate a max heap, which would give incorrect results for this problem.

Why it happens: In some problems, we need a max heap and use negation as a workaround. Applying this pattern here would cause us to connect the largest sticks first, resulting in suboptimal cost.

Incorrect approach example:

# WRONG: Don't negate values for this problem!
heapify([-x for x in sticks])  # This would create wrong behavior

Solution: Use the min heap directly as shown in the original code. The algorithm requires connecting smallest sticks first.

3. Modifying Original Input Without Consideration

Pitfall: The solution modifies the input array sticks in-place. If the caller expects the original array to remain unchanged, this causes unexpected side effects.

Why it happens: Using heapify(sticks) modifies the original list structure. After the function returns, the original sticks array is altered.

Solution: Create a copy if preserving the original is important:

def connectSticks(self, sticks: List[int]) -> int:
    # Create a copy to avoid modifying the original
    sticks_heap = sticks.copy()  # or sticks[:]
    heapify(sticks_heap)
  
    total_cost = 0
    while len(sticks_heap) > 1:
        combined_length = heappop(sticks_heap) + heappop(sticks_heap)
        total_cost += combined_length
        heappush(sticks_heap, combined_length)
  
    return total_cost

4. Inefficient Two-Line Pop Operations

Pitfall: Some might write the extraction of two sticks in separate lines with intermediate variables when not needed for clarity.

Less efficient style:

first = heappop(sticks)
second = heappop(sticks)
combined = first + second

Why it matters: While functionally correct, it uses more lines and variables than necessary. The original compact form z = heappop(sticks) + heappop(sticks) is more concise.

Note: This is more about style than correctness. Choose based on team preferences for readability vs. conciseness.

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

What is the best way of checking if an element exists in an unsorted array once in terms of time complexity? Select the best that applies.


Recommended Readings

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

Load More