leetcode热题100(15) - 三数之和
2024年4月16日 · 281 字 · 2 分钟
Question
https://leetcode.cn/problems/3sum/?favorite=2cktkvj
给你一个整数数组 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] <= 105
Solution
双指针
前面我们讨论过两数之和,当时是利用Map来解决的,但是三数之后以及后面的四数之和需要换种思路去解答。三重循环的暴力解法本文不讨论,时间复杂度达到了O(N^3)
,会出现超时。
题意要求我们找出3个数字之和为0
的3个数字,什么情况下3个数字会为0?
- 3个数字全部为0
- 3个数字有正数和负数
我们不妨用指针i
遍历数字,在每轮循环中nums[i]
是固定的,因此我们需要找出另外的两个数,可以先将三个数相加,和有三种情况:
- 和等于0,此时找到了一个解
- 和大于0,数字太大了,我们需要将另外两个数大的那个移动一下,让他变小
- 和小于0,数字太小了,我们需要将另外两个数小的那个移动一下,让他变大
那么问题来了,如果才能确保两个数中大的移动之后变小或者小的移动之后变大呢?答案是排序,只有对nums排序后,最小的在左边,此时右移一定是变大;最大的在左边,此时左移一定变小。
题目还要求答案中不可以包含重复的3元组,因此排序后如果nums[i]
和nums[i+1]
相等,则我们需要跳过nums[i+1]
,对于另外两个数也是一样处理。
class Solution1 {
public List<List<Integer>> threeSum(int[] nums) {
if (nums.length == 0) {
return Collections.emptyList();
}
var answer = new ArrayList<List<Integer>>();
Arrays.sort(nums);
for (int i = 0; i < nums.length; i++) {
// 当前数字和上一个数字相同,跳过当前数字
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
int left = i + 1;
int right = nums.length - 1;
while (left < right) {
int sum = nums[i] + nums[left] + nums[right];
if (sum == 0) {
answer.add(List.of(nums[i], nums[left], nums[right]));
// left右移到不重复的
while (left < nums.length - 1 && nums[left] == nums[left + 1]) {
left++;
}
// right左移到不重复的
while (right > 0 && nums[right] == nums[right - 1]) {
right--;
}
// 此时left和right停在和当前数字相等的数字上,所以还得移动
left++;
right--;
} else if (sum > 0) { // 和太大,右指针左移
right--;
} else { // 和太小,左指针右移
left++;
}
}
}
return answer;
}
}
时间复杂度
O(n^2),n是nums长度,我们有一个for循环和2个while(2个while循环一起算,每个元素也只会访问一次),因此时间复杂度是O(n^2)而不是O(n^3)。
空间复杂度
O(1), 答案所占空间一般不计入空间复杂度,本题中我们除了答案只使用了常数项的变量。