国内某著名IT公司编程测验: 以数组内的三个下标四等分一个数组
论坛原贴>
http://bbs.csdn.net/topics/392114261
刚刚参加了国内某著名IT公司的编程测验,感觉有点怀疑人生了
题目如下:写一个函数,输入是一个整数数组,输出是一个布尔值,判断这个整数数组能不能四等分。什么是四等分呢?简单说,就是在这个数组里面找到三个下标,这三个下标对应的数把数组分成四个部分,这四个部分和是相等的,那么这个数组就是可以四等分的。例如:1,1,1,2,0,2,5,-1,3,找到三个下标2,4,6分成的四个部分是{1,1},{2},{2},{-1,3},这四个部分和都是2,所以这个数组是可以四等分的,暂且认为如果四个部分有一个部分是空的话和就是0,比如可以认为{1,1,1}这个数组可以四等分(这一个要求我记得不是很清楚了,其实不是很重要)。
数组长度十万以内,数组里的每个数范围正负十万之间,要求:时间复杂度O(N)以内,空间复杂度O(N)以内,N是数组长度
首先说明:如果按照题目的要求,四等分不能包括边界下标的话,这是一道动态规划的算法题,而且需要考虑比较多的因素,因为每段区间的和是不能通过总和来计算的,必须通过不断的动态规划,才能找出或不能找出这个“四等分”值来。本人才疏学浅,不知道怎样才能将动态规划问题的复杂度变为O(N)。
以下是用php实现的方法:复杂度O(M*N)
首先来讲讲思路:比如有如下满足条件的数组$arr =[1,3,1,3,1,3,1,3,4,4],长度为10,默认数组两头之外为0,首先我们把这个数组分成3份(左边,中间,右边),其中,左边 = 右边,比如这样[0], 1, [3,1,3,1,3,1,3,4,],4, [0] 又或者是这样[1,3] ,1,[3,1,3,1,3],4,[4],加粗的数字是从数组抽离的隔离点,是不纳入区间求和范围的,这点要注意。我们分成左中右后呢,接下来就是把中间那部分也分成两部分,比如以上的例子,可以是
[0], 1, [3,1],3,[1,3,]1,[3,4,],4, [0] ,
也可以是这样的:
[1,3] ,1,[3,1,],3,[1,3],4,[4]。其中只有后者才能满足条件。废话不多,首先我们来写一个分割中间那部分的方法,如以下的
div2Pairs(),首先我们得把中间这一段攫取下来,传入左右两个分界点,两个分解点往中间挤一挤,比如上面的
1,[3,1,3,1,3],4 ,挤掉分解点后,就只剩下[3,1,3,1,3]。现在要把[3,1,3,1,3]分成两等份(如果可以等份),因为我们必须保留一个分解点,所以不能通过求总和折半的方式,为了保证尽可能的分成两等份,我们可以分别从两头开始,假设从左边开始的和为
lsum,右边开始的为
rsum ,那么我们应该确保每一步操作中总有|
lsum−
rsum|是最小的,以避免错过任何一个可能的
lsum==
rsum,以下为这个方法具体实现:
<?php
/**
* [div2pairs divide the array into two group ]
* @param int $avg [dynamic average]
* @param int $ldeliIndex [the left delimeter]
* @param array &$array [the array to divide by lsum compared rsum]
* @param int $rdeliIndex [the right delimeter]
* @return [mixed] [if true return the middle delimeter,or false]
*/
public function div2pairs(int $avg,int $ldeliIndex,array&$array,int $rdeliIndex){
$lstart = $ldeliIndex+1; //the index where begin to calculate the sum from left;
$rstart = $rdeliIndex-1; //the index where begin to calculate the sum from right;
$lsum = $array[$lstart]; //init the $lsum
$rsum = $array[$rstart]; //init the $rsum
$gap = $lsum-$rsum;
while($lstart<$rstart){ //although within two 'while' but its time complexity is truely o(n);
while($lsum>$rsum && $lstart<$rstart-2){ //here the number 2 means that there must be
$rsum+=$array[--$rstart]; //a delimeter index can't not put into calculate
if($lsum-$rsum>$gap && $lstart<$rstart-2){ //avoid the gap being bigger;
$lsum+=$array[++$lstart];
}
$gap = $lsum-$rsum;
}
while ($lsum<$rsum && $lstart<$rstart-2) {
$lsum+=$array[++$lstart];
if($lsum-$rsum<$gap && $lstart<$rstart-2){
$rsum+=$array[--$rstart];
}
$gap = $lsum-$rsum;
}
if($rsum==$lsum){
if($rstart ==$lstart+2){
if($rsum ==$avg) //true return the dividing delimeter
return $lstart+($rstart-$lstart)/2;
else
return false;
}else{
++$lstart; //you can also change these two lines like this:
$lsum+=$array[$lstart]; //--$rstart;$rsum+=$array[$rstart]
}
}else{
if($lsum<$rsum){ //here and 'while' is used to make the $lsum and $rsum more close;
++$lstart;
$lsum+=$array[$lstart];
}else{
--$rstart;
$rsum +=$array[$rstart];
}
}
$gap = $lsum-$rsum; //upgrade the gap between $lsum and $rsum;
}
return false;
}
但是在拆分中间那部分时,我们需要先确保,左边 = 右边($lsum ==$rsum),
确保动态的可能是最终结果的区间和$avg,我们使用与div2Pairs()里相类似
的方式,两头开工,获取$lsum ==$rsum==$avg使他们相对应的两个分解点($ldelimeter 和 $rdelimeter),
然后代入div2Pair()就可以了。
但是由于所有的m个可能的div2Pair()都需要用到,所以我们不能每次都去
求一边,我们可以先用一个数组$lsumArray 先把$lsum所有下标都村起来,
以方便$rsum动态调用。以下的div4Pairs()将数组四等分返回左中右三个
分界点,或不能等分返回false
/**
* [div4pairs divide the array into 4 pairs with delimeter]
* @param array $array [array to divide]
* @return [mixed] [if true then return an array cluding all delimeter]
*/
public function div4pairs(array$array){
$length = count($array); //get the lengt of the array;
if($length<5) return false; //because divide into 4 equals with delimeter
$lsumArray = []; //array to store the lsum which calculate from 0;
$lsumArray[0][] = -1; //$lsumArray[$lsum] = index, if index<0 ,default -1
$end = $length-2; //the 4th pair must be >=0,default $array[$length] = 0 as well as the
//$array[-1] = 0,thought they do not exist
for ($i = 0,$lsum = 0; $i < $end; ++$i) {
$lsum+=$array[$i];
$lsumArray[$lsum][] = $i; //store the index for each lsum;
}
for($j = $length-1,$rsum = 0;$j>3;--$j){ //from right to left ,calculate the rsum;
if(isset($lsumArray[$rsum])){ //
while(false!==current($lsumArray[$rsum])){
$ldeliIndex = current($lsumArray[$rsum])+1;
if($ldeliIndex>$j-4) break; //3 elements between ldelimeter and rdelimeter($j);
if(($mid=self::div2pairs($rsum,$ldeliIndex,$array,$j))) //try to divide array into 4 equals
return [$ldeliIndex,$mid,$j]; //true then return these three delimeters index;
next($lsumArray[$rsum]);
}
}
$rsum+=$array[$j];
}
return false;
}
以下这个是最常规的方法,简单一目了然。空间复杂度O(N) 时间复杂度O(N^2)
/**
* [div4pairsF another simple method to divide array into 4 equals with excluding delimeter index]
* @param array $array [array to divide]
* @return [mixed] [if true return delimeters index,or false]
*/
public function div4pairsF(array$array){
$length = count($array);
$ilength = $length-4;
$jlength = $length-2;
for ($i=0,$isum = 0;$i < $ilength; ++$i) {
for ($j=$i+1,$jsum = 0; $j < $jlength; ++$j) {
$jsum+=$array[$j];
if($jsum==$isum){
++$j;
for($k = $j+1,$ksum = 0;$k<$length;++$k){
$ksum+=$array[$k];
if($ksum==$isum){
++$k;
if($k+1<$length){
for ($n=$k+1,$nsum=0; $n < $length; ++$n) {
$nsum+=$array[$n];
}
if($nsum==$isum)
return [$i,$j,$k];
}
}
}
}
}
$isum+=$array[$i];
}
return false;
}
public function createTestingData(int $m){
for ($i=0,$data=[]; $i < $m; ++$i) {
$in = mt_rand(-$m,$m);
$data[] = $in;
}
return $data;
}
check:
$all = [1,2,5,4,-1]; // false
$all = [1,-1,2,-1,4,-3,4,2,1]; //array(3) { [0]=> int(1) [1]=> int(4) [2]=> int(7) }
$all = [1,3,1,3,1,3,1,3,4,4]; //Array ( [0] => 2 [1] => 5 [2] => 8 )
// $res = AlgorithmicTest::div4pairsF($all);
$res = AlgorithmicTest::div4pairs($all);
print_r($res);
测试两种方法
$n = 100;//1000,10000,100000,1000000
$all = AlgorithmicTest::createTestingData($n);
echo memory_get_usage(),'<br>';
$s = microtime(true);
$res = AlgorithmicTest::div4pairs($all);
// $res = AlgorithmicTest::div4pairsF($all);
echo 'times: ',microtime(true)-$s,'<br>';
echo memory_get_peak_usage(),'<br>';
结果:
memory:byte times:s
div4pairsF($all) n=100
memory used before processing:447216
times: 0.00018191337585449
memory used_peak in processin: 612104
div4pairs($all)n=100
memory used before processing:447176
times: 6.9141387939453E-5
memory used_peak in processin: 612104
div4pairsF($all) n=1000
memory used before processing:475888
times: 0.010547161102295
memory used_peak in processin: 612104
div4pairs($all) n=1000
memory used before processing:475848
times: 0.00043797492980957
memory used_peak in processin: 880960
div4pairsF($all) n=10000
memory used before processing:967408
times: 0.97418403625488
memory used_peak in processin: 967664
div4pairs($all)n=10000
memory used before processing:967368
times: 0.0051088333129883
memory used_peak in processin: 5293064
div4pairsF($all) n=100000 (已经阵亡!)
memory used before processing:4637448
(30s内无法完成)
Fatal error: Maximum execution time of 30 seconds exceeded in /usr/local/nginx/html/algorithmic/algorithmic_test/AlgorithmicTest.php on line 741
div4pairs($all) n=100000
memory used before processing:4637408
times: 0.089447975158691
memory used_peak in processin: 46877568
div4pairs($all) n=1000000
memory used before processing:33997536
times: 3.2110240459442
memory used_peak in processin: 447408504
欢迎交流!
“`