如何找到1到n的第n位?

时间:2016-10-27 15:37:21

标签: c

例如,当输入为25时,输出必须为7(数字的第25位数12345678910111213141516171819202122232425)

如何在C中解决此问题?

2 个答案:

答案 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' ;