好的,这个问题实际上是围绕线性序列展开的。考虑最小值为 1 的序列:
f(n) = 1 + 2 + ... + n - 1 + n
这样一个序列的总和等于:
f(n) = n * (n + 1) / 2
因此,对于 n = 4,例如,总和为 10。这意味着如果您选择 4 个不同的数字,则没有零且没有负数的最小总数为 10。现在反过来:如果您总共有 10 和4 个数字,那么只有 (1,2,3,4) 的一种组合。
所以首先你需要检查你的总数是否至少与这个下限一样高。如果它更少,则没有组合。如果相等,则恰好只有一种组合。如果它更高,它会变得更复杂。
现在想象一下,您的约束共有 12 个,有 4 个数字。我们已经确定 f(4) = 10。但是如果第一个(最小的)数字是 2 呢?
2 + 3 + 4 + 5 = 14
所以第一个数字不能大于1。你知道你的第一个数字。现在您生成一个由 3 个数字组成的序列,总共 11 个(即 12 - 1)。
1 + 2 + 3 = 6
2 + 3 + 4 = 9
3 + 4 + 5 = 12
第二个数字必须是 2,因为它不能是 1。它不可能是 3,因为以 3 开头的三个数字的最小和是 12,我们必须加到 11。
现在我们找到两个数字加起来为 9 (12 - 1 - 2),其中 3 是可能的最小值。
3 + 4 = 7
4 + 5 = 9
第三个数字可以是 3 或 4。找到第三个数字后,最后一个数字是固定的。两种可能的组合是:
1, 2, 3, 6
1, 2, 4, 5
你可以把它变成一个通用算法。考虑这个递归实现:
$all = all_sequences(14, 4);
echo "\nAll sequences:\n\n";
foreach ($all as $arr) {
echo implode(', ', $arr) . "\n";
}
function all_sequences($total, $num, $start = 1) {
if ($num == 1) {
return array($total);
}
$max = lowest_maximum($start, $num);
$limit = (int)(($total - $max) / $num) + $start;
$ret = array();
if ($num == 2) {
for ($i = $start; $i <= $limit; $i++) {
$ret[] = array($i, $total - $i);
}
} else {
for ($i = $start; $i <= $limit; $i++) {
$sub = all_sequences($total - $i, $num - 1, $i + 1);
foreach ($sub as $arr) {
array_unshift($arr, $i);
$ret[] = $arr;
}
}
}
return $ret;
}
function lowest_maximum($start, $num) {
return sum_linear($num) + ($start - 1) * $num;
}
function sum_linear($num) {
return ($num + 1) * $num / 2;
}
输出:
All sequences:
1, 2, 3, 8
1, 2, 4, 7
1, 2, 5, 6
1, 3, 4, 6
2, 3, 4, 5
一种实现方式是获取所有序列并随机选择一个。这样做的好处是对所有可能的组合进行同等加权,这可能对您正在做的事情有用或不必要。
如果总数很大或元素数量很大,这将变得笨拙,在这种情况下,可以修改上述算法以返回从$start
到范围内的随机元素,$limit
而不是每个值。