力扣15题 三数之和 双指针算法

15. 三数之和

给你一个整数数组 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 。

算法思路

这个题和两数之和十分相似, 不同的是, 题目要找的是"不重复"的三元组(元素不同), 那么我们可以在两数之和的双指针思想上进行解题:

  1. 先排序
  2. 先固定一个数, 加入这个数从左到右枚举到了i位置
  3. 然后定义两个指针, 指向i位置右边区间的两端, left = i + 1, right = nums.length - 1, 在[left, right]区间内找到两数之和为-nums[i]的二元组.
    • 这里要注意的是, 当我们固定的数是正数a时, 我们需要在右侧区间寻找两数之和为-a的二元组, 但是右侧区间的数都大于等于a, 都是正数, 所以根本不可能找到两数之和为负数的二元组, 所以当枚举到正数时, 就可以直接break.
  4. 找到了就和nums[i]组成符合题目要求的三元组

但是要注意的是, 这道题有去重操作

假设nums是这样一个数组[-4, -4, -1, 0, 0, 0, 1, 1, 4, 4, 5, 6]

如果现在到了这种情况:[-4, 0, 4]已经计入了返回的结果集里面.

力扣15题 三数之和 双指针算法_第1张图片

left++, right--后, 虽然形成的三元组依然符合题意, 但是这和上一个结果就重复了, 而题目要求不能重复. 所以, 指针移动的操作中, 要"跳过重复"的元素.

跳过符合两数之和为-num[i]的重复元素. 而那些不符合却重复的数不用管, 因为它本身也进入不到结果集.

不仅如此, 我们再看, 假如第一个-4右边的区间已经遍历完成, i++, 但是新的固定值和前一个固定值相同, 如果我们不做处理, 依然在它右边找两数之和是4的元素, 也会产生重复. 所以这里也要"跳过重复"的元素

因此

  • 找到⼀个结果之后, left right 指针要「跳过重复」的元素;
  • 当使⽤完⼀次双指针算法之后,固定的 a 也要「跳过重复」的元素。

Java代码

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        Arrays.sort(nums);
        List<List<Integer>> lists = new LinkedList<>();
        int i = 0;
        //为什么是i < nums.length - 2, 因为我们要找的是三元组
        //最后判断的一个三元组是nums[nums.length -3], nums[nums.length - 2], nums[nums.length - 1]
        while(i < nums.length - 2) {
            //如果固定的数字是正数, 那么右侧绝对不会出现两数之和为负数的二元组, 所以可以直接终止循环
            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]) {
                    right--;
                } else if(sum < -nums[i]) {
                    left++;
                } else {
                    lists.add(new LinkedList<Integer>(Arrays.asList(nums[i], nums[left], nums[right])));
                    right--;
                    left++;
                    //跳过重复元素, 注意left < right
                    while(left < right && nums[left] == nums[left - 1]) {
                        left++;
                    }
                    while(left < right && nums[right] == nums[right + 1]) {
                        right--;
                    }
                }
            }
            i++;
            //跳过重复的数字
            while (i < nums.length && nums[i] == nums[i - 1]) {
                i++;
            }
        }
        return lists;
    }
}

时间复杂度: O(N2) 空间复杂度:O(log2N)

你可能感兴趣的:(力扣算法题,算法,leetcode)