题目

1
2
3
4
5
给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请

你返回所有和为 0 且不重复的三元组。

注意:答案中不可以包含重复的三元组。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
示例 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] <= 105

暴力解法

最简单的往往都是暴力解法,三个数 就三重循环, 比较简单,直接上代码

三重循环时间复杂度是 O(N3)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// ThreeNumsBaoli 暴力之美
func ThreeNumsBaoli(nums []int) [][]int {
res := make([][]int, 0)
repMap := make(map[string]struct{})
for index := 0; index < len(nums); index += 1 { // 固定第一个数
for j := index + 1; j < len(nums); j += 1 { // 固定第二个不重复的数
for k := j + 1; k < len(nums); k += 1 { // 第三个不重复的数
if nums[index]+nums[j]+nums[k] == 0 {
unVal := getUniqueVal([3]int{nums[index], nums[j], nums[k]})
if _, ok := repMap[unVal]; ok {
continue
}
repMap[unVal] = struct{}{}
res = append(res, []int{nums[index], nums[j], nums[k]})
}
}
}
}

return res
}

func getUniqueVal(nums [3]int) string { // 这里是为了去重复
for index := 1; index < len(nums); index += 1 {
t := nums[index]
j := index - 1
for ; j >= 0; j-- {
if nums[j] < t {
break
}
nums[j+1] = nums[j]
}
// 插入j后面
nums[j+1] = t
}
return fmt.Sprintf("%d,%d,%d", nums[0], nums[1], nums[2])
}

双指针法

这里既然三个数之和为0, 要么三个数都是0, 必须有一个负数,这里是不是可以考虑先将数组排序,固定一个数, 该数后面使用,头尾双指针,将三个指针位置的数相加,如果小于0, 那将左边的指针右移,这里为啥? 直接就右移,因为数组有序,右边指针已经是最大的数了,只能增加最小的数,才可能等于0。应该很好理解。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
func ThreeNumRep(nums []int) [][]int {
sort.Ints(nums)
res := make([][]int, 0)
for index := 0; index < len(nums) && nums[index] <= 0; index += 1 { // 固定第一个数
if index > 0 && nums[index] == nums[index-1] { // 第一个数重复的忽略
continue
}

left, right := index+1, len(nums)-1
for left < right { // 后面两个数
s := nums[index] + nums[left] + nums[right]
if s > 0 {
right -= 1
} else if s < 0 {
left += 1
} else if s == 0 { // 等于0 要注意 1. 左右指针要都移动
res = append(res, []int{nums[index], nums[left], nums[right]})
left += 1
right -= 1 // 先移动 左右指针,避免出现下面两个条件都不满足的情况, 当时踩坑了,智商堪忧
for left < right && nums[left-1] == nums[left] { // 这里是把重复的数 直接忽略掉
left += 1
}
for left < right && nums[right+1] == nums[right] {
right -= 1
}
}
}
}
return res
}

代码实现的是思路,思路有了代码实现 还搞半天,这是编码能力的问题吗? 写过之后 再来看这个思路有,代码又要搞半天,我理解应该是见的少了。