这是我视频面试时做过的一道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
, 上面的程序不能满足要求。剩下的工作就交给大家,一起动动脑,动动手,活动活动。