这是另一种方法:
264 = 18446744073709551616
我们可以将数字表示为分量的总和(每个数字位置一个分量):
18446744073709551616 associated range of numbers
———————————————————— ———————————————————————————————————————————
0xxxxxxxxxxxxxxxxxxx => [00000000000000000000;09999999999999999999]
17xxxxxxxxxxxxxxxxxx => [10000000000000000000;17999999999999999999]
183xxxxxxxxxxxxxxxxx => [18000000000000000000;18399999999999999999]
1843xxxxxxxxxxxxxxxx => [18400000000000000000;18439999999999999999]
18445xxxxxxxxxxxxxxx => [18440000000000000000;18445999999999999999]
...
1844674407370955160x => [18446744073709551600;18446744073709551609]
18446744073709551616 => [18446744073709551610;18446744073709551616]
如果我们可以计算每个组件的幸运数字的数量,那么每个组件的数量之和将是 264 的总数。
请注意,每个组件都包含一个前缀,后跟xs。
想象一下,我们知道一个 n 位数 xx..x 中有多少个幸运数字(即数字 [0..0 - 9..9]),我们称之为 N(n)。
现在让我们看一个组件18445x..x。其中18445 是一个前缀和一个n 位数xx..x。
在这个组件中,我们查看从 18440xx..x 到 18445xx..x 的所有数字。
对于每个项目1844dxx..x,我们查看前缀1844d:
- 如果前缀不包含
6或8,则与不带前缀的x..x相同=> N(n)特殊数字
- 如果前缀包含
6而没有8,那么x..x不能包含8 => 9ⁿ特殊数字
- 如果前缀包含
8而没有6,那么x..x不能包含6 => 9ⁿ特殊数字
- 如果前缀包含
6 和8 => 0 特殊数字
现在让我们计算 N(n) — n 位 xx..x(即 [0..0 - 9..9])中幸运数字的数量。
我们可以迭代地做:
-
n=1:只有 2 个可能的数字:8 和 6 => N(1)=2。
-
n=2:有2组:
-
8 存在:8x 和 x8 其中x 是除6 之外的任何数字
-
6 存在:6x 和 x6 其中x 是除8 之外的任何数字
=> N(2)=4*9=34.
-
n=3: 让我们修复第一个数字:
-
0xx — 5xx, 7xx, 9xx => 8 * N(2)
-
6xx: xx 是除 8 之外的任意 2 位数字 => 9²
-
8xx: xx 是除 6 之外的任意 2 位数字 => 9²
=> N(3) = 8*N(2) + 2*9².
-
n=k+1 => N(k+1) = 7*N(k) + 2*9ᵏ
这是一个实现(未 100% 测试):
public final class Numbers {
public long countLuckyNumbersBelow(BigInteger num) {
if (num.compareTo(BigInteger.ZERO) < 0) {
throw new IllegalArgumentException("num < 0: " + num);
}
var numberText = num.toString();
var result = 0L;
for (var digitPosition = 0; digitPosition < numberText.length(); digitPosition++) {
result += countLuckyNumbersForComponent(numberText, digitPosition);
}
return result;
}
private long countLuckyNumbersForComponent(String numberText, int digitPosition) {
var prefixEndIdx = numberText.length() - 1 - digitPosition;
var prefixHas6s = containsChar(numberText, '6', prefixEndIdx);
var prefixHas8s = containsChar(numberText, '8', prefixEndIdx);
if (prefixHas6s && prefixHas8s) {
return 0;
}
var result = 0L;
for (var c = numberText.charAt(prefixEndIdx) - 1; c >= '0'; c--) {
var compNo6s = (!prefixHas6s) && (c != '6');
var compNo8s = (!prefixHas8s) && (c != '8');
if (compNo6s && compNo8s) {
result += countLuckyNumbers(digitPosition);
} else if (compNo6s || compNo8s) {
result += power9(digitPosition);
}
}
return result;
}
private static boolean containsChar(String text, char c, int endIdx) {
var idx = text.indexOf(c);
return (idx > 0) && (idx < endIdx);
}
private long[] countLuckyNumbersCache = {0L, 0L};
/**
* Computes how many lucky numbers are in an n-digit `xx..x`
*/
private long countLuckyNumbers(int numDigits) {
if (countLuckyNumbersCache[0] == numDigits) {
return countLuckyNumbersCache[1];
}
long N;
if (numDigits <= 1) {
N = (numDigits == 1) ? 2 : 0;
} else {
var prevN = countLuckyNumbers(numDigits - 1);
N = (8 * prevN) + (2 * power9(numDigits-1));
}
countLuckyNumbersCache[0] = numDigits;
countLuckyNumbersCache[1] = N;
return N;
}
private long[] power9Cache = {0L, 1L};
/**
* Computes 9<sup>power</sup>
*/
private long power9(int power) {
if (power9Cache[0] == power) {
return power9Cache[1];
}
long res = 1;
var p = power;
if (power > power9Cache[0]) {
p -= power9Cache[0];
res = power9Cache[1];
}
for (; p > 0; p--) {
res *= 9;
}
power9Cache[0] = power;
power9Cache[1] = res;
return res;
}
}
顺便说一句,我花了半天时间,我不知道怎么可能在 30 分钟内完成。
我猜你的面试官希望你向他们展示你的思维过程。