leetcode热题100(15) - 三数之和

2024年4月16日 · 281 字 · 2 分钟

Question

https://leetcode.cn/problems/3sum/?favorite=2cktkvj

给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != ji != kj != 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?

  1. 3个数字全部为0
  2. 3个数字有正数和负数

我们不妨用指针i遍历数字,在每轮循环中nums[i] 是固定的,因此我们需要找出另外的两个数,可以先将三个数相加,和有三种情况:

  1. 和等于0,此时找到了一个解
  2. 和大于0,数字太大了,我们需要将另外两个数大的那个移动一下,让他变小
  3. 和小于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), 答案所占空间一般不计入空间复杂度,本题中我们除了答案只使用了常数项的变量。