优化查找数组中最接近给定数字的数字总和

时间:2019-11-27 07:12:34

标签: php combinations

说我有一个数组[10000,5000,1000,1000],我想找到与给定数字最接近的数字总和。抱歉,我们的解释不正确,但是这里有一个例子:

说我有一个数组[10000,5000,1000,1000]我想找到最接近的数字,例如6000。

然后该方法应返回5000和1000

另一个例子:我们想要最接近14000,那么他应该返回10000和5000

我尝试使用下面的代码,但是这里工作正常,但是如果 $ desiredSum $ numbers数组大。在php执行超时之前,它的运行是如此缓慢

$numbers = array(
    10000,5000,1000,1000
);
$desiredSum = 6000;
$minDist = null;
$minDist_I = null;
// Iterate on every possible combination
$maxI = pow(2,sizeof($numbers));
for($i=0;$i<$maxI;$i++) {
    if(!(($i+1) % 1000)) echo ".";

    // Figure out which numbers to select in this 
    $sum = 0;
    for($j=0;$j<sizeof($numbers);$j++) {
        if($i & (1 << $j)) {
            $sum += $numbers[$j];
        }
    }
    $diff = abs($sum - $desiredSum);
    if($minDist_I === null || $diff < $minDist) {
        $minDist_I = $i;
        $minDist = $diff;
    }

    if($diff == 0) break;
}
$chosen = array();
for($j=0;$j<sizeof($numbers);$j++) {
    if($minDist_I & (1 << $j)) $chosen[] = $numbers[$j];
}
echo "\nThese numbers sum to " . array_sum($chosen)  . " (closest to $desiredSum): ";
echo implode(", ", $chosen);
echo "\n";

有人可以帮助我吗?

1 个答案:

答案 0 :(得分:0)

<?php

function coinChange($numbers,$desiredSum){
    sort($numbers);
    $set = [];
    $set[0] = [];

    for($i = $numbers[0];$i <= $desiredSum;++$i){
        foreach($numbers as $index => $current_number){
            if($i >= $current_number && isset($set[$i - $current_number])){
                if(isset($set[$i - $current_number][$index])) continue;
                $set[$i] = $set[$i - $current_number];
                $set[$i][$index] = true;
                break;
            }
        }
    }

    if(count($set) === 0){
        return [0,[]];
    }

    if(isset($set[$desiredSum])){

        return [
            $desiredSum,
            formatResult($numbers,array_keys($set[$desiredSum]))
        ];
    }else{
        $keys = array_keys($set);
        $nearestSum = end($keys);
        $sum = 0;
        $rev_numbers = array_reverse($numbers);
        $result = [];
        foreach($rev_numbers as $number){
            $sum += $number;
            $result[] = $number;
            if($sum > $nearestSum && abs($nearestSum - $desiredSum) > abs($sum - $desiredSum)){
                $nearestSum = $sum;
                break;
            }else if($sum > $nearestSum && abs($nearestSum - $desiredSum) < abs($sum - $desiredSum)){
                $result = formatResult($numbers,array_keys($set[$nearestSum]));
                break;
            }
        }

        return [
            $nearestSum,
            $result
        ];
    }
}

function formatResult($numbers,$keys){
    $result = [];
    foreach($keys as $key) $result[] = $numbers[$key];
    return $result;
}

print_r(coinChange([10000,5000,1000,1000],14000));
print_r(coinChange([10000,5000,1000,1000],13000));
print_r(coinChange([100000,100000,100000,100000,100000,100000,50000,50000,50000,50000,10000,10000,500,500,500,1000,1000],250000));
print_r(coinChange([100000,100000,100000,100000,100000,100000,50000,50000,50000,50000,10000,10000,500,500,500,1000,1000],179999));

演示: https://3v4l.org/hBGeW

算法:

  • 这类似于coin change问题。

  • 我们首先对数字进行排序。

  • 现在,我们从数组中的最小数量迭代到所需的总和。
  • 在其中,我们遍历数组中的所有元素。
  • 现在,如果我们已完成总和$i,我们就只能使$i - $current_number(这是总和)成为可能。如果我们有前一个,则将$current_number加到集合中以求和$i

两种情况:

  • 如果我们可以得出确切金额,那么我们将按原样返回结果。
  • 如果我们做不到,那么有两种可能性:
    • 我们在$set中已经有最接近的总和,这将是最后一个条目。我们将它们保留在变量中。
    • 现在,最接近的总和也可能高于所需的总和。因此,我们得到较大的总和,并检查它是否比最接近的最小总和更近,然后将两者进行比较并返回结果。

结果格式:

让我们看下面的示例输出:

Array
(
    [0] => 15000
    [1] => Array
        (
            [0] => 10000
            [1] => 5000
        )

)

这只是意味着第一个索引是可能的最接近的总和,第二个索引处的数组是从$numbers获取该总和的所有元素。