例如,当输入为25时,输出必须为7(数字的第25位数12345678910111213141516171819202122232425)
如何在C中解决此问题?
答案 0 :(得分:4)
像往常一样,有一个简单的答案和一个复杂的答案。
Clifford在另一个答案中已经描述了简单的答案:将数字序列构造为字符串,然后从中挑选i
个字符。
复杂的答案更有趣。该字符串实际上是OEIS序列A007376。有已知的公式(通过相关序列A033307),但它们要么是为了产生整个序列,要么包含令人讨厌的东西,如the principal value of the Lambert W function。由于序列相当简单,我们可以构造自己的算法。
让我们看看序列本身:
前9位是数字本身:1,2,3,..,8,9
下一个2 * 9 * 10 = 180位来自数字10,11,..,98,99
下一个3 * 9 * 10 * 10 = 2700位来自数字100,101,..,998,999
下一个4 * 9 * 10 * 10 * 10 = 36000位来自数字1000,1001,...,9998,9999
让我们调用由具有相同位数的数字组成的序列部分, region 。第一个区域包含9个单个数字,接下来是数字10到99的180个数字,下一个2700从数字100到999,依此类推。
关键是为我们感兴趣的索引找到正确的区域。结果很简单:我们只是减去每个区域的位数,直到"剩余指数"在当前区域内!
我们说n
是正确区域中的位数,i
是"剩余索引",减去较小区域中的位数来自序列索引。
要获取目标数 - 序列索引从 - 中选取数字的整数值,我们需要将10 n-1 添加到(i-1)/n
。这是因为第一个(所以i = 1)两位数字是10而不是11.数字中的位置是(i-1)%n
(除法的余数(i-1)%n),0表示最左边。另一种计算位置的方法是使用(n-1)-(i-1)%n
,其中0表示最右边的数字(" 1")。
(像往常一样,如果先前将索引减一,并使用从零开始的计算,则数学会变得更简单。)
不需要接受我的话。请考虑以下实现:
#include <stdint.h>
const uint64_t pow_10[20] = {
UINT64_C(1),
UINT64_C(10),
UINT64_C(100),
UINT64_C(1000),
UINT64_C(10000),
UINT64_C(100000),
UINT64_C(1000000),
UINT64_C(10000000),
UINT64_C(100000000),
UINT64_C(1000000000),
UINT64_C(10000000000),
UINT64_C(100000000000),
UINT64_C(1000000000000),
UINT64_C(10000000000000),
UINT64_C(100000000000000),
UINT64_C(1000000000000000),
UINT64_C(10000000000000000),
UINT64_C(100000000000000000),
UINT64_C(1000000000000000000),
UINT64_C(10000000000000000000)
};
int A007376(uint64_t i)
{
uint64_t digits = 1U;
uint64_t value = 1U;
uint64_t limit = 9U;
unsigned int tens;
/* The sequence starts at index 1. Shift to zero. */
if (!i--)
return 0;
/* Find the number of digits in each number
in the region of index i. */
while (i/limit >= digits) {
const uint64_t old_limit = limit;
i -= digits * limit;
digits++;
value *= 10U;
limit *= 10U;
/* If limit overflows, we are in the correct, final region. */
if (limit <= old_limit)
break;
}
/* We know the desired digit is i'th digit
in a string formed by concatenating the
9*10^(digits-1) numbers starting at 10^(digits-1).
The value of this number is pow10(digits-1) + i/digits: */
value += i / digits;
#ifdef DEBUG_OUTPUT
fprintf(stderr, "Digit %d of %" PRIu64 " ", 1 + (int)(i % digits), value);
#endif
/* Move the desired digit into the ones position. */
value /= pow_10[(digits - 1) - (i % digits)];
#ifdef DEBUG_OUTPUT
fprintf(stderr, "= %d\n", (int)(value % 10U));
#endif
return value % 10U;
}
上述函数以对数时间复杂度(O(log N))提供所需的数字。一些有趣的结果:
i A007376(i)
1 1
9 9
10 1
11 0
12 1
13 1
186 9
187 8
188 9
189 9
190 1
191 0
192 0
2884 9
2885 9
2886 8
2890 1
2891 0
2892 0
2893 0
4294967284 4
4294967285 8
4294967286 9
4294967287 5
4294967288 6
4294967289 4
4294967290 2
4294967291 6
4294967292 6
4294967295 9
18446744073709551580 1
18446744073709551581 0
18446744073709551582 2
18446744073709551583 9
18446744073709551584 3
18446744073709551585 6
18446744073709551586 0
18446744073709551587 7
18446744073709551588 9
18446744073709551589 9
18446744073709551590 2
18446744073709551591 0
18446744073709551592 1
18446744073709551593 0
18446744073709551594 8
18446744073709551595 7
18446744073709551596 5
18446744073709551597 1
18446744073709551598 0
18446744073709551615 5
当然,简单的&#39;之间有许多可能的中间实现。 (作为字符串的数字序列,从中选择i
个字符)和&#39;复杂的&#39; (上面的函数),取决于您深入研究序列行为的深度。
我个人认为,对于这个特定的序列, region 概念或类似概念是一个分水岭。 (例如,您可以在缓冲区中打印64位目标值,然后通过从缓冲区中取出数字来返回数字。我仍然会将这样的实现放在复杂的&#39; ;类别。)
这种简单的方法在字符串操作中很有用,甚至可能在动态内存管理中,但组合中的实际解决方案和与序列相关的问题应该使用复杂的&#39;尽可能接近:简单方法只是does not scale。
答案 1 :(得分:3)
您将遇到的第一个问题是代表数字。即使是无符号的64位整数也只适用于19位十进制数 - 即log10(2 64 )。
所以在这种情况下你的&#34;号码&#34;必须输入并存储为字符串;在这种情况下,它只是索引字符串的情况:
int digit_value = number_string[n-1] - '0' ;