如何找到数字1到n的第n个数字?

Mah*_*deh -4 c

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

如何在C中解决此问题?

Nom*_*mal 5

像往常一样,有一个简单的答案和一个复杂的答案。

Clifford在另一个答案中已经描述了简单的答案:将数字序列构造为字符串,然后i从中选择第'个字符。

复杂的答案要有趣得多。该字符串实际上是OEIS序列A007376。有已知的公式(通过相关的序列A033307),但它们要么适合于生成整个序列,要么包含令人讨厌的内容,例如Lambert W函数的本金。由于序列非常简单,因此我们可以构建自己的算法。

让我们看一下序列本身:

  • 前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表示最右边的数字(“ ones”)。

(与往常一样,如果将索引较早地减一,并使用基于零的计算,则数学会变得简单一些。)

无需信守我的诺言。考虑以下实现:

#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;
}
Run Code Online (Sandbox Code Playgroud)

上面的函数提供了对数时间复杂度(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
Run Code Online (Sandbox Code Playgroud)

当然,“简单”(数字顺序为字符串,i从中选择第一个字符)与“复杂”(上面的函数)之间有许多可能的中间实现方式,具体取决于对行为的深入了解。您研究的顺序。

就个人而言,我想说对于这个特定的序列,区域概念或类似概念是一个分水岭。(例如,您可以只在缓冲区中打印64位目标值,然后通过从缓冲区中挑选出来返回数字。我仍将这种实现方式归类为“复杂”类别。)

简单方法可用作字符串操作(甚至甚至在动态内存管理中)的练习,但组合方法和与序列相关的问题的实际解决方案应尽可能使用“复杂”方法:简单方法无法扩展