如何生成最多18位的数字,其倒数的总和是一个整数

时间:2017-09-12 17:18:08

标签: c algorithm

我正在寻找一种方法来生成一系列数字,其倒数之和是一个整数?

例如 - 11(1/1 + 1/1 = 2是整数),122(1/1 + 1/2 + 1/2是整数),236(1/2 + 1 / 3 + 1/6是整数) 同样。

另外,我想避免重复相同的数字组合或排列。例如,如果打印122,我不想打印212和221。

我想知道如何解决这个问题

2 个答案:

答案 0 :(得分:3)

让你开始的一些想法:

  • 您的号码不能包含零。
  • 您可以随时将其添加到有效数字中,但仍然有效。
  • 您不需要生成数字。生成数字列表。如果按升序保留列表,您将自动解决如何消除排列的问题。
  • 每个真实分数1/2至1/9可以用公分母2520表示为扩展分数。

因此,以下方法可能有效:

  • 以空数组开头,总和为0.
  • 现在递归调用您的生成函数。在每次递归中,检查总和是否可被2520整除而没有余数。如果是这样,请打印该号码,并打印前面填充的所有数字,直到您有18位数字。因此对于236打印236,1236,11236等等,直到111 111 111 111 111 236。
  • 如果您的阵列长度为18,请不要进一步递增。否则,在将每个数字2到9添加到列表后调用您的函数,但不要使用小于列表中最后一位数字的数字来保持列表排序。
  • 不要重新计算总和,而是保持一个运行总和:当你递归时,将相应的分子加到总和中:1260为2,840为3,630为4等等。

这种自下而上的方法比检查所有可能的数字快得多。以下程序将在几分之一秒内打印所有14,137个有效数字(无序,即按生成顺序):

#include <stdlib.h>
#include <stdio.h>

#define NDIGIT 18

void rec(int set[], int n, int sum)
{
    static int num[10] = {
        0, 2520, 1260, 840, 630, 504, 420, 360, 315, 280
    };

    if (sum % 2520 == 0) {
        for (int j = 0; j < NDIGIT - n + 1; j++) {
            for (int i = 0; i < j; i++) putchar('1');
            for (int i = 0; i < n; i++) putchar(set[i] + '0');
            putchar('\n');
        }
    }

    if (n < NDIGIT) {
        int i0 = n ? set[n - 1] : 2;

        for (int i = i0; i < 10; i++) {
            set[n] = i;
            rec(set, n + 1, sum + num[i]);
        }
    }
}

int main()
{
    int set[NDIGIT];

    rec(set, 0, 0);

    return 0;
}

如果您需要排序输出,请将set转换为长整数,可能是uint64_t <stdint.h>并将其存储在数组中,而不是立即打印它们。然后对数组进行排序并打印数字。

答案 1 :(得分:1)

首先,你有九个可能的数字(因为0没有导致有效分数),其倒数有一个公约数2×2×2×3×3×5×7 = 2520:

1 -> 1/1 = 2520/2520
2 -> 1/2 = 1260/2520
3 -> 1/3 =  840/2520
4 -> 1/4 =  630/2520
5 -> 1/5 =  504/2520
6 -> 1/6 =  420/2520
7 -> 1/7 =  360/2520
8 -> 1/8 =  315/2520
9 -> 1/9 =  280/2520

因此,我们可以使用x/2520来描述此类总和的确切值。当且仅当x2520的倍数时,总和才是整数。我们可以使用modulo operator:if x % 2520 == 0,分数x / 2520表示整数。

在实践中,我们根本不需要表示分数的值,我们只需要记住分子模2520,x % 2520

让我们看看以伪代码检查每个数字的倒数之和是否为整数的算法:

Constant Array  per_digit_2520[10] = {
    0,    /* 0 -> 0/1 =    0/2520 */
    2520, /* 1 -> 1/1 = 2520/2520 */
    1260, /* 2 -> 1/2 = 1260/2520 */
    840,  /* 3 -> 1/3 =  840/2520 */
    630,  /* 4 -> 1/4 =  630/2520 */
    504,  /* 5 -> 1/5 =  504/2520 */
    420,  /* 6 -> 1/6 =  420/2520 */
    360,  /* 7 -> 1/7 =  360/2520 */
    315,  /* 8 -> 1/8 =  315/2520 */
    280,  /* 9 -> 1/9 =  280/2520 */
}

Function corresponds_to_whole_fraction(digits):
    Let remain = 0

    For each digit d in digits:
        remain = (remain + per_digit_2520[d]) % 2520 
    End For

    If remain == 0:
        Return "Yes, the digits represent a whole fraction"
    Else:
        Return "No, the digits do not represent a whole fraction"
    Fi

End Function

在上面的循环中,您还可以检查数字d是否为零,如果是,则拒绝该组数字。

通过编写上述内容,我们可以立即注意到,因为总和中分数的顺序不会改变总和,所以数字的所有组合都会产生完全相同的倒数位数之和。

换句话说,如果236产生一个总和(确实如此,因为1/2 + 1/3 + 1/6 =(1260 + 840 + 420)/ 2520 = 2520/2520 = 1),那么也是326 ,362,623和632产生相同的总和。每个数字都是数字集的唯一permutation。为了进一步探索排列,我们需要查看combinatorics

幸运的是,corresponds_to_whole_fraction()很简单,通过顺序生成普通数字或使用随机数生成器生成所需数字是合理的(对于最多18位的数字,我建议{{3}自2 64 &gt; 10 18 )。

让我们考虑顺序情况:

#include <stdlib.h>
#include <inttypes.h>
#include <stdio.h>

const unsigned int num_2520[10] = {
    0, 2520, 1260, 840, 630, 504, 420, 360, 315, 280
};

int is_acceptable(uint64_t value)
{
    unsigned int  remain = 0;

    if (!value)
        return 0;

    while (value > 0) {
       const unsigned int  digit = value % 10;

       if (!digit)
           return 0;

       value /= 10;
       remain = (remain + num_2520[digit]) % 2520;
    }

    return (remain == 0);
}

uint64_t next_number(uint64_t previous)
{
    while (++previous)
        if (is_acceptable(previous))
            return previous;

    return 0;
}

int main(void)
{
    uint64_t  value = 0;

    while (1) {
        value = next_number(value);
        if (!value)
            break;

        printf("%" PRIu64 "\n", value);
    }

    /* Never reached */
    return EXIT_SUCCESS;
}

上述程序打印1和2 64 -1 = 18446744073709551615之间的所有数字,包括它们,其倒数的总和是一个整数。它们太多了,任何人都不能等待足够长的时间来打印它们。

密度约为10%左右的0.05%-0.1%,即平均而言,每千分之一(或千分之二千)数是可接受的(尽管有很多变化)。所以,如果你从一个随机数开始,你可能希望检查从该随机数开始的连续数字,而不是每次都尝试所有新的随机数(因为随机数字生成器起初不是很快)。

如果您想将上述内容限制为18位数,请从uint64_t value = UINT64_C(99999999999999999);(17个9)开始,并将if (!value)替换为if (value > UINT64_C(999999999999999999))(18个9)。