编辑:根据 user3386109 的评论,阐明可翻转与可旋转(“倒置”或旋转 180 度)。
真正的问题是测试上限的剪切大小,约为 1.2 x 10^16。如果迭代每秒可以处理 10 亿个数字,那么该算法原样将需要约 12,000,000 秒的处理时间,即约 138 天。因此,为什么算法会超时...
如果将数字范围限制为可旋转的 5 位数字的排列,则排列数为 2 x 5^16 或 305,175,781,250。 (2 代表第一个数字,可以是 0 或 1,5^16 代表其余 16 个数字中的每一个的 5 种可能性(即 0、1、6、8、9)。)这更易于管理在合理的时间内计算...
因此,如果遵循遍历数字范围的路径,则只生成给定可旋转数字(即 0,1,6,8,9)的可能性,而不是遍历整个数字范围...
不过,更好的是,如果您只是在寻找完全可旋转的数字的计数,那么当您通过一些逻辑和数学运算就可以计算出刚才举例说明的可能性时,几乎不需要实际迭代该范围。更具体地说,假设您希望计算可旋转的 5 位数字的数量,即在 10000 - 99999 范围内。
- 首先,可旋转数字必须分成“自旋转”和“镜像对”。即,0,1,8 自旋,6/9 形成镜像对。
- 然后,简单地计算五个数字中每个数字的可能性:
-
数字 4:可以是 1、6、8、9(4 种可能性)
-
数字 3:可以是 0、1、6、8、9(5 种可能性)
-
数字 2:只能是 0、1、8(3 种可能)这是 5 位数字的中心数字,因此不能是 6 或 9,因为中心数字必须是自旋的。
-
第 1 位:必须与第 3 位相同。(1 种可能性)
-
第 0 位:必须与第 4 位相同。(1 种可能性)
-
所以,可旋转的 5 位数字的数量是......
- 4 * 5 * 3 * 1 * 1 - 或 - 60 个可旋转数字。
以上只是基于所涉及的位数的算法。如果在问题中范围涉及一个 17 位数字,那么必须遍历数字范围,并在此过程中求和。即,
-
1 位 (0 - 9):3 个可旋转数字
-
2 位 (10 - 99):4 * 1 -或- 4 个可旋转数字
-
3 位 (100 - 999):4 * 3 * 1 - 或 - 12 个可旋转数字
-
4 位 (1000 - 9999):4 * 5 * 1 * 1 - 或 - 20 个可旋转数字
o
o
o
-
15 位数(100000000000000 - 999999999999999):4 * 5 * 5 * 5 * 5 * 5 * 5 * 3 * 1 * 1 * 1 * 1 * 1 * 1 * 1 -或- 187,500 个可旋转数字-
-
16 位(1000000000000000 - 9999999999999999):4 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 1 * 1 * 1 * 1 * 1 * 1 * 1 * 1 - 或 - 312,500 个可旋转数字
我会在这里停下来,因为现在在处理最后一个范围时需要进一步考虑,因为通常数字的全宽(在问题的情况下为 17 位)不会运行整个范围17 位数。即,范围是从 10000000000000000 - 12345678900000000 而不是 99999999999999999。在这种情况下,仍然可以使用所提出的算法,但现在必须考虑到 17 位数字中每个数字施加的限制...
编辑 2:在之前提出的策略之后,下面的代码 sn-p 结合了计算每个数字范围的微调器的逻辑,直到数字的全宽和数字全宽的微调器。也就是说,这必然是一个两阶段算法,因为在处理数字的全宽度时,第二阶段必须考虑到数字的尾随数字可能不一定在开头的可能可旋转数字范围内号码。因此,第二阶段必须在每个增加分辨率的范围内计算微调器...
代码 sn-p 的控制台输出可能比试图解释第二阶段背后概念的一段文字更能说明问题,尤其是数字 189942 的示例。
function calcSpinners( x ) {
const selfies = [ 0, 1, 8 ];
const mirrors = [ [ 6, 9 ] ];
// Stage 1. Walk from 1 digit up to n - 1 digits, calculating the number
// of possible spinners at each step and adding to the total
let n = x.toString().length;
let sum1 = 0;
for ( let i = 1; i < n; i++ ) {
let count = 1;
// If an even number of digits, then there is no center digit. If an odd
// number of digits, then let's factor in the number of possible selfies.
count = count * ( i % 2 === 0 ? 1 : selfies.length );
let halfDigits = i / 2 |0;
if ( 0 < halfDigits ) {
count = count * ( selfies.filter( sx => sx !== 0 ).length + mirrors.length * 2 );
count = count * ( selfies.length + mirrors.length * 2 ) ** ( halfDigits - 1 );
}
console.log( `Range ${10n ** BigInt( i - 1)} - ${10n ** BigInt( i ) - 1n} has ${count} spinners` );
sum1 += count;
}
console.log( `Subtotal of spinners from 1 to ${n-1} digits: ${sum1}` );
// Stage 2: Now we're just dealing with the full width of x. Eg, if x is 17
// digits, then the evaluation of the spinnable numbers is evaluated at each
// range of increasing resolution of x, always 17 digits.
let sum2 = 0;
n = BigInt( x.toString().length );
let loopTill = n / 2n + ( n % 2n );
for ( let i = 0n; i <= loopTill; i++ ) {
let dhi = 10n ** ( n - i - 1n );
let dlo = dhi * 10n
let hi = ( i === loopTill ? x : ( x / dhi ) * dhi - 1n );
let lo = ( i === 0n ? dhi : ( x / dlo ) * dlo );
count = 0;
if ( lo < hi ) {
let hiNum = hi.toString().split( '' ).map( x => parseInt( x ) );
let loNum = lo.toString().split( '' ).map( x => parseInt( x ) );
// If an even number of digits, then there is no center digit. If an odd
// number of digits, then let's factor in the number of possible selfies.
let center = hiNum.length / 2 |0;
count = ( hiNum.length % 2 === 0 ? 1 : selfies.filter( sx => loNum[ center ] <= sx && sx <= hiNum[ center ] ).length );
for ( let j = 0; j < center; j++ ) {
hiNumHiDigit = hiNum[ j ];
hiNumLoDigit = hiNum[ hiNum.length - 1 - j ];
loNumHiDigit = loNum[ j ];
loNumLoDigit = loNum[ loNum.length - 1 - j ];
// For the digit in question, count the number of possible selfie and
// mirror digits.
let eligible =
selfies.filter( sx =>
loNumHiDigit <= sx && sx <= hiNumHiDigit
&& loNumLoDigit <= sx && sx <= hiNumLoDigit
&& !( sx === 0 && j === 0 )
).length
+ mirrors.filter( pair =>
( loNumHiDigit <= pair[ 0 ] && pair[ 0 ] <= hiNumHiDigit
&& loNumLoDigit <= pair[ 1 ] && pair[ 1 ] <= hiNumLoDigit )
).length
+ mirrors.filter( pair =>
( loNumHiDigit <= pair[ 1 ] && pair[ 1 ] <= hiNumHiDigit
&& loNumLoDigit <= pair[ 0 ] && pair[ 0 ] <= hiNumLoDigit )
).length;
count = count * eligible;
}
console.log( `Range ${lo} - ${hi} has ${count} spinners.` );
sum2 += count;
}
}
console.log( `Subtotal of spinners of ${n} digits up to and including ${x}: ${sum2}` );
console.log( `Total spinners: ${sum1 + sum2}` );
return sum1 + sum2;
}
let loVal = 100000n;
let hiVal = 12345678900000000n;
console.log( `Calculating spinner count for ${loVal}.` );
let loTotal = calcSpinners( loVal );
console.log( `${loVal} has ${loTotal} spinners.` );
console.log( `` );
console.log( `Calculating spinner count for ${hiVal}.` );
let hiTotal = calcSpinners( hiVal );
console.log( `${hiVal} has ${hiTotal} spinners.` );
console.log( `` );
console.log( `Between ${loVal} - ${hiVal} there are ${hiTotal - loTotal} spinners.` );
console.log( `` );
console.log( `` );
let testVal = 189942n;
console.log( `Calculating spinner count for ${testVal}.` );
let testTotal = calcSpinners( testVal );
console.log( `${testVal} has ${testTotal} spinners.` );
console.log( `` );
底线是,虽然这个算法更复杂,但它在最短的时间内解决了问题,因为它没有尝试迭代输入数字的整个数字范围(或可旋转数字的组合)......
这个问题是一个很好的例子,说明必须重构的不是代码,而是底层算法,因为蛮力方法无法在经典计算机上扩展......