国内某著名IT公司编程测验: 以数组内的三个下标四等分一个数组

国内某著名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

欢迎交流!

“`

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值