LeetCode 15-三数之和

88 阅读5分钟

题目

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

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

思路

基础解法:三层循环+对结果排序去重

思路

1、找到i,j,k位置上,≈=0的值

i、j、k分别以i从0...n, i<j<k的顺序循环取值,然后判断i,j,k位置上的值加起来是否为0,如果是,则视为结果

2、结果去重

[nums[i], nums[j], nums[k]]列表进行排序,后查询是否在结果中,如果不在,再保存到结果

class Solution:
    def threeSum(self, nums: List[int]) -> List[List[int]]:
        # 解法1: O(N^3)
        res = []
        if len(nums) < 3:
            return
        for i in range(len(nums)):
            for j in range(i+1, len(nums):
                for k in range(j+1, len(nums)):
                    if nums[i] + nums[j] + nums[k] == 0:
                        # 通过排序来去重
                        if sorted([nums[i], nums[j], nums[k]])not in res:
                            res.append(sorted([nums[i], nums[j], nums[k]]))
        return res

基础解法:三层循环+循环遍历时去重

不重复本质,三重循环等价于独立的每一层循环都不重复+两层之间有顺序:

  • 第一层循环元素不重复
  • 第二层循环元素不重复,且第二层循环元素不小于第一层循环元素
  • 第三层循环元素不重复,且第三层的循环元素不小于第二层循环元素

第一层i1、i2....in有序,且本层相邻元素不重复;第二层j1、j2....jn有序,且本层相邻元素不重复;第三层k1、k2....kn有序,且本层相邻元素不重复

所以nums需要排序,排序后检查每层的相邻是否重复,如果重复指针,继续往右移,否则进入下一层

# 伪代码
# O(N^3)
nums.sort() # 排序
for first in 0...n-1: # 第一层
    if nums[first] == nums[first-1]: # 相邻不允许重复
        continue
    for second in first+1...n-1: # 第二层
        if nums[second] == nums[second-1]: # 相邻不允许重复
            continue
        for third in second+1...n-1: # 第三层
            if nums[third] == nums[third-1]: # 相邻不允许重复
            continue
            check(first, second, third, num) # check是否满足条件

参考解法:排序+双指针

第二层和第三层其实一直在一个有序数组中,查找 nums[j] + nums[k] = -nums[i] 的操作,和 twoSum(nums, target)的题目要求一模一样,所以可以用双指针O(N)代替第二层+第三层循环的O(N^2)的代码。

# 伪代码
# O(N^3)
nums.sort() # 排序
res = []
for first in 0...n-1: # 第一层
    if nums[first] == nums[first-1]: # 相邻不允许重复
        continue
    twoSum(nums, frist, first+1, len(nums)-1, -nums[first])


def twoSum(nums, start, i, j, target):
    res = []
    while i < j:
        s = nums[i] + nums[j]
        if s ==  target:
            res.append(nums[start], nums[i], nums[j])
            j -= 1
            # 第二层指针右移时去重
            while i < j and nums[i] == nums[i+1]:
                i += 1
            i += 1 # 第二层指针右移 
            # 第三层指针左移时去重
            while i < j and nums[j] == nums[j-1]:
                j -= 1
            j -= 1  # 第三层指针左移
        elif s < target:
            i += 1 # 总值太小,第二层指针右移
        else:
            j -= 1 # 总值太大,第三层指针左移
    

代码一:twoSum辅助函数版

class Solution:
    def threeSum(self, nums: List[int]) -> List[List[int]]:
        res = []
        nums.sort()
        if len(nums) < 3:
            return res
        n = len(nums)
        for first in range(n):
            if first > 0 and nums[first] == nums[first-1]:
                continue
            res.extend(self.twoSum(nums, first, first+1, n-1, -nums[first]))
        return res

    def twoSum(self, nums: List[int], first: int, second: int, third: int, target:int) -> List[List[int]]:
        res = []
        while second < third:
            s = nums[second] + nums[third]
            if s == target:
                res.append([nums[first], nums[second], nums[third]])
                while second < third and nums[second] == nums[second+1]:
                    second += 1
                second += 1
                while second < third and nums[third] == nums[third-1]:
                    third -= 1
                third -= 1
            elif s > target:
                third -= 1
            else:
                second += 1
        return res

代码二:简洁版twoSum双指针

class Solution:
    def threeSum(self, nums: List[int]) -> List[List[int]]:
        res = []
        nums.sort()
        if len(nums) < 3:
            return res
        n = len(nums)
        for first in range(n):
            if first > 0 and nums[first] == nums[first-1]:
                continue
            second = first + 1
            third = n - 1
            while second < third:
                s = nums[first] + nums[second] + nums[third]
                if s == 0:
                    res.append([nums[first], nums[second], nums[third]])
                    while second < third and nums[second] == nums[second + 1]:
                        second += 1
                    second += 1
                    while second < third and nums[third] == nums[third - 1]:
                        third -= 1
                    third -= 1
                elif s > 0:
                    third -= 1
                else:
                    second += 1
        return res

小优化:双指针操作前优化双指针区间

在确定了 first、second、third 的指针后,可以判断 nums[first] + nums[second] + nums[third] 是否大于0或者小于0,注意,这里只需要判断一侧即可,即大于0或者小于0,切记不能两边都判断。

  • 当sum > 0时,说明保持third指针不变,随着second指针的右移,sum都会大于0,在这个区间是不可能有满足条件的值,所以应该在当前third的左边区间找,third往左移动
  • 当sum < 0时,说明保持second指针不变,随着third指针的左移,sum都会小于0,在这个区间是不可能有满足条件的值,所以应该在当前second的右边寻找,second往右移动 从上面的两种方案种,判断一种即可:

代码三:优化版twoSum双指针

class Solution:
    def threeSum(self, nums: List[int]) -> List[List[int]]:
        res = []
        nums.sort()
        if len(nums) < 3:
            return res
        n = len(nums)
        for first in range(n):
            if first > 0 and nums[first] == nums[first-1]:
                continue
            second = first + 1
            third = n - 1
            if second < third:
                while nums[first] + nums[second] + nums[third] > 0 and second < third:
                    third -= 1
            while second < third:
                s = nums[first] + nums[second] + nums[third]
                if s == 0:
                    res.append([nums[first], nums[second], nums[third]])
                    while second < third and nums[second] == nums[second + 1]:
                        second += 1
                    second += 1
                    while second < third and nums[third] == nums[third - 1]:
                        third -= 1
                    third -= 1
                elif s > 0:
                    third -= 1
                else:
                    second += 1
        return res