LeetCode - Hot 100 - 三数之和

3 阅读3分钟

📌 题目描述

给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != ji != kj != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请你返回所有和为 0 且不重复的三元组。

注意: 答案中不可以包含重复的三元组。

💡 示例:

输入: 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] 。 注意,输出的顺序和三元组的顺序并不重要。

🧠 思路分析

我们在拿到这道题时,最直观的联想就是它的前置问题—— “两数之和”

思路1:

我们先固定一个数 i ,所求的target可以转变为 0 - nums[i]。对i之后的数组使用我们的两数之和即可。但是运行用例后发现我们计算出来居然有重复的答案,虽然位置不同但这是不行的。

例如[[-1,1,0],[0,-1,1]]

解决方案:

我们可以使用HashSet包裹住我们的排序后的List,利用set进行一个去重,但这种方式耗时很高。下面给出代码:

💻 核心代码实现

class Solution1 {

    HashSet<List<Integer>> res;//去重HashSet
    public List<List<Integer>> threeSum(int[] nums) {

        res = new HashSet<>();
        for (int i = 0; i < nums.length; i++) {
            calTwoWordSum(i,nums);
        }
        return new ArrayList<>(res);// 将HashSet转为List返回结果

    }

    // 计算两数之和
    public void calTwoWordSum(int start,int[] nums){

        int target = 0 - nums[start];//新的Target

        HashMap<Integer,Integer> cache = new HashMap<>();// 创建HashMap保存数据
        for (int i = start+1; i < nums.length; i++) {
            int nTarget = target - nums[i];
            if(cache.containsKey(nTarget)){ 
                List<Integer> list = new ArrayList<>();
                list.add(nums[start]);
                list.add(nums[i]);
                list.add(nTarget);
                Collections.sort(list);// 对list进行排序后放到HashSet中
                res.add(list);
            }
            cache.put(nums[i],i);
        }

    }
}

思路2:

使用排序+双指针的形式

(这里建议一下大家,遇到排序好的数组问题,可以第一时间考虑二分法或左右双指针的方式,看看能不能求解)

  • 先对nums进行排序
  • 进行外层数字 i 的遍历
    • 如果当前 nums[i] > 0 直接break,因为可以保证后续的节点无法实现 三数之和 == 0
    • 如果当前 nums[i] == nums[i-1]直接continue,有效的去重
  • 对剩余的数组进行左右双指针 nTarget = -num[i]
    • 如果nums[left]+num[right] == nTarget,得到我们的值。再分别对左右指针进行去重判断后 left++,right --;
    • 如果nums[left]+num[right] < nTarget, left++ (因为比目标值小,所有右移左指针)
    • 如果nums[left]+num[right] > nTarget, right++(因为比目标值大,所有左移右指针)

💻 核心代码实现

class Solution {
    List<List<Integer>> res; // 结果res
    public List<List<Integer>> threeSum(int[] nums) {


        res = new ArrayList<>();
        Arrays.sort(nums); // 排序数组
        for (int i = 0; i < nums.length -2; i++) {
            if( nums[i] > 0){ // 减枝操作
                break;
            }
            if(i > 0 && nums[i] == nums[i-1]){
                continue; // 去重操作
            }
            calTwoWordSum(i,nums);
        }

        return res;

    }

    // 计算两数之和
    public void calTwoWordSum(int start,int[] nums){

        int target = 0 - nums[start];
        int left = start + 1;
        int right = nums.length -1;

        while (left < right){

            int sum = nums[left] + nums[right];
            if(sum == target){
                res.add(Arrays.asList(nums[start],nums[left],nums[right]));
                //这一步很关键,是内部的去重,如果下一个数字相同,直接移动左右指针
                while (left < right && nums[left] == nums[left+1]){
                    left++;
                }
                while (left < right && nums[right] == nums[right-1]){
                    right--;
                }
                //确认去重完后同时移动左右指针
                left++;
                right--;
            }else if(sum > target){
                right--;
            }else {
                left ++;
            }

        }

    }
}