LeetCode hot100系列——15. 3sum(2sum,排序+双指针)

题目描述

给定一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?找出所有满足条件且不重复的三元组。

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

例如, 给定数组 nums = [-1, 0, 1, 2, -1, -4],

满足要求的三元组集合为:

[
  [-1, 0, 1],
  [-1, -1, 2]
]

思路分析

暴力法

算法题的基本原则,先从暴力法开始,这里的3sum,用暴力法的伪代码如下

for(int i = 0; i < nums.length; i++) {
    for(int j = i + 1; j < nums.length; j++) {
        for(int k = j + 1; k < nums.length; k++) {
            if(nums[i] + nums[j] + nums[k] == 0) {
                record_trip;
            }
        }
    }
}

可以看出复杂度是

$$ O(n^3) $$

转为2sum问题

遇到3sum的时候,会想到类似的2sum问题,那个题的暴力解法也是O(n^2)的遍历,优化解法是利用hash登记+一层循环来解决。考虑到3sum也可以有类似的解题思路(这里是基于了2sum的经验)。

3sum问题考虑用两层循环+hash登记,两层循环记录两两数字的和,然后得到它们需要的数字,
遍历到每个数字的时候都去查表:

  • 如果hash里found=null,说明没有登记过,说明没有二元组需要它
  • 如果hash里found=true,说明已经成功匹配过了
  • 如果hash里found=false,说明刚好有二元组需要该元素

大体思路如上,但是为了处理去重逻辑需要做很多判断,时间复杂度O(n^2)

排序+双指针法

网上的解题思路看到的方法,既然2sum处理起去重问题比较棘手,并且复杂度也才O(n^2)时,去重-->排序是一个非常重要的思路,需要去重的时候往往排序后会比较好处理(源自于编码工作以及linux sort -nr等经验带来的直觉)
于是,先用复杂度

$$ O(logn) $$

的快排将数组排序

这时候3sum的三个数(min,mid,max)具有这样的关系:

  • 最小的数一定在最左边,其余两个数在它的右边。自然而然想到在最小数的右边最小最大位置各安插一个指针来双指针法找结果(算法的直觉,有排序属性存在,就不可能用O(n^2)去两两遍历剩下的数,自然而然想到双指针)
  • 如上图的-1和-1,当前遍历的位置-1如果发现前一个也是-1的时候,为了去重,就可以直接pass当前循环
  • 如果最小的min已经是正数了,另外两个会更大,则不可能会有结果了,直接结束方法

代码实现

class Solution {
    public List> threeSum(int[] nums) {
        List> res = new ArrayList<>();
        Arrays.sort(nums);
        for(int i = 0; i < nums.length; i++) {
            if(i > 0 && nums[i] == nums[i-1]) {
                continue; // 和前一个相等,则跳过,去重
            }
            if(nums[i] > 0) {
                return res; // nums[i]是三个数里面最小的,它都大于0了,就不可能和为零了
            }
            int require = nums[i] * -1;
            int l = i + 1;
            int r = nums.length-1;
            while(l < r) {
                // l或r如果和上一个重复了,则跳过,为了去重
                if(l - 1 > i && nums[l-1] == nums[l]) {
                    l++;
                    continue;
                }
                if(r + 1 < nums.length && nums[r+1] == nums[r]) {
                    r--;
                    continue;
                }
                // 和require比较,并且调整l和r
                if(nums[l] + nums[r] < require) {
                    l++;
                    continue;
                }
                if(nums[l] + nums[r] > require) {
                    r--;
                    continue;
                }
                if(nums[l] + nums[r] == require) {
                    List list = Arrays.asList(nums[i], nums[l], nums[r]);
                    res.add(list);
                    l++;
                    r--;
                    continue;
                }
            }
        }
        return res;
    }
}

总结

先用暴力法保底,2sum其实是最容易想到优化方法,但是处理去重问题逻辑太麻烦,
因为去重而想到了排序,进而用到了双指针

算法题有时需要根据过往刷题的经验->直觉来解决问题的,例如去重想到排序,排序后找mid和max用到双指针

你可能感兴趣的:(leetcode)