【从零开始刷leetcode-6】 15. 三数之和

题目

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

解法

对向双指针

这种取三个数的,一般暴力解法时间复杂度为O(N3),首先目标是优化到O(N2),这种情况下,可以大胆用排序!

  • 对数组排序;
  • 要找三个数,我们先固定一个(最小的),再在后面找另外两个;
  • 设置对向双指针。计算三个数的和:
    • 和为负数,如果移动 right 指针,sum只能更小,所以移动 left 指针
    • 和为正数,移动 right 指针
    • 和为0,找到三元组,加入result
  • 注意本题需要去重,主要的思路是每次移动指针时,判断移动后的位置的数是不是与移动前一样,如果一样就继续移动。
class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        int length = nums.length;
        List<List<Integer>> result = new ArrayList<>();
        // 对数组排序
        Arrays.sort(nums);
        for(int i = 0; i < length - 2; 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) {
                    // 将最小值增大
                    left++;
                    while (nums[left] == nums[left-1] && left != right) left++;
                } else if (sum > 0) {
                    // 将最大值拉小
                    right--;
                    while (nums[right] == nums[right+1] && left != right) right--;
                } else {
                    List<Integer> list = new ArrayList<>();
                    list.add(nums[i]);
                    list.add(nums[left]);
                    list.add(nums[right]);
                    result.add(list);
                    left++;
                    while (nums[left] == nums[left-1] && left != right) left++;
                }
            }
        }
        return result;
    }
}

时间复杂度 O(N2)

细节优化

第一个确定的数一定是三元组中最小的,如果与后面剩余数组最小两个数加起来仍然大于0,就不可能在这一轮中找到。(没有再小的了)

同理,如果与后面剩余数组最大两个数加起来仍然小于0,同样不可能找到。

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        int length = nums.length;
        List<List<Integer>> result = new ArrayList<>();
        // 对数组排序
        Arrays.sort(nums);
        for(int i = 0; i < length - 2; i++) {
            if (nums[i] + nums[i + 1] + nums[i + 2] > 0) continue;
            if (nums[i] + nums[length - 1] + nums[length - 2] < 0) continue;
            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) {
                    // 将最小值增大
                    left++;
                    while (nums[left] == nums[left-1] && left != right) left++;
                } else if (sum > 0) {
                    // 将最大值拉小
                    right--;
                    while (nums[right] == nums[right+1] && left != right) right--;
                } else {
                    List<Integer> list = new ArrayList<>();
                    list.add(nums[i]);
                    list.add(nums[left]);
                    list.add(nums[right]);
                    result.add(list);
                    left++;
                    while (nums[left] == nums[left-1] && left != right) left++;
                }
            }
        }
        return result;
    }
}

优化

没必要每次移动都判断移动后的与原本的是否一样,因为目的只是去重,在找到三元组时候进行判断即可,节省判断时间。

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        int length = nums.length;
        List<List<Integer>> result = new ArrayList<>();
        // 对数组排序
        Arrays.sort(nums);
        for(int i = 0; i < length - 2; i++) {
            if (nums[i] + nums[i + 1] + nums[i + 2] > 0) break;
            if (i !=  0 && nums[i] == nums[i-1]) continue;
            if (nums[i] + nums[length - 1] + nums[length - 2] < 0) continue;
            int left = i + 1;
            int right = length - 1;
            while (left < right) {
                int sum = nums[i] + nums[left] + nums[right];
                if (sum < 0) {
                    // 将最小值增大
                    left++;
                } else if (sum > 0) {
                    // 将最大值拉小
                    right--;
                } else {
                    result.add(Arrays.asList(nums[i],nums[left],nums[right]));
                    left++;
                    right--;
                    while (left < right && nums[left] == nums[left-1]) left++;
                    while (left < right && nums[right] == nums[right+1]) right--;
                }
            }
        }
        return result;
    }
}

小结

其实和昨天的题(盛最多水的容器)中的对向双指针思路大概一致,重点都是对于left指针和right指针要移动哪一个的判断。

关键的思考是,移动哪个指针才有可能找到目标(昨天是最大的容积,今天是和为0的三元组),就移动哪个指针。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇