Heap · 18 of 18
Minimum sum of two numbers formed from digits of an array
Deal digit cards to two players, smallest first, so the two numbers they build add up to as little as possible.
- Simple way
- time O(n! * n^2)
room O(n) - Clever way
- time O(n log n)
room O(n)
The problem, simply
You get an array of digits, each from 0 to 9. Use every digit exactly once to build two numbers. Make the sum of the two numbers as small as possible, and return that sum.
For [6, 8, 4, 5, 2, 3] the best numbers are 246 and 358, and the answer is 604.
A story to remember it
Picture it
The digits in a min heap. The smallest digit, 2, is on top, so it is dealt first.
- first
- 0
- second
- 0
- looking now
Examples
A normal case
- In
[6, 8, 4, 5, 2, 3]- Out
604
Sorted: 2, 3, 4, 5, 6, 8. Deal them: 2, 4, 6 to the first number and 3, 5, 8 to the second. 246 + 358 = 604.
An odd count with a zero
- In
[5, 3, 0, 7, 4]- Out
82
Sorted: 0, 3, 4, 5, 7. The first number gets 0, 4, 7, which is 047 = 47. The second gets 3, 5 = 35. 47 + 35 = 82. A leading zero is free.
All the same
- In
[1, 1, 1, 1]- Out
22
11 + 11 = 22.
One digit
- In
[7]- Out
7
The first number is 7 and the second is empty, which counts as 0.
Way 1: the simple way
Try every order of the digits and every place to cut the order into two numbers. Keep the smallest sum.
- Make every order (permutation) of the digits.
- For each order, try every cut point. The left part is one number, the right part is the other.
- Keep the smallest sum seen.
class Solution {
private long best;
public long minSum(int[] digits) {
best = Long.MAX_VALUE;
if (digits.length == 0) return 0;
permute(digits, 0);
return best;
}
private void permute(int[] d, int start) {
if (start == d.length) {
for (int cut = 0; cut <= d.length; cut++) { // left part is d[0..cut-1]
best = Math.min(best, number(d, 0, cut) + number(d, cut, d.length));
}
return;
}
for (int i = start; i < d.length; i++) {
swap(d, start, i);
permute(d, start + 1);
swap(d, start, i);
}
}
private long number(int[] d, int from, int to) {
long x = 0;
for (int i = from; i < to; i++) x = x * 10 + d[i];
return x;
}
private void swap(int[] d, int i, int j) { int t = d[i]; d[i] = d[j]; d[j] = t; }
}How long it takes: n! orders, each with n + 1 cuts that cost O(n) to read, so O(n! * n^2). Already at 12 digits that is billions of steps. Extra room: the recursion, O(n).
Way 2: the clever way
Deal the cards smallest first, taking turns.
- Put all digits into a min heap.
- Poll digits one at a time. Odd turns go to the first number, even turns to the second:
num = num * 10 + digit. - Return the sum of the two numbers.
Why it works: a digit in the front spot of a 3 digit number is worth 100, the next spot 10, the last spot 1. Taking turns keeps the two numbers the same length, so both have a 100 spot, both have a 10 spot, and so on. The two smallest digits get the two 100 spots, the next two get the 10 spots, and the biggest get the 1 spots. Any swap that puts a bigger digit in a more valuable spot makes the sum bigger.
Deal [6, 8, 4, 5, 2, 3] from a min heap
01 / 09
All digits are in the min heap. 2 is on top. Both numbers start at 0.
- first
- 0
- second
- 0
- next goes to
- first
- looking now
- checking
import java.util.PriorityQueue;
class Solution {
public long minSum(int[] digits) {
PriorityQueue<Integer> cards = new PriorityQueue<>(); // min heap
for (int d : digits) cards.offer(d);
long first = 0, second = 0;
boolean toFirst = true;
while (!cards.isEmpty()) {
int d = cards.poll(); // smallest card left
if (toFirst) first = first * 10 + d;
else second = second * 10 + d;
toFirst = !toFirst; // take turns
}
return first + second;
}
}How long it takes: n offers and n polls, O(n log n). Extra room: the heap, O(n). Sorting the array and dealing from left to right is the same idea with the same time.
Another way: count the digits, add as strings
Digits only go from 0 to 9, so you can count them instead of sorting: walk the counts from 0 to 9 and deal. With many digits the numbers do not fit in a long (18 digits are always safe), so build them as strings and add them digit by digit from the right, like on paper.
class Solution {
public String minSum(int[] digits) {
int[] count = new int[10];
for (int d : digits) count[d]++;
StringBuilder a = new StringBuilder(), b = new StringBuilder();
boolean toA = true;
for (int d = 0; d <= 9; d++) {
for (int c = 0; c < count[d]; c++) {
(toA ? a : b).append(d); // deal smallest first
toA = !toA;
}
}
StringBuilder sum = new StringBuilder();
int i = a.length() - 1, j = b.length() - 1, carry = 0;
while (i >= 0 || j >= 0 || carry > 0) { // paper addition from the right
int s = carry;
if (i >= 0) s += a.charAt(i--) - '0';
if (j >= 0) s += b.charAt(j--) - '0';
sum.append(s % 10);
carry = s / 10;
}
while (sum.length() > 1 && sum.charAt(sum.length() - 1) == '0') {
sum.setLength(sum.length() - 1); // drop leading zeros (they are at the end before reversing)
}
return sum.length() == 0 ? "0" : sum.reverse().toString();
}
}This is O(n) time, and it is the version to give when the input can have thousands of digits.
Watch out for
- Do not split the sorted digits into a left half and a right half.
[2, 3, 4, 5]split that way gives 23 + 45 = 68, but taking turns gives 24 + 35 = 59. - Zeros are fine at the front of a number in math, but if you must print the numbers themselves, strip the leading zeros.
- An empty input should give 0, not crash.
What interviewers ask next
- Why taking turns and not two halves? A digit's value depends on its spot. Taking turns gives both numbers the same spots, so the smallest digits fill the two most valuable spots first.
- Can you do it in
O(n)? Yes, count the ten digits instead of sorting, as in the last code block. - What if you must build three numbers? Deal to three players in turn. The same argument works for any number of players.
- Return the two numbers, not only the sum? Return the two strings you built, and strip leading zeros before printing them.
Remember this
Grown-up words: the card pile is a min heap (priority queue), dealing smallest first is a greedy choice, the value of a spot is its place value, counting the ten digits is counting sort, and adding from the right with a carry is big number addition.