0015_3Sum【M】三数之和为 0 的组合(要求组合不重复)

  • JY:三数之和为 0 的组合(要求组合不重复)

1、转换为求解 2 数之和(超时)

  • a + b + c = 0等价于b + c = -a,所以问题就转化为 0001_Two-Sum【S】
  • 遍历数组,判断其余的数组元素中是否存在两个数的和为当前元素的相反数。
  • 由于 0001_Two-Sum【S】的时间复杂度是O(n),算上外层的循环,三数之和的时间复杂度为O(n^2),导致此题超时;此外,符合条件的三个数的组合不能重复,多了一步去重的工作,也进一步加大了算法的时间复杂度。
class Solution:
    def threeSum(self, nums: List[int]) -> List[List[int]]:
        sums = []
        # jy: 遍历列表中的元素
        for i, value in enumerate(nums):
            # jy: 从列表 nums 中找出两个值为 -value 的下标 (下标为 i 的值不被考虑)
            ls_two_sum = self._two_sum(nums, i, -value)
            # jy: 如果 two_sums 这样的值存在 (可能存在多组), 则 3 数之
            #     和为 0 的组合存在
            if ls_two_sum:
                # jy: 将下标转换为相应的数值列表, 并添加到 three_sums 列表中
                for two_sum in ls_two_sum:
                    three_sum = [value] + two_sum
                    # jy: 判断 sums 组合列表中是否已经包含 three_sum 组合, 如
                    #     果包含则不再重复加入
                    if not self._has_three_sum(sums, three_sum):
                        sums.append(three_sum)
        return sums


    def _two_sum(self, nums: List[int], current_index: int,
                 target: int) -> List[List[int]]:
        """
        从列表 nums 中找出两个值为 target 的下标, 并确保找到的二元组不重复 
        (查找过程中忽略下标为 current_index 的值)
        """
        mapping = {}
        ls_two_sum = []
        # jy: 用于记录已获取的数值, 便于去重
        set_done = set()
        
        for i, value in enumerate(nums):
            if i == current_index:
                continue

            if target - value in mapping and value not in set_done:
                ls_two_sum.append([target - value, value])
                set_done.add(value)
                set_done.add(target - value)
            else:
                mapping[value] = i
        return ls_two_sum


    def _has_three_sum(self, three_sums: List[List[int]],
                       three_sum: List[int]) -> bool:
        """
        判断 three_sums 的组合列表中是否已经包含 three_sum 组合
        """
        for sum_ in three_sums:
            #if sorted(sum_) == sorted(three_sum):
            if set(sum_) == set(three_sum):
                return True
        return False


nums = [-1, 0, 1, 2, -1, -4]
res = Solution().threeSum(nums)
print(nums, " === ", res)

2、排序 + 问题转换

  • 先将数组排序,将问题转换为 0167_two-sum-II__input-array-is-sorted【M】:遍历数组,在当前元素之后的数中使用双指针查询和为当前元素的相反数的两个数;需要注意以下几点:
    • 外层遍历数组时,如果当前元素值大于 0,则可以直接退出循环,因为数组有序,当前元素后的数必然也是大于 0,不可能存在三数之和为 0
    • 如果当前元素的值和前一个元素的值相同,则直接跳过当前元素,因为最终的三元组不能重复(如果当前元素符合条件则在上一轮操作时已包含了这个数)
    • 对于外层循环下标为i的元素来说,为什么双指针处理只需要查找[k+1, L-1]区间(L为数组的长度)的元素即可?因为假设存在满足条件的结果集中有一个元素的下标小于k,记为k',则这个组合已经在外层循环到k'时就已经找到了这个组合,所以无需再次处理
  • 涉及到的排序算法最好能自己实现
class Solution:
    def threeSum(self, nums: List[int]) -> List[List[int]]:
        # jy: 对数组进行排序
        nums.sort()
        sums = []
        # jy: 遍历列表中的元素, 找出 nums[i+1: ] 中与 nums[i] 能组合成
        #     3 数为 0 的所有组合 (双指针法查找)
        for i in range(0, len(nums)-2):
            # jy: 3 数之和为 0, 必定以负数开始; 由于数组已经排序, 因此当遍
            #     历至大于 0 的数值时, 即可终止 (等于 0 时不能终止, 因为可
            #     能后续有两个数为 0, 此时三个 0 也能满足条件)
            if nums[i] > 0:
                break

            # jy: 如果当前数值与前一个数值相同, 则跳过, 避免重复查找; 因为
            #     如果当前数值与之后的数值能组成满足要求的一组数, 则在遍历
            #     上一个数值时就都已经找到相应的组合了 (不管遍历上一个数值
            #     时是否包含该相同的数值)
            if i-1 >= 0 and nums[i] == nums[i-1]:
                continue

            # jy: target 为剩余两数之和
            target = - nums[i]
            # jy: 剩余两数从 i+1 到 len(nums)-1 的下标中找 (双指针即初始化为这两个下标)
            low, high = i+1, len(nums)-1
            while low < high:
                # jy: 如果两值相等, 则将符合要求的 3 数列表加入 sums 列表组合中
                if nums[low] + nums[high] == target:
                    # jy: 注意需将 target 恢复为原数值
                    sums.append([-target, nums[low], nums[high]])

                    # jy: 跳过相同的数值, 避免重复计算
                    while low < high and nums[low] == nums[low+1]:
                        low += 1
                    while low < high and nums[high] == nums[high-1]:
                        high -= 1

                    low += 1
                    high -= 1
                elif nums[low] + nums[high] < target:
                    low += 1
                else:
                    high -= 1
        return sums


nums = [-1, 0, 1, 2, -1, -4]
res = Solution().threeSum(nums)
print(nums, " === ", res)
  • 9
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

融码一生

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值