# 15. 三数之和 - 力扣题解最佳实践
访问原文链接:[15. 三数之和 - 力扣题解最佳实践](https://leetcoder.net/zh/leetcode/15-3sum),体验更佳!
力扣链接:[15. 三数之和](https://leetcode.cn/problems/3sum), 难度:**中等**。
## 力扣“15. 三数之和”问题描述
给你一个整数数组 `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`
- `-10^5 <= nums[i] <= 10^5`
### [Hints]
提示 1
So, we essentially need to find three numbers `x`, `y`, and `z` such that they add up to the given value. If we fix one of the numbers say `x`, we are left with the two-sum problem at hand!
提示 2
For the two-sum problem, if we fix one of the numbers, say `x`, we have to scan the entire array to find the next number `y`, which is `value - x` where value is the input parameter. Can we change our array somehow so that this search becomes faster?
提示 3
The second train of thought for two-sum is, without changing the array, can we use additional space somehow? Like maybe a hash map to speed up the search?
## 思路 1
1. 三个数相加等于0,等同于`两个数相加的和`等于`负的第三个数`。
2. 有两种方案可供选择:
1. 先定下一个数,然后查找另外两个数。
2. 先定下两个数,然后查找第三个数。
3. 如果选择`方案2`,需要用到`Map`。因为需要对`nums`去重复;在`Map`中查找`第三个数`时,还需要避开已经定下的`两个数`,实现起来会比较麻烦。
4. 如果选择`方案1`,查找另外两个数时,需要用到`双指针`算法。
5. 对于`方案2`,仅给出了`Python`示例代码。下文重点讲`方案1`。
## 步骤
1. 对`nums`进行排序。
2. 遍历`nums`。
3. 伪代码:
```javascript
for (i = 0; i < nums.length; i++) {
left = i + 1
right = nums.length - 1
while (left < right) {
if (condition1) {
left += 1
} else (condition2) {
right -= 1
}
}
}
```
## 复杂度
- 时间复杂度: `O(N * N)`.
- 空间复杂度: `O(N)`.
## Python
```python
# If you want the program to run faster, uncomment the two places in the code.
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
nums.sort()
# nums_2 = []
# for i, num in enumerate(nums):
# if i >= 3 and num == nums[i - 1] == nums[i - 2] == nums[i - 3]:
# continue
# nums_2.append(num)
# nums = nums_2
results = set()
for i, num in enumerate(nums[:len(nums) - 2]):
# if num > 0:
# break
left = i + 1
right = len(nums) - 1
while left < right:
sum_ = nums[left] + nums[right]
if sum_ == -num:
results.add((num, nums[left], nums[right]))
left += 1
elif sum_ > -num:
right -= 1
else:
left += 1
return list(results)
```
## Ruby
```ruby
# @param {Integer[]} nums
# @return {Integer[][]}
def three_sum(nums)
nums.sort!
results = Set.new
nums_2 = []
nums.each_with_index do |num, i|
next if i >= 3 && num == nums[i - 1] && num == nums[i - 2] && num == nums[i - 3]
nums_2.append(num)
end
nums = nums_2
# Iterate through each number as potential first element
(0...nums.length - 2).each do |i|
break if nums[i] > 0
left = i + 1
right = nums.length - 1
# Two-pointer approach for remaining elements
while left < right
current_sum = nums[i] + nums[left] + nums[right]
if current_sum == 0
# Add sorted triplet to avoid duplicates
results.add([nums[i], nums[left], nums[right]])
left += 1
right -= 1
elsif current_sum < 0
left += 1 # Need larger sum
else
right -= 1 # Need smaller sum
end
end
end
results.to_a
end
```
## Go
```go
func threeSum(nums []int) [][]int {
sort.Ints(nums)
nums2 := make([]int, 0)
for i, num := range nums {
if i >= 3 && num == nums[i-1] && nums[i-1] == nums[i-2] && nums[i-2] == nums[i-3] {
continue
}
nums2 = append(nums2, num)
}
nums = nums2
results := make([][]int, 0)
seen := make(map[string]bool)
for i := 0; i < len(nums)-2; i++ {
// if nums[i] > 0 {
// break
// }
left := i + 1
right := len(nums) - 1
for left < right {
sum := nums[left] + nums[right]
if sum == -nums[i] {
triplet := []int{nums[i], nums[left], nums[right]}
key := fmt.Sprintf("%d,%d,%d", triplet[0], triplet[1], triplet[2])
if !seen[key] {
results = append(results, triplet)
seen[key] = true
}
left++
} else if sum > -nums[i] {
right--
} else {
left++
}
}
}
return results
}
```
## C++
```cpp
class Solution {
public:
vector> threeSum(vector& nums) {
sort(nums.begin(), nums.end());
// Uncomment to speed up
// vector nums2;
// for (int i = 0; i < nums.size(); i++) {
// if (i >= 3 && nums[i] == nums[i-1] && nums[i-1] == nums[i-2] &&
// nums[i-2] == nums[i-3]) {
// continue;
// }
// nums2.push_back(nums[i]);
// }
// nums = nums2;
vector> results;
set> seen;
for (int i = 0; i < nums.size() - 2; i++) {
// Uncomment to speed up
// if (nums[i] > 0) {
// break;
// }
int left = i + 1;
int right = nums.size() - 1;
while (left < right) {
int sum = nums[left] + nums[right];
if (sum == -nums[i]) {
vector triplet = {nums[i], nums[left], nums[right]};
if (seen.find(triplet) == seen.end()) {
results.push_back(triplet);
seen.insert(triplet);
}
left++;
} else if (sum > -nums[i]) {
right--;
} else {
left++;
}
}
}
return results;
}
};
```
## JavaScript
```javascript
/**
* @param {number[]} nums
* @return {number[][]}
*/
var threeSum = function(nums) {
nums.sort((a, b) => a - b);
// Uncomment to speed up
// let nums2 = [];
// for (let i = 0; i < nums.length; i++) {
// if (i >= 3 && nums[i] === nums[i-1] && nums[i-1] === nums[i-2] && nums[i-2] === nums[i-3]) {
// continue;
// }
// nums2.push(nums[i]);
// }
// nums = nums2;
const results = [];
const seen = new Set();
for (let i = 0; i < nums.length - 2; i++) {
// Uncomment to speed up
// if (nums[i] > 0) {
// break;
// }
let left = i + 1;
let right = nums.length - 1;
while (left < right) {
const sum = nums[left] + nums[right];
if (sum === -nums[i]) {
const triplet = [nums[i], nums[left], nums[right]];
const key = triplet.join(',');
if (!seen.has(key)) {
results.push(triplet);
seen.add(key);
}
left++;
} else if (sum > -nums[i]) {
right--;
} else {
left++;
}
}
}
return results;
};
```
## C#
```csharp
public class Solution {
public IList> ThreeSum(int[] nums) {
Array.Sort(nums);
// Uncomment to speed up
// var nums2 = new List();
// for (int i = 0; i < nums.Length; i++) {
// if (i >= 3 && nums[i] == nums[i-1] && nums[i-1] == nums[i-2] && nums[i-2] == nums[i-3]) {
// continue;
// }
// nums2.Add(nums[i]);
// }
// nums = nums2.ToArray();
var results = new List>();
var seen = new HashSet();
for (int i = 0; i < nums.Length - 2; i++) {
// Uncomment to speed up
// if (nums[i] > 0) {
// break;
// }
int left = i + 1;
int right = nums.Length - 1;
while (left < right) {
int sum = nums[left] + nums[right];
if (sum == -nums[i]) {
var triplet = new List { nums[i], nums[left], nums[right] };
string key = string.Join(",", triplet);
if (!seen.Contains(key)) {
results.Add(triplet);
seen.Add(key);
}
left++;
} else if (sum > -nums[i]) {
right--;
} else {
left++;
}
}
}
return results;
}
}
```
## Java
```java
class Solution {
public List> threeSum(int[] nums) {
Arrays.sort(nums);
// Uncomment to speed up
// List nums2 = new ArrayList<>();
// for (int i = 0; i < nums.length; i++) {
// if (i >= 3 && nums[i] == nums[i-1] && nums[i-1] == nums[i-2] && nums[i-2] == nums[i-3]) {
// continue;
// }
// nums2.add(nums[i]);
// }
// nums = nums2.stream().mapToInt(i -> i).toArray();
List> results = new ArrayList<>();
var seen = new HashSet<>();
for (int i = 0; i < nums.length - 2; i++) {
// Uncomment to speed up
// if (nums[i] > 0) {
// break;
// }
int left = i + 1;
int right = nums.length - 1;
while (left < right) {
int sum = nums[left] + nums[right];
if (sum == -nums[i]) {
List triplet = Arrays.asList(nums[i], nums[left], nums[right]);
if (!seen.contains(triplet)) {
results.add(triplet);
seen.add(triplet);
}
left++;
} else if (sum > -nums[i]) {
right--;
} else {
left++;
}
}
}
return results;
}
}
```
## Other languages
```java
// Welcome to create a PR to complete the code of this language, thanks!
```
## 思路 2
请参考`题解1`的思路,本处仅给出代码,用来解释为什么使用 `Map` 不是一个好办法。
## 复杂度
- 时间复杂度: `O(N * N)`.
- 空间复杂度: `O(N)`.
## Python
```python
# from collections import defaultdict
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
nums = duplicate_removed_nums(nums)
results = set()
num_to_indices = defaultdict(list)
for i, num in enumerate(nums):
num_to_indices[num].append(i)
for i in range(len(nums) - 1):
for j in range(i + 1, len(nums)):
if -(nums[i] + nums[j]) in num_to_indices:
for index in num_to_indices[-(nums[i] + nums[j])]:
if index not in (i, j):
result = [nums[i], nums[j], nums[index]]
result.sort()
results.add(tuple(result))
return list(results)
def duplicate_removed_nums(nums):
num_to_count = defaultdict(int)
for i, num in enumerate(nums):
if num_to_count[num] <= 2 or (num_to_count[num] <= 3 and num == 0):
num_to_count[num] += 1
new_nums = []
for num in num_to_count:
new_nums.extend([num] * num_to_count[num])
return new_nums
```
## Other languages
```java
// Welcome to create a PR to complete the code of this language, thanks!
```
亲爱的力扣人,为了您更好的刷题体验,请访问 [leetcoder.net](https://leetcoder.net/zh)。
本站敢称力扣题解最佳实践,终将省你大量刷题时间!
原文链接:[15. 三数之和 - 力扣题解最佳实践](https://leetcoder.net/zh/leetcode/15-3sum).
GitHub 仓库: [f*ck-leetcode](https://github.com/fuck-leetcode/fuck-leetcode).