原问题:
老板有1000个苹果,却只有10个箱子,客户给老板除了一个难题,说如果老板把题解开就把1000个苹果全买了,老板说没问题,来吧!我之前是程序员。客户说:“把你现在的1000个苹果全部装进10个箱子(分别为1号、2号.....10号),忽略箱子的容量大小,每个箱子都要装苹果,装几个你自己定,我要买几个苹果,你就告诉我,把几号,几号拿走,就是你要的个数。”,老板说:“呵呵。”
问题答案:
此问题是否有解?如何求解?
1.首先我们必须写一个验证的程序
/** * 验证数组 * @author syh * @param array $arr * @return bool */ function check($arr) { rsort($arr);//逆序 $listNum=range(1,array_sum($arr)); $fun=function ($num) use ($arr){ foreach ($arr as $key => $val) { if($num===$val) return true; //减去数组中比自己小的最大值 if($num>$val) $num=$num-$val; } return false; }; return !in_array(false,array_map($fun,$listNum)); } $arr=[1,2,4,8,16,32,64,128,256,489];//此数组为正解的一种,可以通过验证 $data=check($arr); var_dump($data);//true
2.遍历所有可能的组合或随机取值
2.0非递归遍历(最原始的实现)
/** * $m个苹果分为4堆 * @author syh * @param int $m苹果总数 * @return array 验证通过后的数组 */ function test($m) { $listAll=[]; $list=[]; $len=4;//苹果堆数 $max=function($list) use ($m,$len){ return $m-array_sum($list)-($len-count($list))+1; }; for ($k1=1,$max1=$max($list);$k1 <=$max1; $k1++) { $list[1]=$k1; for ($k2=1,$max2=$max($list);$k2 <=$max2 ;$k2++) { $list[2]=$k2; for ($k3=1,$max3=$max($list);$k3 <=$max3; $k3++) { $list[3]=$k3; $list[4]=$m-array_sum($list); if(check($list)) { $listAll[]=$list; } unset($list[3]); unset($list[4]); } unset($list[2]); } unset($list[1]); } return $listAll; }
2.1第一种遍历所有可能,
/** * 递归实现1 * @author int $m 苹果的个数 * @param int $n 苹果堆数 * @return array */ function crateArr($m,$n) { static $list=[];//保存当前的组合 static $listAll=[];//保存所有可能的组合 static $count=0,$len; if(($count++)===0) $len=$n;//保存数组长度 $max=$m-array_sum($list)-($len-count($list))+1; for ($i=1;$i<=$max;$i++) { $list[$n]=$i;//第$n堆苹果 if ($n===2) { //此处为最后一次循环 $list[$n-1]=$m-array_sum($list);//最后一堆苹果的个数 if (check($list)) { $listAll[]=$list; //验证通过后合并 } unset($list[$n-1]); unset($list[$n]); } elseif($n>2) { crateArr($m,$n-1);//递归进行 相当于for循环嵌套 unset($list[$n]); } } return $listAll;//返回所有符合要求的组合 }
2.2第一种遍历的次数太多,进一步优化
/** *递归实现2 * @author syh * @param string $m 苹果总数 * @param string $n 苹果堆数 * @param string $p 第一堆苹果起始值 * @return array */ function createArr($m,$n,$p=1) { static $list=[],$listAll=[]; static $count=0,$len; if(($count++)===0) $len=$n;//保存数组长度 $d=$len-($len-$n)-1;//第$n项到$len-1项的元素个数 // 假设第$n堆苹果到$len-1堆苹果数位公差为1的等差数列 //最后一堆苹果个数=苹果总和-当前堆前面的总和-当前堆到第$len-1堆等差数列的和 //第$len-1堆苹果的个数为a1+(n-1)*d 等差数列第n项公式 //当前堆到$len-1的总和为a1*n+n*(n-1)*d/2等差数列前n项和 // 数组为从小到大排序,当最后一项与倒数第二项相等时为每堆苹果的最大值,即临界点 $max=($m-array_sum($list)-$d*($d-1)/2-$len+1)/($d+1)+1; for ($i=$p;$i<=ceil($max);$i++) { $list[$n]=$i; if ($n===2) { $list[$n-1]=$m-array_sum($list); if(check($list)) { // 验证通过后存入数组 $listAll[]=$list; } unset($list[$n-1]); unset($list[$n]); } elseif($n>2) { $function=__FUNCTION__; //堆数递减,后面堆苹果最小个数=前面堆苹果个数+1 $function($m,$n-1,$i+1); unset($list[$n]); } } return $listAll; }
2.3随机取值
function crateArr($sum,$len) { while(1) { $arr=[]; for ($j=1; $j<=$len; $j++) { $max=$sum-array_sum($arr)-($len-count($arr))+1; $arr[$j]=($j==$len)?$sum-array_sum($arr):mt_rand(1,$max); } if(check($arr)) var_dump($arr); } }
总结:经过自己不懈的努力,程序终于实现了;但1000个苹果分成10堆计算量太大,自己的电脑最多能计算300个苹果分成10堆。
1,2,4,8,16,32,64,128,256,489
通过观察得出,符合此规律的等比数列,能组合的最大数为2的(数列长度)次方-1。通过程序验证63(2^6-1)个苹果分成6堆苹果只有一个解,小于63就会存在多解;同理,1000个苹果分成10堆,能组合的最大数为2^10-1(1023);所以此问题存在多解,上面的数列只是其中一种解。
2.4 进一步研究,求全部解
/** * 遍历后2位 * 此解并不是最终解 * @author syh * @return array */ function checkAll() { $arr=[1,2,4,8,16,32,64,128,256,489]; $list=[]; for ($i=256; $i >128 ; $i--) { array_pop($arr); array_pop($arr); array_push($arr, $i,745-$i); if (check($arr)) { $list[]=implode('--',$arr); } } return $list; } $data=checkAll($arr); echo '<pre>'; print_r($data); Array ( [0] => 1--2--4--8--16--32--64--128--256--489 [1] => 1--2--4--8--16--32--64--128--255--490 [2] => 1--2--4--8--16--32--64--128--254--491 [3] => 1--2--4--8--16--32--64--128--253--492 [4] => 1--2--4--8--16--32--64--128--252--493 [5] => 1--2--4--8--16--32--64--128--251--494 [6] => 1--2--4--8--16--32--64--128--250--495 [7] => 1--2--4--8--16--32--64--128--249--496 [8] => 1--2--4--8--16--32--64--128--248--497 [9] => 1--2--4--8--16--32--64--128--247--498 [10] => 1--2--4--8--16--32--64--128--246--499 [11] => 1--2--4--8--16--32--64--128--245--500 )
转自梦里不知身是客