如何生成指定和与固定长度的正整数拆分组合?
问题说明
你要解决的这个数学问题叫固定项数的正整数分拆(也叫「有序k分拆」,k就是你说的组合长度),说白了就是找所有长度为指定值的正整数序列,加起来等于目标和。
现有代码的问题
你给的代码只写了开头的初始化逻辑,既没实现生成所有组合的核心逻辑,也没用到生成器的yield输出结果,等于没完成核心功能。
完善后的代码方案
下面给你两种实现:一种是完全贴合你的需求(只用正整数,不用自定义可选数)的简化版,另一种是保留原函数参数、支持自定义可选数字的版本。
简化版(完全贴合你的需求)
这个版本不用传可选数字集合,直接生成所有符合要求的正整数组合:
function getFixedLengthIntegerPartitions(int $targetSum, int $setLength): \Generator { // 边界检查:每个数至少是1,目标和必须不小于组合长度,否则无解 if ($targetSum < $setLength) { return; } // 递归生成组合的内部函数 $generate = function(int $remainingSum, int $remainingLength, array $currentCombination) use (&$generate) { // 只剩最后一个位置时,直接填入剩余值并输出组合 if ($remainingLength === 1) { $currentCombination[] = $remainingSum; yield $currentCombination; return; } // 当前数的取值范围:最小1,最大为剩余总和减去(剩余位置数-1)——保证剩下的位置至少能填1 $maxCurrent = $remainingSum - ($remainingLength - 1); for ($i = 1; $i <= $maxCurrent; $i++) { // 递归处理剩余位置,把当前数加入组合 yield from $generate($remainingSum - $i, $remainingLength - 1, [...$currentCombination, $i]); } }; // 启动递归生成逻辑 yield from $generate($targetSum, $setLength, []); } // 使用示例 // 找和为50、长度2的组合 foreach (getFixedLengthIntegerPartitions(50, 2) as $comb) { print_r($comb); } // 找和为50、长度4的组合 foreach (getFixedLengthIntegerPartitions(50, 4) as $comb) { print_r($comb); }
兼容原参数的版本(支持自定义可选数字)
如果需要保留原函数的$numbers参数,让你可以指定能用哪些数字来组合,就用这个版本:
function getCombinations(array $numbers, int $sum, int $setLength): \Generator { // 对可选数字去重、排序,方便后续逻辑处理 $uniqueNumbers = array_unique($numbers); asort($uniqueNumbers); $sortedNumbers = array_values($uniqueNumbers); $minNum = $sortedNumbers[0] ?? 0; // 边界检查:如果最小数字乘组合长度都大于目标和,直接返回空 if ($minNum * $setLength > $sum) { return; } // 递归生成组合的内部函数,startIndex用于避免重复组合(比如[1,49]和[49,1]只生成一次) $generate = function(int $remainingSum, int $remainingLength, array $currentCombination, int $startIndex) use (&$generate, $sortedNumbers, $minNum) { // 只剩最后一个位置时,检查剩余值是否在可选数字中,符合则输出组合 if ($remainingLength === 1) { if (in_array($remainingSum, $sortedNumbers)) { $currentCombination[] = $remainingSum; yield $currentCombination; } return; } // 从startIndex开始遍历,避免重复组合;如果需要生成有序组合(比如[1,49]和[49,1]都要),把startIndex改成0即可 for ($i = $startIndex; $i < count($sortedNumbers); $i++) { $num = $sortedNumbers[$i]; $newRemaining = $remainingSum - $num; // 剩余总和必须满足剩下的位置每个都能放最小数字,否则跳过后续更大的数字 if ($newRemaining >= $minNum * ($remainingLength - 1)) { yield from $generate($newRemaining, $remainingLength - 1, [...$currentCombination, $num], $i); } else { // 数字已排序,后续更大,直接跳出循环节省时间 break; } } }; // 启动递归生成逻辑 yield from $generate($sum, $setLength, [], 0); } // 使用示例(用1到49的正整数组合出和为50、长度2的序列) $positiveIntegers = range(1, 49); foreach (getCombinations($positiveIntegers, 50, 2) as $comb) { print_r($comb); }
关键说明
- 用生成器(
Generator)的好处是不用一次性把所有组合加载到内存中,目标和较大时不会出现内存溢出问题。 - 简化版逻辑更直接,完全匹配你的需求;兼容版更灵活,可应对自定义可选数字的场景。
内容的提问来源于stack exchange,提问作者rafark
相关产品推荐
相关产品推荐

