PHP 三数之和 - LeetCode 15

539 阅读4分钟

「这是我参与11月更文挑战的第25天,活动详情查看:2021最后一次更文挑战

今天说一下三数之和的问题。

image.png

实现思路

暴力解法

按照一般的思考方式,不管数组内有多少元素,由于判断的是某三个元素的和是否为0,所以以三个元素一组,找出数组内元素有多少种排列,然后判断每种排列是否等于0,就可以找出三个元素之和等于0的三元组。

我们可以写三个循环,依次判断,伪代码如下

for($i=0;$i<count($nums);$i++){
    for($j=0;$j<count($nums);$j++){
        for($k=0;$k<count($nums);$k++){
            if($nums[$i] + $nums[$j] + $nums[$k] == 0){
            }
        }
    }
}

通过上面的伪代码就可以得出所有元素三个一组的排列,然后控制一下重复的数据,就可以得出结果。

但是上面这种情况复杂度比较高,有三层for循环,所以时间复杂度为O(n^3)

那么我们如何高效率的实现这个三个元素之和的判断呢?

排序+双指针

我们看三个元素之和等于0这个条件,除了[0,0,0]这个三元组之外,其他的任何三元组都会有一个负数和一个正整数的情况。当一个三元组内全都是正整数时,那么这个三元组内元素之和肯定不等于0。

由于整个数组都属无序的,那么我们如何知道三元组内都是正整数或者有其中某一个元素,就肯定不符合条件呢?

答案就是给数组排序。

数组排序之后,我们依次遍历每一个元素,给该元素组合该位置之后的其他任意两个元素,由于我们说过有[0,0,0]这种情况存在,所以当前元素为大于0的正整数时,后面的元素就不必再组合了,后面任意三个元素之和肯定要大于0。

那么我们如何组合当前元素的其他两个元素呢?

由于数组是有序的,当三个元素符合条件后,除非接下来的元素都是和之前的元素相等的,如果我们只改变一个元素的话,由于另外两个元素不变,第三个元素就确定了,所以我们需要同时改变另外两个元素的值。如果还依次组合剩余的两个元素,那么就会造成多余的判断。

我们遍历排序好的数组,同时定一个下标 left 定义在 i+1 的位置上,定义下标 right 在数组结尾的位置上。使这两个下标不断向中间移动。同时不断判断这三个元素的和有下面三种情况

  • 等于 0 时,说明符合条件,把数据存起来,同时left向后移动一位,right向前移动一位。
  • 小于 0 时,说明三个元素之和比较小,所以left向后移动一位。
  • 大于 0 时,说明三个元素之和比较大,所以right向前移动一位。

完整代码

image.png

第507-510行代码,如果数组长度小于 3,则返回空数组,因为数组内没有符合条件的三元组。

第511行代码,使用PHP内置函数对数组进行升序排序。

第512行代码,定义一个数组,存储符合条件的三元组。

第513-541行代码,遍历数组,寻找符合条件的三元组。

第514-516行代码,由于数组升序排列,所以当前元素值大于0时,说明后续的元素都是正整数,全都不符合条件,直接结束循环。

第517-519行代码,如果当前元素和上一个元素值相等,则中断本次循环,是为了避免重复的三元组。判断$i>0是因为第一个元素不需要判断,因为没有前一个元素。

第520-521行代码,$left指针指向当前元素的后一个元素,$right指针指向数组的结尾。

第522-539行代码,找出当前for循环遍历元素的另外两个元素组合。当$left指针位置大于$right指针位置时退出循环,因为此时已重复。

第523-534行代码,获取三个元素之和并判断,等于0时,把元素组合放入之前定义的数组$arr中。此时判断$left指针位置是否和后一个位置的值相等,如果相等$left指针则不断后移,直到不等为止。判断$right指针位置是否和前一个位置相等,如果相等$right指针则不断前移,直到不等为止。

然后$left指针位置加一,$right指针位置减一。

第535-536行代码,如果三个元素之和小于0,则$left指针位置加一。

第537-538行代码,如果三个元素之和大于0,则$right指针位置减一。

第542行代码,返回符合条件的三元组。