15. 三数之和

题目描述

思路 and Swift 题解
同样是经典的双指针问题。首先,我们需要对输入的数组排序,由于数组的类型是[Int]
,它遵循Sequence
协议,因此直接使用内置的sorted()
方法即可。
需要注意的是,在 Swift 当中,所有函数的参数默认都是常量 ,除非显式指定形参的类型为inout
,所以此处我们修改形参名为anums
,并使用var nums = anums.sorted()
来获取排序后的数组。
为什么需要进行排序?因为我们需要在有序序列上使用双指针。首先,我们需要对排序后的数组进行遍历,由于我们求的是三数之和,理想情况下一个可能的答案是数组的最后三个数字,后两个数需要使用双指针来找到,因此遍历时,我们只需要遍历到nums.count - 3
这个位置即可。此处需要引出一个 Swift for 循环当中的一个关键知识点,那就是如何对数组进行"范围遍历"。在 Swift 3.0+ 当中已经废弃了 C 风格的循环,也就是不能使用for (_; _; _)
来进行遍历,而应该使用for i in 0..<nums.count - 2
进行遍历。
在 Swift 当中,a...b
(三个点)标识了一个闭区间,如果使用a..<b
(两个点,第三个点被<
替代),那就是一个左闭右开区间(此处的 a 和 b 都是整型)。以1...3
为例,使用var range = 1...3
,得到的将是一个Range<Int>
类型的变量,它不是数组,而是一个区间,但可以使用Array(range)
或Array(1..3)
转为数组。除了在 for 循环当中作为循环边界之外,数组的切片当中也可以使用 Range。
现在回到这个问题上,我们对nums
进行循环,每次取当前循环的数值nums[i]
为x
。取j
和k
为i + 1
和n - 1
,在[i + 1, n - 1]
这个范围内,我们需要寻找三个数值,满足x + nums[j] + nums[k] == 0
。令sum = x + nums[j] + nums[k]
,如果sum > 0
,说明右侧(数组已经升序排列了)的数值太大了,需要把右指针它向左调整以减小右侧的数值(k -= 1
);反之如果sum < 0
,则说明左侧的数值太小,左指针需要向右调整(j += 1
)。如果sum == 0
,说明此时找到了答案,记录[x, nums[j], nums[k]]
到答案当中,并令j += 1; k -=1
,继续寻找可能的答案(注意,可能会出现nums[j] == nums[j - 1]
和nums[k] == nums[k + 1]
,因此需要去重)。
完整的 Swift 题解如下,其中额外考虑了一些边界情况,一些是用作优化的,一些是用作找到答案后进行去重的:
swift
class Solution {
func threeSum(_ anums: [Int]) -> [[Int]] {
var nums = anums.sorted()
var n = nums.count
var ans: [[Int]] = []
for i in 0..<n - 2 {
var 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
}
var j = i + 1, k = n - 1
while j < k {
var sum = x + nums[j] + nums[k]
if sum > 0 {
k -= 1
} else if sum < 0 {
j += 1
} else {
ans.append([x, nums[j], nums[k]])
j += 1; k -= 1
while j < k && nums[j] == nums[j - 1] {
j += 1
}
while j < k && nums[k] == nums[k + 1] {
k -= 1
}
}
}
}
return ans
}
}