「这是我参与11月更文挑战的第25天,活动详情查看:2021最后一次更文挑战」
今天说一下三数之和的问题。
实现思路
暴力解法
按照一般的思考方式,不管数组内有多少元素,由于判断的是某三个元素的和是否为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向前移动一位。
完整代码
第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行代码,返回符合条件的三元组。