Array · 33 of 36
Find factorial of a large number
Multiply 1 * 2 * 3 * ... * n when the answer is far too long for any number type, by keeping one digit per box like on school paper.
- Simple way
- time O(n)
room O(1) - Clever way
- time O(n * d)
room O(d)
The problem, simply
The factorial of n, written n!, is 1 * 2 * 3 * ... * n. Give back n! as a string of digits.
The catch: it grows very fast. 20! already has 19 digits, and 100! has 158. No built in number type can hold that, so you must do the multiplying yourself. Below, d stands for the number of digits in the answer.
A story to remember it
Picture it
120 stored one digit per box, ones digit first: box 0 holds 0, box 1 holds 2, box 2 holds 1. New digits are added at the right end, which is cheap.
- number
- 120
- just added
Examples
A small one
- In
n = 5- Out
120
1 * 2 * 3 * 4 * 5 = 120.
The last one that fits in a long
- In
n = 20- Out
2432902008176640000
20! is a little under the long limit of about 9.2 * 10^18.
One step too far
- In
n = 21- Out
51090942171709440000
A long would wrap around and give the nonsense value -4249290049419214848. The digit boxes give the right answer.
Zero
- In
n = 0- Out
1
By definition 0! = 1: multiplying no numbers at all gives 1.
A big one
- In
n = 100- Out
9332621544...0000 (158 digits)
It ends in 24 zeros, one for every pair of factors 2 and 5.
Way 1: the simple way
The first thing anyone tries: keep a long and multiply.
class Solution {
public String factorial(int n) {
long result = 1;
for (int x = 2; x <= n; x++) {
result *= x; // silently wraps around after 20!
}
return String.valueOf(result);
}
}How long it takes: n multiplications, O(n). Extra room: one number, O(1). But it is only correct up to n = 20. After that the number wraps around and the answer is garbage, with no error. That is why this problem exists: we need a number that can grow as long as it wants.
Way 2: the clever way
Keep the answer as a list of digits, ones digit first. Multiply the whole list by x = 2, 3, ..., n, the school way.
- Start with the list
[1]. - For each
x, walk the boxes from the ones digit up. For each box:product = box * x + carry. Putproduct % 10back in the box and carryproduct / 10. - After the last box, while the carry is not 0, add
carry % 10as a new box and divide the carry by 10. - At the end, read the boxes from last to first.
Storing the ones digit first means a new digit goes at the end of the list, which is cheap.
Multiply digit by digit, carrying like at school
01 / 13
Store the number one digit per box, ones digit first. After multiplying 1 by 2, 3 and 4 we hold 24, stored as [4, 2].
- number
- 24
- looking now
- done
- answer
- just added
class Solution {
public String factorial(int n) {
List<Integer> digits = new ArrayList<>();
digits.add(1); // start with 1
for (int x = 2; x <= n; x++) {
int carry = 0;
for (int i = 0; i < digits.size(); i++) {
int product = digits.get(i) * x + carry; // one box times x, plus carry
digits.set(i, product % 10); // keep the last digit
carry = product / 10; // pass the rest on
}
while (carry > 0) { // leftover carry: new boxes
digits.add(carry % 10);
carry /= 10;
}
}
StringBuilder sb = new StringBuilder();
for (int i = digits.size() - 1; i >= 0; i--) sb.append(digits.get(i));
return sb.toString();
}
}How long it takes: for each of the n multipliers we walk at most d boxes, so O(n * d). Since n! has about n * log10(n) digits, that is about O(n^2 log n). Extra room: the d boxes, O(d).
Another way: bigger boxes
Nothing says a box must hold one digit. Let each box hold 9 digits, a number from 0 to 999,999,999. Use long for the product so box * x + carry cannot overflow. There are 9 times fewer boxes, so it runs about 9 times faster, with the same O(n * d) shape. When printing, pad every box except the first with zeros to 9 digits.
class Solution {
public String factorial(int n) {
final int BASE = 1_000_000_000; // 9 digits per box
List<Integer> boxes = new ArrayList<>();
boxes.add(1);
for (int x = 2; x <= n; x++) {
long carry = 0;
for (int i = 0; i < boxes.size(); i++) {
long product = (long) boxes.get(i) * x + carry;
boxes.set(i, (int) (product % BASE));
carry = product / BASE;
}
while (carry > 0) {
boxes.add((int) (carry % BASE));
carry /= BASE;
}
}
StringBuilder sb = new StringBuilder();
sb.append(boxes.get(boxes.size() - 1)); // the top box has no padding
for (int i = boxes.size() - 2; i >= 0; i--) {
sb.append(String.format("%09d", boxes.get(i)));
}
return sb.toString();
}
}In real Java code you can also use BigInteger, but interviewers want to see that you can do the carrying yourself.
Watch out for
- The leftover carry can have more than one digit when
xis large (times 100 can leave a carry of 90 or more). Use awhileloop, not a singleif. - With one digit boxes,
box * x + carrystays below10 * x, sointis safe forxup to about 200 million. With 9 digit boxes you needlong. n = 0andn = 1must give"1". Starting from[1]handles both.
What interviewers ask next
- How many trailing zeros does
n!have? Count the factors of 5:n/5 + n/25 + n/125 + .... For 100 that is 20 + 4 = 24. - How many digits does
n!have, without computing it? Add uplog10(x)forx = 1..n, take the floor, and add 1. - Multiply two huge numbers given as strings? Same carrying idea with two nested loops: digit
itimes digitjlands in boxi + j. - Can factorials be computed faster? Multiply in a balanced tree (pairs, then pairs of pairs) so the big multiplications are between numbers of similar size, and use fast multiplication. That is how
BigIntegerlibraries do it. - What is
n! mod (10^9 + 7)? Then no big numbers are needed: take the remainder after every multiplication.
Remember this
Grown-up words: the boxes are an arbitrary precision (big integer) number in base 10, carrying is schoolbook multiplication, the 9 digit boxes are base 10^9 limbs, and the wrap around in Way 1 is integer overflow.