Coding · Heap (priority queue)
Cheapest way to join ropes
You have ropes of the given lengths. Joining two ropes of lengths a and b produces one rope of length a + b and costs a + b. Join ropes until one remains. Return the minimum possible total cost.
Examples
| Input | Output | Why |
|---|---|---|
ropes = [2, 4, 3] | 14 | Join 2 and 3 (cost 5), then 5 and 4 (cost 9). |
ropes = [1, 8, 3, 5] | 30 | 1+3 = 4, 4+5 = 9, 9+8 = 17; total 30. |
ropes = [5] | 0 | Nothing to join. |
Constraints
- 1 ≤ n ≤ 10⁴
- 1 ≤ ropes[i] ≤ 10⁴
Hints
Hint 1
A rope that is joined early is paid for again in every later join it takes part in. Which ropes should be joined first?
Hint 2
Always join the two shortest ropes available, then put the result back among the others.
Hint 3
You need the two smallest values repeatedly from a changing collection: that is what a min-heap is for.
Dry run on [1,8,3,5]
Read it one frame at a time, the way you would trace the code on paper in an interview. Each frame shows the state after the step it describes.
-
Step 1 / 4heap (sorted view)10315283cost0
Put all the ropes in a min-heap. The two shortest are always at hand.
-
Step 2 / 4heap (sorted view)405182cost4
Take the two shortest, 1 and 3. Joining them costs 4; the total is now 4. The new rope of 4 goes back into the heap.
-
Step 3 / 4heap (sorted view)8091cost13
Take the two shortest, 4 and 5. Joining them costs 9; the total is now 13. The new rope of 9 goes back into the heap.
-
Step 4 / 4heap (sorted view)170cost30
Take the two shortest, 8 and 9. Joining them costs 17; the total is now 30. The new rope of 17 goes back into the heap. One rope is left, so the minimum total is 30.
How to think about it
Each join's cost is added to the total, and the rope it produces takes part in later joins,
so its length is paid again each time. Picture the joins as a binary tree: every original rope is a
leaf, and its length is paid once for every join above it, that is, once per level of depth. The
total cost is the sum of length × depth over all ropes.
To minimise that, the longest ropes should be shallowest and the shortest deepest. The greedy rule does exactly this: always join the two shortest ropes. It is the same argument that proves Huffman coding optimal: in some optimal tree the two shortest ropes are siblings at the deepest level, so joining them first loses nothing, and the rest of the problem has the same form.
A min-heap supplies the two shortest in O(log n) each, and takes the joined rope back in O(log n): O(n log n) overall.
Greedy choices need a proof, and interviewers ask for one here. "Short ropes are paid for more often, so they should be joined first" is the idea; the exchange argument above is the proof.
Complexity. Time O(n log n) · Space O(n)
The solution, in six languages
Each one is compiled and run against the test table before it is published. Java is reviewed by hand.
import heapq
def connect_ropes(ropes):
heap = list(ropes)
heapq.heapify(heap) # O(n)
cost = 0
while len(heap) > 1:
a = heapq.heappop(heap) # the two shortest ropes
b = heapq.heappop(heap)
cost += a + b
heapq.heappush(heap, a + b) # the joined rope competes again
return cost#include <vector>
#include <queue>
#include <functional>
long long solve(const std::vector<int>& ropes) {
std::priority_queue<long long, std::vector<long long>, std::greater<long long>> heap(ropes.begin(), ropes.end());
long long cost = 0;
while (heap.size() > 1) {
long long a = heap.top(); heap.pop(); // the two shortest ropes
long long b = heap.top(); heap.pop();
cost += a + b;
heap.push(a + b);
}
return cost;
}class Solution {
public long connectRopes(int[] ropes) {
java.util.PriorityQueue<Long> heap = new java.util.PriorityQueue<>();
for (int r : ropes) heap.add((long) r);
long cost = 0;
while (heap.size() > 1) {
long a = heap.poll(), b = heap.poll(); // the two shortest ropes
cost += a + b;
heap.add(a + b);
}
return cost;
}
}function connectRopes(ropes) {
const h = []; // binary min-heap
const push = x => {
h.push(x); let i = h.length - 1;
while (i > 0) { const p = (i - 1) >> 1; if (h[p] <= h[i]) break; [h[p], h[i]] = [h[i], h[p]]; i = p; }
};
const pop = () => {
const top = h[0], last = h.pop();
if (h.length) {
h[0] = last; let i = 0;
for (;;) {
const l = 2 * i + 1, r = l + 1; let m = i;
if (l < h.length && h[l] < h[m]) m = l;
if (r < h.length && h[r] < h[m]) m = r;
if (m === i) break;
[h[m], h[i]] = [h[i], h[m]]; i = m;
}
}
return top;
};
ropes.forEach(push);
let cost = 0;
while (h.length > 1) {
const a = pop(), b = pop(); // the two shortest ropes
cost += a + b;
push(a + b);
}
return cost;
}use std::cmp::Reverse;
use std::collections::BinaryHeap;
fn solve(ropes: &[i64]) -> i64 {
let mut heap: BinaryHeap<Reverse<i64>> = ropes.iter().map(|&r| Reverse(r)).collect();
let mut cost = 0;
while heap.len() > 1 {
let Reverse(a) = heap.pop().unwrap(); // the two shortest ropes
let Reverse(b) = heap.pop().unwrap();
cost += a + b;
heap.push(Reverse(a + b));
}
cost
}import qualified Data.Map.Strict as M
-- A Map from length to count serves as the min-heap.
solve :: [Int] -> Int
solve ropes = go (foldr push M.empty ropes) (length ropes) 0
where
push x = M.insertWith (+) x 1
popMin m = case M.findMin m of
(v, 1) -> (v, M.delete v m)
(v, c) -> (v, M.insert v (c - 1) m)
go m n cost
| n <= 1 = cost
| otherwise =
let (a, m1) = popMin m -- the two shortest ropes
(b, m2) = popMin m1
in go (push (a + b) m2) (n - 1) (cost + a + b)
Where people lose marks
- Sorting once and joining left to right. After the first join, the new rope may be longer than the next rope in the list, and the order is wrong.
- Adding only the final rope length to the cost. Every join is paid for, including the intermediate ones.
- Overflow: with 10⁴ ropes of length 10⁴ the total reaches the order of 10¹⁰. Use 64-bit integers.
- Returning the last rope’s length instead of the accumulated cost.
Variants to try
- Huffman coding: the same algorithm on character frequencies builds the optimal prefix code.
- Join k ropes at a time instead of two: pad with zero-length ropes so that (n − 1) is divisible by (k − 1), then always join the k shortest.
- The lengths are already sorted: use two queues (the originals and the joined ropes, which come out in increasing order) for O(n) time.
Next problems
The k-th largest value
Keep only the k largest values seen so far. The smallest of them, on top of a min-heap, is the answer.
Open the problem → Hard25 minMost capital from at most k projects
Projects unlock as your capital grows. A sort finds what just became affordable; a heap picks the most profitable.
Open the problem →Write a solution and run it against the real test table.
Keep going
The k-th largest value
Keep only the k largest values seen so far. The smallest of them, on top of a min-heap, is the answer.
Open the problem → Hard25 minMost capital from at most k projects
Projects unlock as your capital grows. A sort finds what just became affordable; a heap picks the most profitable.
Open the problem → Heap (priority queue)The pattern behind it
Keep the few elements that matter in a structure that hands you the smallest (or largest) in O(1) and updates in O(log n).
Read the pattern →Preparing for a real process? Quant interview preparation, or book a free 20-minute call.