一、问题描述:三数之和
给你一个整数数组 nums
,判断是否存在三元组 [nums[i], nums[j], nums[k]]
满足 i != j
、i != k
且 j != k
,同时还满足 nums[i] + nums[j] + nums[k] == 0
。请你返回所有和为 0
且不重复的三元组。
注意:答案中不可以包含重复的三元组。
示例 1:
输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
解释:
nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。
nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。
nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。
不同的三元组是 [-1,0,1] 和 [-1,-1,2] 。
注意,输出的顺序和三元组的顺序并不重要。
示例 2:
输入:nums = [0,1,1]
输出:[]
解释:唯一可能的三元组和不为 0 。
示例 3:
输入:nums = [0,0,0]
输出:[[0,0,0]]
解释:唯一可能的三元组和为 0 。
提示:
3 <= nums.length <= 3000
-105 <= nums[i] <=
二、实现方案
方案一:双指针法
方法思路:
- 排序数组:首先将数组排序,以便于后续处理重复元素和使用双指针法。
- 固定第一个元素:遍历数组中的每个元素作为三元组的第一个元素。
- 双指针查找:对于每个固定的第一个元素,使用双指针在剩余部分中查找另外两个元素,使得三数之和为 0。
- 跳过重复元素:在遍历和查找过程中,跳过重复的元素以避免生成重复的三元组。
复杂度:
- 时间复杂度为 O(n²),
- 空间复杂度为 O(1)(不考虑排序的空间),是解决此问题的最优方案。
代码实现:
- Python
def threeSum(nums):
nums.sort()
res = []
n = len(nums)
for i in range(n - 2):
# 跳过重复的第一个元素
if i > 0 and nums[i] == nums[i - 1]:
continue
left, right = i + 1, n - 1
while left < right:
total = nums[i] + nums[left] + nums[right]
if total < 0:
left += 1
elif total > 0:
right -= 1
else:
res.append([nums[i], nums[left], nums[right]])
# 跳过左侧重复元素
while left < right and nums[left] == nums[left + 1]:
left += 1
# 跳过右侧重复元素
while left < right and nums[right] == nums[right - 1]:
right -= 1
# 移动指针以寻找下一个可能的三元组
left += 1
right -= 1
return res
- php:
function threeSum($nums) {
sort($nums); // 将数组排序
$result = array();
$n = count($nums);
for ($i = 0; $i < $n - 2; $i++) {
// 跳过重复的起始元素
if ($i > 0 && $nums[$i] == $nums[$i - 1]) {
continue;
}
$left = $i + 1;
$right = $n - 1;
$target = -$nums[$i]; // 转化为两数之和问题:nums[left] + nums[right] = target
while ($left < $right) {
$sum = $nums[$left] + $nums[$right];
if ($sum == $target) {
// 找到有效组合,加入结果
array_push($result, array($nums[$i], $nums[$left], $nums[$right]));
// 跳过左侧重复元素
while ($left < $right && $nums[$left] == $nums[$left + 1]) {
$left++;
}
// 跳过右侧重复元素
while ($left < $right && $nums[$right] == $nums[$right - 1]) {
$right--;
}
// 移动指针寻找下一个可能
$left++;
$right--;
} elseif ($sum < $target) {
$left++; // 和太小,左指针右移
} else {
$right--; // 和太大,右指针左移
}
}
}
return $result;
}
- golang:
package main
import (
"fmt"
"sort"
)
func threeSum(nums []int) [][]int {
sort.Ints(nums) // 先排序
var result [][]int
n := len(nums)
for i := 0; i < n-2; i++ {
// 跳过重复的第一个元素
if i > 0 && nums[i] == nums[i-1] {
continue
}
left, right := i+1, n-1
target := -nums[i] // 转化为两数之和问题
for left < right {
sum := nums[left] + nums[right]
if sum == target {
// 找到有效组合
result = append(result, []int{nums[i], nums[left], nums[right]})
// 跳过左侧重复元素
for left < right && nums[left] == nums[left+1] {
left++
}
// 跳过右侧重复元素
for left < right && nums[right] == nums[right-1] {
right--
}
// 移动指针寻找下一个可能
left++
right--
} else if sum < target {
left++ // 和太小,左指针右移
} else {
right-- // 和太大,右指针左移
}
}
}
return result
}
## 测试实例
func main() {
// 示例 1
nums1 := []int{-1, 0, 1, 2, -1, -4}
fmt.Println(threeSum(nums1)) // 输出:[[-1 -1 2] [-1 0 1]]
// 示例 2
nums2 := []int{0, 1, 1}
fmt.Println(threeSum(nums2)) // 输出:[]
// 示例 3
nums3 := []int{0, 0, 0}
fmt.Println(threeSum(nums3)) // 输出:[[0 0 0]]
}
方案二:
暴力法(三重循环)
复杂度:
- 时间复杂度:O(n³),适用于极小的数据量,但在题目约束下会超时。
代码实现:
- Python:
def threeSumBruteForce(nums):
res = []
nums.sort()
n = len(nums)
for i in range(n-2):
if i > 0 and nums[i] == nums[i-1]:
continue
for j in range(i+1, n-1):
if j > i+1 and nums[j] == nums[j-1]:
continue
for k in range(j+1, n):
if k > j+1 and nums[k] == nums[k-1]:
continue
if nums[i] + nums[j] + nums[k] == 0:
res.append([nums[i], nums[j], nums[k]])
return res
- PHP:
function threeSumBruteForce($nums) {
sort($nums);
$result = array();
$n = count($nums);
for ($i = 0; $i < $n - 2; $i++) {
if ($i > 0 && $nums[$i] == $nums[$i-1]) continue;
for ($j = $i+1; $j < $n - 1; $j++) {
if ($j > $i+1 && $nums[$j] == $nums[$j-1]) continue;
for ($k = $j+1; $k < $n; $k++) {
if ($k > $j+1 && $nums[$k] == $nums[$k-1]) continue;
if ($nums[$i] + $nums[$j] + $nums[$k] == 0) {
array_push($result, [$nums[$i], $nums[$j], $nums[$k]]);
}
}
}
}
return $result;
}
- Golang:
func threeSumBruteForce(nums []int) [][]int {
sort.Ints(nums)
var result [][]int
n := len(nums)
for i := 0; i < n-2; i++ {
if i > 0 && nums[i] == nums[i-1] {
continue
}
for j := i + 1; j < n-1; j++ {
if j > i+1 && nums[j] == nums[j-1] {
continue
}
for k := j + 1; k < n; k++ {
if k > j+1 && nums[k] == nums[k-1] {
continue
}
if nums[i]+nums[j]+nums[k] == 0 {
result = append(result, []int{nums[i], nums[j], nums[k]})
}
}
}
}
return result
}
方案三:哈希表法:
复杂度:
- 时间复杂度:O(n²),但哈希表操作常数较大,实际效率可能不如双指针法。
代码示例:
- python:
def threeSumHash(nums):
res = []
nums.sort()
n = len(nums)
for i in range(n - 2):
if i > 0 and nums[i] == nums[i - 1]:
continue
seen = set()
target = -nums[i]
j = i + 1
while j < n:
complement = target - nums[j]
if complement in seen:
res.append([nums[i], complement, nums[j]])
while j + 1 < n and nums[j] == nums[j + 1]:
j += 1
seen.add(nums[j])
j += 1
return res
- php:
function threeSumHash($nums) {
sort($nums);
$result = array();
$n = count($nums);
for ($i = 0; $i < $n - 2; $i++) {
if ($i > 0 && $nums[$i] == $nums[$i-1]) continue;
$seen = array();
$target = -$nums[$i];
for ($j = $i + 1; $j < $n; $j++) {
$complement = $target - $nums[$j];
if (in_array($complement, $seen)) {
array_push($result, [$nums[$i], $complement, $nums[$j]]);
while ($j + 1 < $n && $nums[$j] == $nums[$j+1]) $j++;
}
array_push($seen, $nums[$j]);
}
}
return $result;
}
- Golang:
func threeSumHash(nums []int) [][]int {
sort.Ints(nums)
var result [][]int
n := len(nums)
for i := 0; i < n-2; i++ {
if i > 0 && nums[i] == nums[i-1] {
continue
}
seen := make(map[int]bool)
target := -nums[i]
for j := i + 1; j < n; j++ {
complement := target - nums[j]
if seen[complement] {
result = append(result, []int{nums[i], complement, nums[j]})
// 跳过重复的j
for j+1 < n && nums[j] == nums[j+1] {
j++
}
}
seen[nums[j]] = true
}
}
return result
}
三、效率对比
- 双指针法:最优解,时间复杂度 O(n²),空间复杂度 O(1)(排序的辅助空间可忽略),适用于大规模数据。
- 哈希表法:同样时间复杂度 O(n²),但实际运行效率较低,且处理重复元素逻辑复杂。
- 暴力法:仅适用于极小数据量,实际不可行。