LeetCode-15-三数之和

99封情书 提交于 2020-01-30 19:44:31

题意描述:

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

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


示例:

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

满足要求的三元组集合为:
[
  [-1, 0, 1],
  [-1, -1, 2]
]

解题思路:

Alice: 这个三重循环肯定会超时吧。
Bob: 那肯定的,O(n^2) 都够呛,而且去重应该没那么简单。如果先把所有的答案都求出来,然后再去重可能会不行哦。求解的时候有重复计算,去重的时候还要再附加计算,可能会超时啊。
Alice: emmm, 有数组不能先排个序吗,然后再分析看看,要求 a + b + c == 0那肯定得有正有负吧。
Bob: 不一定哦,万一是 三个零 [0, 0, 0] 呢。
Alice: 那就除了 三个零的情况,至少得有一个正数,至少得有一个负数。
Bob: 我们可以在外层遍历整个排序后的数组, 然后指定最外层遍历的是三元组中的最小值,这样就只遍历 负数 的部分就可以了。
Alice: 对对对,如果三元组中最小值是正数这样的三元组是不成立的,如果最小值是零,刚才已经处理过了。
Bob: 然后我们用双指针来 寻找 三元组中的 中间值和 最大值, 中间值一定要小于等于最大值。
Alice: 也就是说 两个指针 left < right 是一定成立的,然后还有去重的问题,无论是最小值,中间值,最大值的都要去重。
Bob: 对,最小值用 pre 记录上一次访问的值,然后比较呗,如果再遇到就跳过呗。
Alice: 然后就是 leftright, 如果 nums[minIndex] + nums[left] + nums[right] == 0,然后leftright 要向中间移动,一直移动到 两个新的元素为止。
Bob: 如果 nums[minIndex] + nums[left] + nums[right] < 0left 右移,否则 right 左移,应该就可以了。
Alice; 那应该就没错了,最小值,中间值,最大值的去重都考虑了,时间复杂度是O(n^2),空间复杂度应该是O(1),还不错。
Bob: 😎😎


代码:

Python 方法一: 排序 + 双指针。

class Solution:
    def threeSum(self, nums: List[int]) -> List[List[int]]:

        ans = []
        if len(nums) < 3:
            return ans
        else:
            nums.sort()
            
            positiveIndex = -1
            zeroCnt       = 0
            for x in range(len(nums)):
                if nums[x] == 0:
                    zeroCnt += 1
                elif nums[x] > 0:
                    positiveIndex = x
                    break

            #print(nums)
            #print(zeroCnt)

            if positiveIndex == -1:
                # 没有正数
                if zeroCnt >= 3:
                    return [[0,0,0]]
                else:
                    return []

            pre = nums[0] + 1
            # pre 初始值不等于 nums[0] 即可
            for x in range(len(nums)):
                # 遍历 所有可能的三元组中最小的那一个
                if nums[x] > 0:
                    # nums[x] > 0, x 后的所有数字都是正数
                    continue
                elif nums[x] < 0:
                    if nums[x] == pre:
                        # 去重, 两个最小值
                        continue
                    else:
                        # 寻找满足条件的三元组
                        pre = nums[x]
                        positiveOne = len(nums)-1
                        # positiveOne 是三元组中最大的 元素的下标
                        anotherOne = x + 1

                        while anotherOne < positiveOne:
                            if nums[x] + nums[anotherOne] + nums[positiveOne] == 0:
                                ans.append([nums[x], nums[anotherOne], nums[positiveOne]])

                                # 去重 
                                while positiveOne > anotherOne and nums[positiveOne] == nums[positiveOne-1]:
                                    positiveOne -= 1
                                while anotherOne < positiveOne and nums[anotherOne] == nums[anotherOne+1]:
                                    anotherOne += 1
                                
                                # 尝试新的答案
                                positiveOne -= 1
                                anotherOne  += 1
                            elif nums[x] + nums[anotherOne] + nums[positiveOne] < 0:
                                anotherOne += 1
                            else:
                                positiveOne -= 1

                else:
                    # nums[x] == 0, 且 0 是三元组中的最小值
                    if zeroCnt >= 3:
                        # 最后一组三元组
                        ans.append([0,0,0])    
                    break

            return ans

Java 方法一: 排序 + 双指针。

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

        List<List<Integer>> ans = new ArrayList();   // ans 存储答案

        if(nums.length < 3){                         // 不可能有满足条件的三元组,直接返回
            return ans;
        }else{

            Arrays.sort(nums);                        // 排个序
            int zeroCnt = 0;
            int firstPositiveIndex = -1;              // 统计下数组中零的个数 和 第一个正数出现的下标
            for(int i=0; i<nums.length; ++i){
                if(firstPositiveIndex == -1 && nums[i] > 0){
                    firstPositiveIndex = i;
                    break;
                }
                if(nums[i] == 0){
                    zeroCnt += 1;
                }
            }

            if(firstPositiveIndex == -1){               // 一个正数也没有就可以直接返回了
                // nothing, 直接到最后加零走人
            }else{
                int pre = nums[0] + 1;                  // 去重用,pre 初始值和 nums[0] 不同即可
                for(int minIndex = 0; minIndex < nums.length; ++minIndex){   // 以三元组中最小的元素开始搜索
                    if(nums[minIndex] >= 0){
                        break;                            // 后面是没有答案的,可以直接退出循环了
                    }else{
                        if(pre == nums[minIndex]){        // 去重,跳过这个最小元素
                            continue;
                        }
                        pre = nums[minIndex];
                        int left = minIndex + 1;
                        int right = nums.length - 1;       // right 指向三元组最大的一个数,必须是正数
                        while(left < right){
                            if(nums[minIndex] + nums[left] + nums[right] == 0){
                                List<Integer> tmp = new ArrayList();
                                tmp.add(nums[minIndex]);
                                tmp.add(nums[left]);
                                tmp.add(nums[right]);
                                ans.add(tmp);
                                
                                while(right > left && nums[right] == nums[right-1]){
                                    right -= 1;          // 去重,最大值不能重复
                                }
                                while(left < right && nums[left] == nums[left+1]){
                                    left += 1;           // 去重,中间值不能重复
                                }
                                left  += 1;              // 一直到 left 和 right 指向下两个不一样的值 
                                right -= 1; 
                                
                            }else if(nums[minIndex] + nums[left] + nums[right] < 0){
                                left += 1;
                            }else{
                                right -= 1;
                            }
                        }
                    }
                }
            }
            if(zeroCnt >= 3){
                List<Integer> zeroAns = new ArrayList();   // 准备三个零的答案
                zeroAns.add(0);
                zeroAns.add(0);
                zeroAns.add(0);
                ans.add(zeroAns);
            }
            return ans;
        }
    }
}

易错点:

  • 一些测试用例:
[-1,0,1,2,-1,-4]
[-1,-2,-3,0,0,0]
[-4,2,2,2,1,3,0,0,0,4]
[-2,-2,-4,0,0,0,1,1,2,2,4]
[0,2,3,3]
[-1,-2]
[2,3,4,5]
  • 答案:
[[-1,-1,2],[-1,0,1]]
[[0,0,0]]
[[-4,0,4],[-4,1,3],[-4,2,2],[0,0,0]]
[[-4,0,4],[-4,2,2],[-2,-2,4],[-2,0,2],[-2,1,1],[0,0,0]]
[]
[]
[]

总结:

  • 这题怎么着也能记住个几年吧。🤣🤣🤣
    在这里插入图片描述

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!