1. 程式人生 > >在連續數字序列中快速找出連續數字之和等於指定值的子序列

在連續數字序列中快速找出連續數字之和等於指定值的子序列

這是我視訊面試時做過的一道PHP題目,題目其實並不複雜,是一道常見筆試題的變種。自己年紀大了,好久沒做過筆試題,有點生疏。當時反應變慢了,寫了個愚蠢的答案,這裡總結一下,活動活動自己的腦子,同時給出自己更好的答案,大家可以一起來討論,發表自己的看法。總結總是好的,因為它可以提高自己以後解決問題的能力和反應速度。題目要求如下:

給定一數字數列,如: 3,2,1,5,4,3,7,9, 返回 數字之和 等於指定值的 連續子序列,比如 要求數字之和 等於 12 返回連續子序列 2 1 5 4

說實話,自己剛開始有點愣,不過很快我給出了下面的答案,為了更清晰,方便討論,我保持程式邏輯不變,改動了函式變數名,因為當時自己面試的時候有點亂。

 /**
  * @todo 返回數字之和等於指定值的連續子序列
  * @param array $array 數字陣列
  * @param integer $targetSum 目標和
  * @return array
  */
 function getSubSequenceEqualTargetSum($array,$targetSum){
    $count = count($array);
    $sequence= [];
    for($i=0; $i<$count;$i++){
        $sequence= getSubSequence($array,$i
,$count,$targetSum); if(count($sequence)>0){ break; } } return $sequence; } /** * @todo 從陣列任一下標開始查詢數字之和等於指定值的連續子序列 * @param $array 陣列序列 * @param $index 陣列任一下標 * @param $count 陣列最大下標 * @param $targetSum 目標和 * @return array */ function getSubSequence($array,$index
,$count,$targetSum)
{
if($index<$count){ $sum = 0; $sub = []; for($i=$index; $i<$count;$i++){ $sum += $array[$i]; array_push($sub,$array[$i]); if($sum == $targetSum){ return $sub; }else if($sum > $targetSum){ return []; } } } return []; }

上面給出的答案,本身是可以正確返回指定序列的。思路是迴圈遍歷數字序列,累加數字,直到剛好等於目標和,就直接返回;如果中途大於目標和,說明到目前為止這個連續子序列不合要求,我們從下一個下標開始重新累加。 不過第二個函式會頻繁呼叫array_push, 會影響效能。所以我面試完又細想了一下,發現在子序列不合要求的情況下,從下一個下標開始重新累加數字之和的時候,不用重新計算,只需減去前面子序列開始位置的數字,然後再加上下一位數字即可。為此利用了一個臨時變數作為指標,記錄當前累加的子序列的開始位置。所以我給出的效能更好的答案如下:

 /**
  * @todo 返回數字之和等於指定值的連續子序列[快速版]
  * @param array $array 數字陣列
  * @param integer $targetSum 目標和
  * @return array
  */
function getSubSequenceEqualTargetSumFast($array,$targetSum){
    $sum = 0;
    $start = 0;//指向子序列開始的位置
    $count = count($array);
    $sequence = [];
    for($i=0;$i<$count;$i++){//$i指向子序列最後的位置
        $sum += $array[$i];
        if($sum == $targetSum){
            $sequence = array_slice($array,$start,$i-$start+1);
        }else if($sum > $targetSum){
            $sum -= $array[$start];
            $start ++;
            //或者下面這種更簡潔些的寫法
            // $sum -= $array[$start++];
        }
    }
    return $sequence;
}

為了對比兩個程式的效能,我故意構造了一個巨大的陣列,會引起array_push的頻繁呼叫,測試在糟糕的情況下,程式效能能提升到什麼程度。

ini_set('memory_limit','512M');```
$arr = [];
for($i=0;$i<1000000;$i++){
    array_push($arr,1);
}
for($i=0;$i<1000000;$i++){
    array_push($arr,-1);
}
for($i=0;$i<2000000;$i++){
    array_push($arr,1);
}
$start = microtime(true);
$sub = getSubSequenceEqualTargetSum($arr,2000000);
$end = microtime(true);
echo "<br/>用時 ".($end-$start);
$start = microtime(true);
$sub = getSubSequenceEqualTargetSumFast($arr,2000000);
$end = microtime(true);
echo "<br/>用時 ".($end-$start);

//下面是測試結果

用時 0.75404405593872
用時 0.28001594543457

對比發現,程式提高了差不多2倍多。讓我有點小意外,我以為會提高10倍以上。不過優化還是值得的。

舉一反三
上面的答案也有一個不好的地方,就是隻返回目標和的一個子序列,實際滿足情況的可能會有多個子序列, 如文章開頭等於12的子序列還有 5,4,3 , 還有一種比較特殊的情況,就是序列中間有 連續多個0 的情況,如 3 2 1 0 0 0 0 2 3, 上面的程式不能滿足要求。剩下的工作就交給大家,一起動動腦,動動手,活動活動。