手撕噩梦,睡到天亮👀 --leetcode两数之和 & 三数之和 & 四数之和

142 阅读7分钟

前言

突然想起来之前入门算法的三个题目 , 入门的时候 ,似乎如官网下评论所说 : 一道 leetcode 敲一天 ;

而如今 , 时光荏苒 , 转眼大二上学期即将结束 , 又何尝不是一场梦 , 此刻拿起旧题 , 恍若隔世 ~ 🤡

梦的开始 : 两数之和

1. 两数之和

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。

你可以假设每种输入只会对应一个答案,并且你不能使用两次相同的元素。

你可以按任意顺序返回答案。

示例 1:

输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1]

示例 2:

输入:nums = [3,2,4], target = 6
输出:[1,2]

示例 3:

输入:nums = [3,3], target = 6
输出:[0,1]

两重 for 循环

class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        n = len(nums)
        for i in range(n):
            for j in range(i + 1, n):
                if nums[i] + nums[j] == target:
                    return [i, j]
        
        return []

哈希表

关键点 : 使用一个动态增加的哈希表 , 记录出现过的元素

# class Solution:
#     def twoSum(self, nums: List[int], target: int) -> List[int]:
#         h = dict()
#         for i , x in enumerate(nums) :
#             if target - x in h :
#                 return [h[target-x] , i]
#             h[x] = i
#         return []



class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        h = dict()
        for i in range(len(nums)) :
            if target - nums[i] in h :
                return [h[target - nums[i]] , i]
            h[nums[i]] = i

        return []
        

梦的破碎 : 三数之和

双指针解法


class Solution:
    def threeSum(self, nums: List[int]) -> List[List[int]]:
        n = len(nums)
        nums.sort()
        result = []

        for first in range(n):
            # 跳过重复的first值
            if first > 0 and nums[first] == nums[first - 1]:
                continue

            target = -nums[first]
            third = n - 1
            for second in range(first + 1, n):
                # 跳过重复的second值
                if second > first + 1 and nums[second] == nums[second - 1]:
                    continue

                while second < third and nums[second] + nums[third] > target:
                    third -= 1

                if second == third:
                    break

                if nums[second] + nums[third] == target:
                    result.append([nums[first], nums[second], nums[third]])

        return result

在上述优化后的代码中:

  • 首先对数组 nums 进行排序,这一步的时间复杂度为 ,其中 是数组 nums 的长度。
  • 然后通过遍历 first 指针,对于每个 first 的值,我们设定一个目标值 target(即 -nums[first]),然后使用双指针 secondthirdfirst 之后的区间内寻找满足 nums[second] + nums[third] == target 的组合。
  • 在遍历 second 指针的过程中,通过不断调整 third 指针来逼近目标值,这样就避免了最内层的完整循环枚举 third,大大降低了时间复杂度。

整体时间复杂度优化到了 ,相比于原始代码的 有了显著的提升(这里忽略了排序的时间复杂度,因为在 较大时,主要的时间消耗在后续的双指针遍历过程中)。空间复杂度主要取决于排序算法的实现,一般情况下,Python 内置的 sort 方法的空间复杂度为 之间,而结果列表 result 存储满足条件的三元组,最坏情况下可能存储 个元素,但通常情况下会远小于这个值,所以空间复杂度大致可以认为是 (这里考虑了结果列表的空间占用以及排序可能带来的空间消耗等综合因素)。

解析


整体功能

这段代码定义了一个名为 Solution 的类,其中包含一个方法 threeSum,用于在给定的整数列表 nums 中找到所有满足三数之和为 0 的三元组,并将这些三元组以列表的形式返回。

代码细节

  1. 初始化部分:
ans = []
n = len(nums)
nums.sort()
- 首先创建了一个空列表 `ans`,用于存储最终找到的满足条件的三元组。
- 然后获取输入列表 `nums` 的长度 `n`,并对 `nums` 进行排序。排序操作有助于后续通过双指针法更高效地查找满足条件的三元组,并且方便进行重复元素的处理。

2. 外层循环部分:

for i in range(n):
    if i > 0 && nums[i] == nums[i - 1]:
        continue
- 通过一个 `for` 循环遍历排序后的列表 `nums`,循环变量 `i` 表示当前要考虑的第一个数的索引。
- 在每次循环开始时,进行了一个去重判断。如果 `i` 大于 `0` 且当前元素 `nums[i]` 与前一个元素 `nums[i - 1]` 相同,就直接跳过本次循环。这是因为对于相同的第一个数,后续通过双指针法找到的满足三数之和为 `0` 的三元组组合情况是一样的,为了避免重复计算和重复添加相同的三元组到结果列表中,所以跳过。

3. 双指针部分:

l, r = i + 1, n - 1
while l < r:
    total = nums[i] + nums[l] + nums[r]
    if total < 0:
        l += 1
    elif total > 0:
        r -= 1
    else: 
        ans.append([nums[i], nums[l], nums[r]])
        while l < r and nums[l] == nums[l + 1]:
            l += 1
        while l < r and nums[r] == nums[r - 1]:
            r -= 1
        l+=1
        r-=1

对于每个不重复的 i,设定了两个指针 l(初始化为 i + 1)和 r(初始化为 n - 1),分别指向 nums[i] 后面的元素和列表的末尾元素,通过移动这两个指针来尝试找到满足三数之和为 0 的三元组。

  • while 循环中,首先计算当前三个数(nums[i]nums[l]nums[r])的和 total

    • 如果 total 小于 0,说明当前三个数的和偏小,为了增大和,将左指针 l 向右移动一位,这样下次循环时就会尝试更大的数与 nums[i]nums[r] 相加。
    • 如果 total 大于 0,说明当前三个数的和偏大,为了减小和,将右指针 r 向左移动一位,这样下次循环时就会尝试更小的数与 nums[i]nums[l] 相加。
    • 如果 total 等于 0,说明找到了一个满足三数之和为 0 的三元组,将这个三元组 [nums[i], nums[l], nums[r]] 添加到结果列表 ans 中。然后,为了避免后续重复添加相同的三元组,通过两个内层的 while 循环分别处理左指针 l 和右指针 r 位置上的重复元素。
      即当 l 位置的元素与下一个元素 nums[l + 1] 相同且 l 小于 r 时,将 l 向右移动一位;当 r 位置的元素与前一个元素 nums[r - 1] 相同且 l 小于 r 时,将 r 向左移动一位。处理完重复元素后,将 l 增加 1r 减少 1,以便继续寻找下一个可能满足条件的三元组。
  1. 返回结果部分:
return ans
- 循环结束后,将存储了所有满足三数之和为 `0` 的三元组的列表 `ans` 返回。

梦的疼痛 : 四数之和

18题. 四数之和


力扣题目链接(opens new window)

题意:给定一个包含 n 个整数的数组 nums 和一个目标值 target,判断 nums 中是否存在四个元素 a,b,c 和 d ,使得 a + b + c + d 的值与 target 相等?找出所有满足条件且不重复的四元组。

注意:

答案中不可以包含重复的四元组。

示例: 给定数组 nums = [1, 0, -1, 0, -2, 2],和 target = 0。 满足要求的四元组集合为: [ [-1, 0, 0, 1], [-2, -1, 1, 2], [-2, 0, 0, 2] ]

思路

15.三数之和(opens new window)的双指针解法是一层for循环num[i]为确定值,然后循环内有left和right下标作为双指针,找到nums[i] + nums[left] + nums[right] == 0。

四数之和的双指针解法是两层for循环nums[k] + nums[i]为确定值,依然是循环内有left和right下标作为双指针,找出nums[k] + nums[i] + nums[left] + nums[right] == target的情况,三数之和的时间复杂度是O(n^2),四数之和的时间复杂度是O(n^3) 。

代码


class Solution:
    def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
        nums.sort()
        n = len(nums)
        result = []
        for i in range(n):
            if nums[i] > target and nums[i] > 0 and target > 0:# 剪枝(可省)
                break
            if i > 0 and nums[i] == nums[i-1]:# 去重
                continue
            for j in range(i+1, n):
                if nums[i] + nums[j] > target and target > 0: #剪枝(可省)
                    break
                if j > i+1 and nums[j] == nums[j-1]: # 去重
                    continue
                left, right = j+1, n-1
                while left < right:
                    s = nums[i] + nums[j] + nums[left] + nums[right]
                    if s == target:
                        result.append([nums[i], nums[j], nums[left], nums[right]])
                        while left < right and nums[left] == nums[left+1]:
                            left += 1
                        while left < right and nums[right] == nums[right-1]:
                            right -= 1
                        left += 1
                        right -= 1
                    elif s < target:
                        left += 1
                    else:
                        right -= 1
        return result

**


我是在校大学生 , 欢迎大家一起交流 ~