题目来源
题目描述
给你一个整数数组 nums
,判断是否存在三元组 [nums[i], nums[j], nums[k]]
满足 i != j
、i != k
且 j != k
,同时还满足 nums[i] + nums[j] + nums[k] == 0
。请你返回所有和为 0
且不重复的三元组。
注意:答案中不可以包含重复的三元组。
示例 1
示例 2
示例 3
提示
- 3 <= nums.length <= 3000
- -10^5 <= nums[i] <= 10^5
题目解析
本题我们可以将 nums 进行升序
选取三元组时,我们可以固定 i 索引位置,然后初始 j = i + 1,k = nums.length - 1,如下图所示:
然后计算此时三元组之和:sum = nums[i] + nums[j] + nums[k]
- 若 sum > 0,则说明三元组之和偏大了,此时我们应该 k--,这样的话,三元组之和就会减小
- 若 sum < 0,则说明三元组之和偏小了,此时我们应该 j++,这样的话,三元组之和就会增大
- 若 sum == 0,则说明此时三元组是符合要求的,我们记录此时的三元组,然后 k-- ,j++
对于 nums = [-4, -1, -1, 0, 1, 2],按照上面逻辑演示,过程如下:
本题需要我们求解不重复的三元组,但是我们通过上图发现,存在重复的三元组 [-1, 0, 1],而造成重复的原因是 nums[1] == nums[2]。
如果 i = 1,那么可以产生的三元组(索引)有:
- [i=1, j=2, k=5]
- [i=1, j=2, k=4]
- [i=1, j=2, k=3]
- [i=1, j=3, k=5]
- [i=1, j=3, k=4]
- [i=1, j=4, k=5]
如果 i = 2,那么可以产生的三元组(索引)有:
- [i=2, j=3, k=5]
- [i=2, j=3, k=4]
- [i=2, j=4, k=5]
那么,nums[1] == nums[2] 的话,那么 i = 1可以产生的三元组是可以完全覆盖 i = 2 产生的三元组的(三元组各元素值相同)。
因此当 nums[i] == nums[i-1] 时,我们就可以跳过 nums[i] 产生三元组的过程,因为必然和 nums[i-1] 产生的三元组发生重复。
除了上面情况会产生重复三元组外,还有一种情况,比如 nums = [-2, 0, 0, 2, 2],如下图所示,会产生重复三元组 [-2, 0, 2]
造成重复的原因是:
- 如果 j++ 后,nums[j] == nums[j - 1],则会产生重复三元组
- 如果 k-- 后,nums[k] == nums[k+1],则会产生重复三元组
为了避免产生重复三元组,我们可以在 j++ 或 k-- 过程中,跳过相同元素。
C源码实现
C语言这题比较难以理解是函数参数 int* returnSize 和 int** returnColumnSizes。
我们返回的结果是一个 int** 类型的,主要记录不重复的三元组,比如 [[-1,-1,2], [-1,0,1]],可以发现返回值本质是一个二维数组。
而 returnSize 记录的其实就是返回值二维数组的行数,returnColumnSizes记录的是返回值二维数组每行的列数。
比如对于返回值 [[-1,-1,2], [-1,0,1]] 来说,returnSize = 2,returnColumnSizes = [3, 3]
而本题 returnSize 和 returnColumnSizes 参数类型都是指针,即调用 threeSum 函数时,传递的时 returnSize 和 returnColumnSizes 的指针(地址)。大家可以对照下面代码注释中 main 函数调用 threeSum函数 时的传参来理解。
#include <stdio.h>
#include <stdlib.h>
/**
* Return an array of arrays of size *returnSize.
* The sizes of the arrays are returned as *returnColumnSizes array.
* Note: Both returned array and *columnSizes array must be malloced, assume
* caller calls free().
*/
int cmp(const void *a, const void *b) { return *(int *) a - *(int *) b; }
int **threeSum(int *nums, int numsSize, int *returnSize, int **returnColumnSizes) {
// 记录结果
int **res = (int **) malloc(sizeof(int *) * 20000);
int res_size = 0;
// nums升序
qsort(nums, numsSize, sizeof(nums[0]), cmp);
for (int i = 0; i < numsSize - 2; i++) { // i作为三元组中最小元素的索引位置
if (nums[i] > 0) {
// 由于nums已经升序,因此nums[i]>0时, 而 nums[r] >= nums[l] >= nums[i], 因此之后三数之和必然大于0
break;
}
if (i > 0 && nums[i] == nums[i - 1]) {
// 去重
continue;
}
// 双指针
int l = i + 1;
int r = numsSize - 1;
while (l < r) {
// 三数之和
int sum = nums[i] + nums[l] + nums[r];
if (sum > 0) {
// r左移, 三数之和减少
r--;
} else if (sum < 0) {
// l右移, 三数之和增加
l++;
} else {
// 三数之和为0时, 此时三元组是符合题目要求的
res[res_size++] = (int *) malloc(sizeof(int) * 3);
res[res_size - 1][0] = nums[i];
res[res_size - 1][1] = nums[l];
res[res_size - 1][2] = nums[r];
// 去重
do {
l++;
} while (l < r && nums[l] == nums[l - 1]);
// 去重
do {
r--;
} while (r > l && nums[r] == nums[r + 1]);
}
}
}
*returnSize = res_size; // returnSize 记录 res 行数, 本质是一个int值, 只是参数传递时, 取地址变为了 int* 类型
*returnColumnSizes = (int *) malloc(sizeof(int) * res_size); // returnColumnSizes 记录 res 每一行的列数, 本质是一个int一维数组, 只是参数传递时, 取地址变为了 int** 类型
for (int i = 0; i < res_size; i++) {
(*returnColumnSizes)[i] = 3;
}
return res;
}
//int main() {
// int nums[] = {-1, 0, 1, 2, -1, -4};
// int numSize = 6;
//
// int returnSize; // 记录结果行数
// int *returnColumnSizes; // 记录结果的每一行列数
//
// // int returnSize 取地址后变为 int* 类型
// // int* returnColumnSizes 取地址后变为 int** 类型
// int **res = threeSum(nums, numSize, &returnSize, &returnColumnSizes);
//
// for (int i = 0; i < returnSize; i++) {
// printf("[");
// for (int j = 0; j < returnColumnSizes[i]; j++) {
// printf("%d", res[i][j]);
//
// if (j < returnColumnSizes[i] - 1) {
// printf(",");
// }
// }
// printf("]\n");
// }
//
//
// return 0;
//}
C++源码实现
class Solution {
public:
vector<vector<int>> threeSum(vector<int> &nums) {
vector<vector<int>> res;
// nums升序
sort(nums.begin(), nums.end());
for (int i = 0; i < nums.size() - 2; i++) { // i作为三元组中最小元素的索引位置
if (nums[i] > 0) {
// 由于nums已经升序,因此nums[i]>0时, 而 nums[r] >= nums[l] >= nums[i], 因此之后三数之和必然大于0
break;
}
if (i > 0 && nums[i] == nums[i - 1]) {
// 去重
continue;
}
// 双指针
int l = i + 1;
int r = nums.size() - 1;
while (l < r) {
// 三数之和
int sum = nums[i] + nums[l] + nums[r];
if (sum > 0) {
// r左移, 三数之和减少
r--;
} else if (sum < 0) {
// l右移, 三数之和增加
l++;
} else {
// 三数之和为0时, 此时三元组是符合题目要求的
res.emplace_back(vector<int>{nums[i], nums[l], nums[r]});
// 去重
do {
l++;
} while (l < r && nums[l] == nums[l - 1]);
// 去重
do {
r--;
} while (r > l && nums[r] == nums[r + 1]);
}
}
}
return res;
}
};
Java源码实现
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> lists = new ArrayList<>();
// nums升序
Arrays.sort(nums);
for (int i = 0; i < nums.length - 2; i++) {
if (nums[i] > 0) {
// 由于nums已经升序,因此nums[i]>0时, 而 nums[r] >= nums[l] >= nums[i], 因此之后三数之和必然大于0
break;
}
if (i > 0 && nums[i] == nums[i - 1]) {
// 去重
continue;
}
// 双指针
int l = i + 1;
int r = nums.length - 1;
while (l < r) {
// 三数之和
int sum = nums[i] + nums[l] + nums[r];
if (sum > 0) {
// r左移, 三数之和减少
r--;
} else if (sum < 0) {
// l右移, 三数之和增加
l++;
} else {
// 三数之和为0时, 此时三元组是符合题目要求的
ArrayList<Integer> list = new ArrayList<>();
Collections.addAll(list, nums[i], nums[l], nums[r]);
lists.add(list);
// 去重
do {
r--;
} while (r > l && nums[r] == nums[r + 1]);
// 去重
do {
l++;
} while (l < r && nums[l] == nums[l - 1]);
}
}
}
return lists;
}
}
Python源码实现
class Solution(object):
def threeSum(self, nums):
"""
:type nums: List[int]
:rtype: List[List[int]]
"""
# nums升序
nums.sort()
res = []
for i in range(len(nums) - 2): # i作为三元组中最小元素的索引位置
if nums[i] > 0:
# 由于nums已经升序,因此nums[i]>0时, 而 nums[r] >= nums[l] >= nums[i], 因此之后三数之和必然大于0
break
if i > 0 and nums[i] == nums[i - 1]:
# 去重
continue
# 双指针
l = i + 1
r = len(nums) - 1
while l < r:
# 三数之和
total = nums[i] + nums[l] + nums[r]
if total > 0:
# r左移, 三数之和减少
r -= 1
elif total < 0:
# l右移, 三数之和增加
l += 1
else:
# 三数之和为0时, 此时三元组是符合题目要求的
res.append((nums[i], nums[l], nums[r]))
l += 1
r -= 1
# 去重
while l < r and nums[l] == nums[l - 1]:
l += 1
continue
# 去重
while r > l and nums[r] == nums[r + 1]:
r -= 1
continue
return res
JavaScript源码实现
/**
* @param {number[]} nums
* @return {number[][]}
*/
var threeSum = function (nums) {
// nums升序
nums.sort((a, b) => a - b);
const res = [];
for (let i = 0; i < nums.length - 2; i++) {
// i作为三元组中最小元素的索引位置
if (nums[i] > 0) {
// 由于nums已经升序,因此nums[i]>0时, 而 nums[r] >= nums[l] >= nums[i], 因此之后三数之和必然大于0
break;
}
if (i > 0 && nums[i] == nums[i - 1]) {
// 去重
continue;
}
// 双指针
let l = i + 1;
let r = nums.length - 1;
while (l < r) {
// 三数之和
const sum = nums[i] + nums[l] + nums[r];
if (sum > 0) {
// r左移, 三数之和减少
r--;
} else if (sum < 0) {
// l右移, 三数之和增加
l++;
} else {
// 三数之和为0时, 此时三元组是符合题目要求的
res.push([nums[i], nums[l], nums[r]]);
// 去重
do {
l++;
} while (l < r && nums[l] == nums[l - 1]);
// 去重
do {
r--;
} while (r > l && nums[r] == nums[r + 1]);
}
}
}
return res;
};