15. 三数之和

题目描述
思路
今天我们来回顾经典的三数之和问题。
解决这个问题的思路实际上就是对排序过的数组使用双指针。首先我们需要对数组进行排序;针对排序过后的数组,我们使用x表示当前我们正在遍历的数组下标i对应的值,设置两个指针j和k,j的初始值是i + 1,k的初始值是n - 1(此处的n是数组长度)。我们开始向中间移动这两个指针,如果满足:x + nums[j] + nums[k] > 0,则应该将k向左移动,因为我们应该削减数值最大的那个数才能够让三数之和有可能等于零;反之如果x + nums[j] + nums[k] < 0,那么我们应该将j向右移动,原因是只有将较小的数调大才有机会找到三数之和为零的情况。如果这个和为零,则代表我们找到了一个三数之和。
由于题目中明确要求不能有重复,所以我们找到一个答案之后需要进行一次去重,方法也很简单,那就是向中间移动j和k,如果nums[j] == nums[j - 1]则向右移动j,如果nums[k] == nums[k + 1]则向左移动k。
这道题目在对当前数组的下标i进行遍历的时候有三个缩减遍历空间的小技巧:
- 如果
i != 0且nums[i] == nums[i - 1],则直接向右移动i,因为题目要求不能出现重复的答案; - 如果
nums[i] + nums[i + 1] + nums[i + 2] > 0,那么我们就可以停止遍历了,因为数组已经经过了排序,后面的数值不会再出现三数之和为零的情况了; - 如果
nums[i] + nums[n - 1] + nums[n - 2] < 0,则代表当前遍历的数值加上数组当中最大的两个数之和,其值仍然小于零,对当前这个数进行双指针寻找三数之和为零的情况一定是找不到的,此时我们应该继续向右移动i。
基于以上思路,我们写代码来解决问题。
Golang 题解
go
func threeSum(nums []int) [][]int {
sort.Slice(nums, func(i, j int) bool {
return nums[i] < nums[j]
})
ans := [][]int{}
n := len(nums)
for i := 0; i < n - 2; i ++ {
x := nums[i]
if i > 0 && x == nums[i - 1] {
continue
}
if x + nums[n - 1] + nums[n - 2] < 0 {
continue
}
if x + nums[i + 1] + nums[i + 2] > 0 {
break
}
j, k := i + 1, n - 1
for j < k {
sum := x + nums[j] + nums[k]
if sum > 0 {
k --
} else if sum < 0 {
j ++
} else {
ans = append(ans, []int{x, nums[j], nums[k]})
j, k = j + 1, k - 1
for j < k && nums[j] == nums[j - 1] {
j ++
}
for j < k && nums[k] == nums[k + 1] {
k --
}
}
}
}
return ans
}
Python 题解
python
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
nums.sort()
ans, n = [], len(nums)
i = 0
while i < n - 2:
x = nums[i]
if i > 0 and x == nums[i - 1]:
i += 1
continue
if x + nums[i + 1] + nums[i + 2] > 0:
break
if x + nums[n - 1] + nums[n - 2] < 0:
i += 1
continue
j, k = i + 1, n - 1
while j < k:
sum = x + nums[j] + nums[k]
if sum > 0:
k -= 1
elif sum < 0:
j += 1
else:
ans.append([x, nums[j], nums[k]])
j, k = j + 1, k - 1
while j < k and nums[j] == nums[j - 1]:
j += 1
while j < k and nums[k] == nums[k + 1]:
k -= 1
i += 1
return ans