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:
- Extracts the two smallest sticks from the heap
- Connects them (calculating the cost)
- Adds the cost to the running total
- Inserts the new combined stick back into the heap
- Continues until only one stick remains
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
andb
, we paya + 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:
- Smaller values are "promoted" to larger sticks early
- Larger values participate in fewer connection operations
- 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 EvaluatorExample 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 takesO(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)
- Two
- Since we have
n - 1
iterations, the loop contributesO(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 usesO(1)
extra space. - The heap operations (
heappop
andheappush
) 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.
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
Greedy Introduction div class responsive iframe iframe src https www youtube com embed WTslqPbj7I title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture web share allowfullscreen iframe div When do we use greedy Greedy algorithms tend to solve optimization problems Typically they will ask you to calculate the max min of some value Commonly you may see this phrased in the problem as max min longest shortest largest smallest etc These keywords can be identified by just scanning
https assets algo monster cover_photos heap svg Priority Queue and Heap What is the relationship between priority queue and heap Priority Queue is an Abstract Data Type and Heap is the concrete data structure we use to implement a priority queue Priority Queue A priority queue is a data structure
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Don’t Miss This!