基于your comment 我知道您现在对输入数据分析不感兴趣,即识别问题中有多少不同的字母(自变量),也不表示它们在您求解的方程中的位置,或者用于测试对这些变量分配一位数是否满足等式的方法。
您只需寻找一种方法来找到将一位数分配给这些变量的所有可能分配。
因此,允许使用多个数字 n=10 并使用多个不同的字母(解决方案中的变量)k 您需要找到
- 要分配的所有 C(n,k) 个数字组合,并且
- 对于每个组合,所有的 k!排列,即分配给字母/变量的不同顺序。
找到这些组合和排列的最有效方法可能是递归。但是,这会使您在递归底部调用下一个阶段,并且代码将难以理解。所以我更喜欢一种效率较低但更易读的迭代方法。
让我们声明一个要分配给变量的值数组。我们事先不知道需要多少个变量,但肯定不会超过 10 个(因为问题定义了一个约束,即为每个变量(字母)分配一个不同的数字,并且最多分配 10 个不同的数字)。所以:
int values[10];
现在,让我们创建第一个组合。它将由第一个可能的k 数字组成,即0 到k-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=23 是AB+C=DE 的有效解决方案,但07+5=12 不是。因此,您应该注意哪些字母被用作三个单词中的首字母,而不是处理将零分配给其中任何一个的排列。