LeetCode15、三数之和

题目描述

在这里插入图片描述

做法

第一次做法(失败)

public List<List<Integer>> threeSum(int[] nums) {
    
    

        if(nums==null||nums.length<3){
    
    
            return null;
        }
        List<List<Integer>> lists = new ArrayList<>();
        for(int i=0;i<nums.length-3;i++){
    
    
            for(int j=i+1;j<nums.length-2&&nums[j]>=nums[i];j++){
    
    
                for(int k=j+1;k<=nums.length-1&&nums[k]>=nums[j];k++){
    
    
                    if(nums[i]+nums[j]+nums[k]==0){
    
    

                        ArrayList<Integer> arrayList = new ArrayList<>();
                        arrayList.add(nums[i]);
                        arrayList.add(nums[j]);
                        arrayList.add(nums[k]);
                        lists.add(arrayList);
                    }
                }
            }
        }
        return lists;
    }

原因:三重循环我没有进行避重。因为我觉得使用哈希表来进行避重太过于麻烦。

参考官方题解

使用三层循环需要考虑的问题是重复问题,所以我们如果用哈希表是很麻烦的,至少我认为。然后我们需要 另辟蹊径。官方题解里面提到的一个很重要的一点是:将重复的情况一点一点的降低。

  • 减少如同 (a, b, c) 这个顺序会被枚举到,而 (b, a, c)、(c, b, a) 等等这些不会。——解决办法就是使用排序。保证了第二重循环枚举到的元素不小于当前第一重循环枚举到的元素;第三重循环枚举到的元素不小于当前第二重循环枚举到的元素。

该办法之解决了所有的数都不同的情况。但是还有一些重复的情况如:-1,-1,1,1,0;
排序得到-1,-1,0,1,1.则存在的情况:第一次找到:[-1,-1,0],第二次找到:[-1,0,1]。实际上还是重复的情况 。那我们要如何避免这种情况呢? 只有和上一次枚举的元素不相同,我们才会进行枚举。

官方伪代码

nums.sort()
for first = 0 .. n-1
    // 只有和上一次枚举的元素不相同,我们才会进行枚举
    if first == 0 or nums[first] != nums[first-1] then
        for second = first+1 .. n-1
            if second == first+1 or nums[second] != nums[second-1] then
                for third = second+1 .. n-1
                    if third == second+1 or nums[third] != nums[third-1] then
                        // 判断是否有 a+b+c==0
                        check(first, second, third)


我们使用[-1,-1,-1,-1]进行分析:第一层循环遍历第一个-1,第二层遍历第二个-1,第三层遍历第三个-1,不满足和等于0。回到第一层的枚举,因为-1开头的结果枚举过,所以我们下一次枚举的不是-1,不允许是-1。同理第二层也是如此。

测试:

class Solution {
    
    
    public List<List<Integer>> threeSum(int[] nums) {
    
    
        List<List<Integer>> lists = new ArrayList<>();
        if(nums==null||nums.length<3){
    
    
            return lists;
        }
        Arrays.sort(nums);/*排序*/
        for(int i=0;i<nums.length;i++){
    
    
            if(i==0||nums[i]!=nums[i-1])
            for(int j=i+1;j<nums.length;j++){
    
    
                if(j==i+1||nums[j]!=nums[j-1])
                for(int k=j+1;k<nums.length;k++){
    
    
                    if(k==j+1||nums[k]!=nums[k-1]){
    
    
                        if(nums[i]+nums[j]+nums[k]==0){
    
    
                            ArrayList<Integer> arrayList = new ArrayList<>();
                            arrayList.add(nums[i]);
                            arrayList.add(nums[j]);
                            arrayList.add(nums[k]);
                            lists.add(arrayList);
                        }
                    }
                }
            }
        }
        return lists;
    }
}

结果超时:
在这里插入图片描述
说明时间复杂度O(N3)难以通过

官方题解再次出场:
因为我们前两重循环得到的a+b,使得存在于第三重的c是唯一满足a+b+c=0的结果。同时如果我们第二重遍历到了b的下一个数 b‘,则可知b’>b,于是存在于第三重的c‘也是唯一满足a+b’+c’=0的。但是因为b<b’,我们可以推出 c‘>c。又因为我们是排序的,所以即 c’
在数组中一定出现在 c 的左侧。也就是说,我们可以从小到大枚举 b,同时从大到小枚举 c,即第二重循环和第三重循环实际上是并列的关系。

于是得到官方伪代码

nums.sort()
for first = 0 .. n-1
    if first == 0 or nums[first] != nums[first-1] then
        // 第三重循环对应的指针
        third = n-1
        for second = first+1 .. n-1
            if second == first+1 or nums[second] != nums[second-1] then
                // 向左移动指针,直到 a+b+c 不大于 0
                while nums[first]+nums[second]+nums[third] > 0
                    third = third-1
                // 判断是否有 a+b+c==0
                check(first, second, third)

作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/3sum/solution/san-shu-zhi-he-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

这个方法就是我们常说的「双指针(盛最多的水这道题里面也出现了。)」,当我们需要枚举数组中的两个元素时,如果我们发现随着第一个元素的递增,第二个元素是递减的,那么就可以使用双指针的方法,将枚举的时间复杂度从 O(N2 ) 减少至O(N)。为什么是 O(N) 呢?这是因为在枚举的过程每一步中,「左指针」会向右移动一个位置(也就是题目中的 b),而「右指针」会向左移动若干个位置,这个与数组的元素有关,但我们知道它一共会移动的位置数为 O(N),均摊下来,每次也向左移动一个位置,因此时间复杂度为O(N)。

注意到我们的伪代码中还有第一重循环,时间复杂度为 O(N),因此枚举的总时间复杂度为 O(N2)。由于排序的时间复杂度为 O(N \log N,在渐进意义下小于前者,因此算法的总时间复杂度为 O(N2)。

作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/3sum/solution/san-shu-zhi-he-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

测试:

class Solution {
    
    
   public List<List<Integer>> threeSum(int[] nums) {
    
    
        List<List<Integer>> lists = new ArrayList<>();
        if(nums==null||nums.length<3){
    
    
            return lists;
        }
        Arrays.sort(nums);/*排序*/
        for(int i=0;i<nums.length;i++){
    
    
            if(i==0||nums[i]!=nums[i-1]){
    
    
                int k = nums.length-1;
                int target = -nums[i];
                for(int j=i+1;j<nums.length;j++){
    
    
                    if(j==i+1||nums[j]!=nums[j-1]) {
    
    
                        // 需要保证 b 的指针在 c 的指针的左侧
                        while (j < k && nums[j] + nums[k] > target) {
    
    
                            --k;
                        }
                        // 如果指针重合,随着 b 后续的增加
                        // 就不会有满足 a+b+c=0 并且 b<c 的 c 了,可以退出循环
                        if (j == k) {
    
    
                            break;
                        }
                        if (nums[j] + nums[k] == target) {
    
    
                            List<Integer> list = new ArrayList<>();
                            list.add(nums[i]);
                            list.add(nums[j]);
                            list.add(nums[k]);
                            lists.add(list);
                        }
                    }
                }
            }

        }
        return lists;
    }
}

在这里插入图片描述
复杂度:
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_44861675/article/details/108476396