【问题标题】:Find all possible combinations of numbers [closed]找到所有可能的数字组合[关闭]
【发布时间】:2021-04-10 13:39:13
【问题描述】:

背景(TL;DR – 原帖)

我目前在 C 语言中遇到了这个问题,我想知道是否有人知道该怎么做。我需要打印所有可能的解决方案来解决像 BASE + BALL = GAMES 这样的密码问题。例如,这里的一种解决方案是:A=4 B=7 E=3 G=1 L=5 S=8 M=9 对应于 7483 +7455= 14938。所以我的任务是找到正确的数字到字符映射这是真的。我可以使用 0-9 的数字,每个数字只能分配给一个字母。解决方案需要是递归的。我的思考过程是从 3 个字符串中找到所有唯一字符并为其分配一个数字,然后检查上述关系的解决方案是否正确。万一这不是真的,我需要尝试不同的数字组合。我只是不知道我需要使用什么方法来涵盖所有可能的组合。我不一定想要代码,我只是想找人向我解释它的算法逻辑开始它或提供任何可能的资源。

编辑:我所说的密码算术问题是一个问题,用户输入 3 个字符串作为输入,这些字符串创建了一个像 SEND + MORE = MONEY 这样的等式。这个方程是通过为每个字母分配一个从 0 到 9 的唯一数字来解决的,就像我在上面的示例中显示的那样。

所以我们想要一个程序来做这样的事情:

输入:s1 = SEND, s2 = "MORE", s3 = "MONEY"
输出:一种可能的解决方案是:
D=1 E=5 M=0 N=3 O=8 R=2 S=7 Y=6

如果您尝试用分配的数字替换每个字符,您会发现创建的等式成立。我需要一个程序来做到这一点,这意味着找到数字到字符的正确映射,以便生成的等式是正确的。

实际问题

实际的问题只是寻找可能的数字与字母的分配,而不是解决一般的密码难题。它是:

如何从十个个位数非负数的集合中生成长度k的所有变体?

换句话说,找到长度为k(对于某些0

【问题讨论】:

  • 你需要我们告诉更多关于你的“密码问题”的细节。
  • @paladin 好的,抱歉,你能告诉我还有什么需要补充的吗?
  • 例如明确定义您的“密码学问题”。
  • @paladin 我试图提供更多上下文。希望现在更清楚,否则请给我更多反馈。
  • 你做了什么?你能分享一些关于算法、解决方案的阶段、数据结构的想法吗?你已经有一些代码?

标签: c combinations permutation


【解决方案1】:

基于your comment 我知道您现在对输入数据分析不感兴趣,即识别问题中有多少不同的字母(自变量),也不表示它们在您求解的方程中的位置,或者用于测试对这些变量分配一位数是否满足等式的方法。

您只需寻找一种方法来找到将一位数分配给这些变量的所有可能分配。

因此,允许使用多个数字 n=10 并使用多个不同的字母(解决方案中的变量)k 您需要找到

  1. 要分配的所有 C(n,k) 个数字组合,并且
  2. 对于每个组合,所有的 k!排列,即分配给字母/变量的不同顺序。

找到这些组合和排列的最有效方法可能是递归。但是,这会使您在递归底部调用下一个阶段,并且代码将难以理解。所以我更喜欢一种效率较低但更易读的迭代方法。

让我们声明一个要分配给变量的值数组。我们事先不知道需要多少个变量,但肯定不会超过 10 个(因为问题定义了一个约束,即为每个变量(字母)分配一个不同的数字,并且最多分配 10 个不同的数字)。所以:

int values[10];

现在,让我们创建第一个组合。它将由第一个可能的k 数字组成,即0k-1

void FirstCombination(int values[], int k)
{
    for (int i = 0; i < k; i++)
        values[i] = i;
}

我们必须从当前组合创建一个新组合。通常这可以通过简单地增加最后一个数字来完成。它将修改选择的序列,例如:

    2, 3, 7  →  2, 3, 8

但是,如果最后一个值已经是9,会发生什么?

    2, 3, 9  →  2, 3, ???

好吧,我们可以检查最后一个值是否可以递增(这意味着它小于8),如果可以递增,则将最后一个值设置为大一:

    2, 3, 9  →  2, 3  →  2, 4  →  2, 4, 5

这显然归结为第一个元素:

    2,7,8,9  →  2  →  3  →  3,4,5,6

如果成功,我们return 'true' 值 (1) 表示我们找到了一个新的有效组合,否则(不能增加任何值)我们返回 'false' (0):

int NextCombination(int values[], int n, int k)
{
    for (int i = k-1; i >= 0; i--)   // from the last number backwards
    {
        if (values[i] < n + i - k)   // can this be incremented?
        {
            values[i] ++;            // increment
            while (++i < k)          // reset following values
                values[i] = values[i - 1] + 1;

            return 1;
        }
    }
    return 0;
}

然后我们可以用一个简单的循环遍历所有组合:

int n = 10;
int values[10];

FirstCombination(values, k);
do {
    //
    // ... process the combination here ...
    //
} while (NextCombination(values, n, k));

这两个函数的一个方便的特点是每个传递的组合都以升序给出。

现在让我们看看如何构建给定数组的所有排列。以升序排列组合为我们以字典(“字母”)顺序生成它们提供了一个良好的开端,这可以理解为由生成的数字序列生成的“数字”本身就是升序。例如,

    2, 4, 5
    2, 5, 4
    4, 2, 5
    4, 5, 2
    5, 2, 4
    5, 4, 2

为了实现这一点,让我们找到序列的递减尾部。如果它是整个序列,我们有最后一个排列(按字典顺序),所以我们完成了。否则,我们取尾部前面的数字并将其与尾部的下一个更大的项目交换。然后我们反转尾巴。以下是一些示例:

sequence    tail        next grtr   exchange    reverse     result
1 3 5 6 7   1 3 5 6[7]  1 3 5 6<7>  1 3 5 7[6]  1 3 5 7[6]  1 3 5 7 6
1 5 7 6 3   1 5[7 6 3]  1 5 7<6>3   1 6[7 5 3]  1 6[3 5 7]  1 6 3 5 7
7 6 5 3 1  [7 6 5 3 1]    none        none     [1 3 5 6 7]  1 3 5 6 7  DONE

这样我们最终恢复了第一个排列,这是继续搜索下一个组合的方便点。

int NextPermutation(int values[], int k)
{
    // find the tail beginning
    int tail = k-1;
    while (tail > 0 && values[tail-1] > values[tail])
        tail --;

    // not the last permutation?
    if (tail > 0)
    {
        int nextGreater = k - 1;
        while (values[nextGreater] < values[tail - 1])
            nextGreater --;

        // found; now swap
        int tmp = values[tail - 1];
        values[tail - 1] = values[nextGreater];
        values[nextGreater] = tmp;
    }

    // reverse the tail
    int left = tail, right = k - 1;
    while (left < right)
    {
        int tmp = values[left];
        values[left] = values[right];
        values[right] = tmp;
        left ++; right --;
    }

    return tail > 0;  // return 'true' if it wasn't the last permutation
}

现在我们可以使用两个嵌套循环遍历所有组合及其排列:

int n = 10;
int values[10];

FirstCombination(values, k);
do {
    do {
        //
        // ... process the permutation here ...
        //
    } while (NextPermutation(values, k));
} while (NextCombination(values, n, k));

或者,由于布尔运算符求值的短路方法,使用带有复合条件的单个循环:

int n = 10;
int values[10];

FirstCombination(values, k);
do {
    //
    // ... process the permutation here ...
    //
} while (NextPermutation(values, k) || NextCombination(values, n, k));

在 godbolt.org 上查看它:https://godbolt.org/z/YKe8s69qT

在循环中,您需要通过将values 应用于相应的字母来评估三个输入表达式,然后验证等式是否成立。在评估输入密码时,请注意可以多次应用相同的值,例如两个B、两个A 和两个L 中的BASEBALL

还有一个你没有提到的额外困难:密码谜题通常假设不能将零指定为前导数字。例如,15+8=23AB+C=DE 的有效解决方案,但07+5=12 不是。因此,您应该注意哪些字母被用作三个单词中的首字母,而不是处理将零分配给其中任何一个的排列。

【讨论】:

  • 这正是我想要的。非常感谢您付出的努力!
【解决方案2】:

您要求的更多是数学问题而不是程序员问题。

这可能是您正在搜索的公式。

  (N_a*10^n + ... + B_a*10^1 + A_a*10^0)
+ (N_b*10^n + ... + B_b*10^1 + A_b*10^0)
----------------------------------------
= (N_c*10^n + ... + B_c*10^1 + A_c*10^0)

因此,在您的“BASE + BALL = GAMES”示例中,公式如下:

           (B*10^3 + A*10^2 + S*10^1 + E*10^0)
+          (B*10^3 + A*10^2 + L*10^1 + L*10^0)
----------------------------------------------
= (G*10^4 + A*10^3 + M*10^2 + E*10^1 + S*10^0)

这是一个有 7 个未知数的线性方程。

以下术语也可以写成:

              (B*10^3 + A*10^2 + S*10^1 + E*10^0)
+             (B*10^3 + A*10^2 + L*10^1 + L*10^0)
-------------------------------------------------
= (2*B*10^3 + 2*A*10^2 + (S+L)*10^1 + (E+L)*10^0)

导致:

2*B*10^3 + 2*A*10^2 + (S+L)*10^1 + (E+L)*10^0 = G*10^4 + A*10^3 + M*10^2 + E*10^1 + S*10^0

所有可能术语的最简单解决方案是:

A=0, B=0, E=0, G=0, L=0, S=0, M=0

但我想,这不是你要找的。​​p>

另一个非常重要的事实是,所有未知数必须是从 0 到 9 的自然数,[0, 9]。但就像我展示的那样,使用 0 时需要仔细查看,因为这可能会产生不想要的结果。

所以也许只查找从 1 到 9 的所有自然数,[1, 9]。

PS 了解数字运算算法

PPS 是一个非常愚蠢的算法:

int number_crunching = 1;
int A = B = E = G = L = S = M = 1;
while(number_crunching == 1){
    if(B*2000 + A*200 + (S+L)*10 + E + L == G*10000 + A*1000 + M*100 + E*10 + S)
    {
        number_crunching = 0; //found a solution
        break;
    }
    if(++A == 10){
        A = 1;
        if(++B == 10){
            B = 1;
            if(++E == 10){
                E = 1;
                if(++G == 10){
                    G = 1;
                    if(++L == 10){
                        L = 1;
                        if(++S == 10){
                            S = 1;
                            if(++M == 10){
                                number_crunching = -1; //found no solution
                                break;
                            }
                        }
                    }
                }
            }
        }
    }   
}

PPPS 我没有测试过循环,但你现在应该知道如何尝试解决你的问题了

【讨论】:

  • PS 我监督要搜索的数字必须是唯一的,所以我的示例程序并不完全正确,但我想你知道如何以你需要的方式改变它;这也消除了 0 的问题
  • 据我第一眼所见,您的算法将测试 许多 个相等值的组合,例如 A, B, C 全部相等 3,其中一些可能给出正确的解决方案(满足给定的方程)但无效(分配给字母的值必须是唯一的)。
  • 糟糕,忽略了您的评论。 :)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-10-06
  • 2020-07-28
  • 2012-04-25
  • 1970-01-01
相关资源
最近更新 更多