之前在南山软件产业园靠近腾讯那边面试了一家公司,其中有一道算法题挺有印象的。当时没能准确写出答案,后来去网上找了下资料将答案记录了下来,题目如下:
一个包含n个整数的数组S,判断S中是否存在元素a + b + c = 0,查找数组中所有和为零的三元组。
注意:解决方案集不能包含重复的三元组。
例如,数组S = [-1,0,1,2,-1,-4]
答案是:
[
[-1 , 0 , 1],
[-1 , -1 , 2]
]
解题思路:
· 首先对数组进行排序,时间复杂度为O(n*logn)
· 然后对数组进行两层的遍历,先取出当前遍历的数字为nums[i],然后从数组两侧取出数字分别为nums[begin]和nums[end],然后三个数求和值为sum
· sum === 0,将三个数加入结果之中,同时将两侧的下标向中间移动,直到不与之前取出的数字相同,避免出现重复的三元组
· sum > 0,因为数组有序,说明右侧的数字过大,所以下标左移,故而执行end--
· sum < 0,因为数组有序,说明左侧的数字过小,所以下标右移,所以执行begin++
· 因为两层的遍历时间复杂度为O(n^2),O(n*logn) + O(n^2) = O(n^2),所以总体时间复杂度为O(n^2)
代码:
class Solution
{
/**
* 获取结果集
* @param array $nums
* @return array
*/
public function getNumList(array $nums = []) : array
{
$list = [];
if (empty($nums) || count($nums) < 3) return $list;
// 首先进行数组升序排列
sort($nums);
for ($i = 0; $i < count($nums); $i++) {
// 如果第一个数字大于0,则其之后的数字一定大于0(因为已经排序了),所以三数之和一定大于0,结束循环
if ($nums[$i] > 0) break;
// 对三个数中的第一个数字去重
if ($i > 0 && $nums[$i] === $nums[$i - 1]) continue;
// 开始查找的下标地址
$begin = $i + 1;
// 结束查找的下标地址
$end = count($nums) - 1;
// 当开始查找的下标地址在结束查找的下标地址前边的时候,遍历所有情况
while ($begin < $end) {
// 对三个数中的第二个数去重
while ($begin < $end && $nums[$begin] === $nums[$begin + 1]) $begin++;
// 对三个数中的第三个数去重
while ($begin < $end && $nums[$end] === $nums[$end - 1]) $end--;
$sum = $nums[$i] + $nums[$begin] + $nums[$end];
if ($sum === 0) {
$list[] = [$nums[$i], $nums[$begin], $nums[$end]];
//当sum===0时,第一个数保持不变的情况下,第二和第三个数不能再重复了,所以左边的begin向后继续遍历,右边的end向前继续遍历
$begin++;
$end--;
} else if ($sum < 0) {
// 当sum<0说明左边的数num[begin]太小了,所以要左边的地址+1继续向后遍历
$begin++;
} else if ($sum > 0) {
// 当sum>0说明右边的数num[end]太大了,所以要右边的地址-1继续向前遍历
$end--;
}
}
}
return $list;
}
}
最后写上调试程序:
$nums = [-4,-2,-2,-2,0,1,2,2,2,3,3,4,4,6,6];
$solution = new Solution();
$list = $solution->getNumList($nums);
echo '<pre>';
print_r($list);
echo '</pre>';
结果无误。