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:
-
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.
-
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.
-
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.
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.
- 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 libraryheapq
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.
heapify(sticks)
- 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.
ans = 0
- 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.
while len(sticks) > 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 costans
. Then, a new stick with this sum length is formed.
z = heappop(sticks) + heappop(sticks) ans += z
- 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.
heappush(sticks, z)
- 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.
return 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.
Ready to land your dream job?
Unlock your dream job with a 2-minute evaluator for a personalized learning plan!
Start EvaluatorExample 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:
-
Heapify the list of sticks: We start by converting our array
[2, 4, 3, 1]
into a min-heap. Theheapify
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. -
Initialize the total cost: We'll create a variable
ans
to keep track of the total cost, initially setting it to 0. -
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.
-
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 of1 + 2 = 3
, and add that to our running totalans
. Now,ans = 3
. -
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]
. -
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 of3 + 3 = 6
, and add that to ourans
making it3 + 6 = 9
. We push the new stick of length 6 back into the heap, which now looks like[4, 6]
. -
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 itans = 9 + 10 = 19
. The min-heap is now[10]
, but since there's only one stick left, we're done. -
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
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.
Which of these properties could exist for a graph but not a tree?
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 algomonster s3 us east 2 amazonaws com 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
LeetCode 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 we
Want a Structured Path to Master System Design Too? Don’t Miss This!