Leetcode刷题笔记7:哈希表2

110 阅读3分钟

导语

leetcode刷题笔记记录,本篇博客记录哈希表部分的题目,主要题目包括:

  • 454 四数相加II
  • 383 赎金信
  • 15 三数之和
  • 18 四数之和

Leetcode 454 四数相加II

题目描述

给你四个整数数组 nums1nums2nums3 和 nums4 ,数组长度都是 n ,请你计算有多少个元组 (i, j, k, l) 能满足:

  • 0 <= i, j, k, l < n
  • nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0

示例 1:

输入: nums1 = [1,2], nums2 = [-2,-1], nums3 = [-1,2], nums4 = [0,2]
输出: 2
解释:
两个元组如下:
1. (0, 0, 0, 1) -> nums1[0] + nums2[0] + nums3[0] + nums4[1] = 1 + (-2) + (-1) + 2 = 0
2. (1, 1, 0, 0) -> nums1[1] + nums2[1] + nums3[0] + nums4[0] = 2 + (-1) + (-1) + 0 = 0

  提示:

  • n == nums1.length
  • n == nums2.length
  • n == nums3.length
  • n == nums4.length
  • 1 <= n <= 200
  • -2^28 <= nums1[i], nums2[i], nums3[i], nums4[i] <= 2^28

解法

本题目操作4个数组,因此不存在重复现象,简单的暴力法复杂度高达O(n4)O(n^4),这里可以借助与两数之和相同的思路,将前两个数组之和提前计算出存入一个字典map中,到时候进行双层循环计算后两个数组中元素的和,然后查找差值是否位于已经计算好的字典即可。这样可以将复杂度降低至O(n2)O(n^2)

完整代码如下:

class Solution:
    def fourSumCount(self, nums1: List[int], nums2: List[int], nums3: List[int], nums4: List[int]) -> int:
        sum2cnt = defaultdict(int)
        count = 0
        for a in nums1:
            for b in nums2:
                sum2cnt[a+b] += 1
        for c in nums3:
            for d in nums4:
                count += sum2cnt[-c-d]

        return count

Leetcode 383 赎金信

题目描述

给你两个字符串:ransomNote 和 magazine ,判断 ransomNote 能不能由 magazine 里面的字符构成。如果可以,返回 true ;否则返回 false 。magazine 中的每个字符只能在 ransomNote 中使用一次。

示例 1:

输入: ransomNote = "a", magazine = "b"
输出: false

提示:

  • 1 <= ransomNote.length, magazine.length <= 105
  • ransomNote 和 magazine 由小写英文字母组成

解法

这道题目和之前的异位词题目很像,都是应用数组的很好场景,因为只处理26个小写字母,所以很容易通过数组解决,解法如下:

class Solution:
    def canConstruct(self, ransomNote: str, magazine: str) -> bool:
        ransom_char, magazine_char = [0] * 26, [0] * 26
        for char in ransomNote:
            ransom_char[ord(char)-ord('a')] += 1
        for char in magazine:
            magazine_char[ord(char)-ord('a')] += 1
    
        for i in range(26):
            if ransom_char[i] > magazine_char[i]:
                return False
        return True

Leetcode 15 三数之和

题目描述

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

示例 1:

输入: nums = [-1,0,1,2,-1,-4]
输出: [[-1,-1,2],[-1,0,1]]
解释:
nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。
nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。
nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。
不同的三元组是 [-1,0,1][-1,-1,2] 。
注意,输出的顺序和三元组的顺序并不重要。

提示:

  • 3 <= nums.length <= 3000
  • -105 <= nums[i] <= 105

解法

本题目相对于之前的题目就复杂很多了,由于可能存在重复,所以并不适用于哈希表,这里使用双指针的方式来解决。

image.png

首先,需要将数组排序(一般双指针题目都需要这样),然后,最外层for循环从前往后遍历节点i,同时定义left和right的相向双指针如图所示。接下来如何移动left 和right呢, 如果nums[i] + nums[left] + nums[right] > 0 就说明 此时三数之和大了,因为数组是排序后了,所以right下标就应该向左移动,这样才能让三数之和小一些。如果 nums[i] + nums[left] + nums[right] < 0 说明 此时 三数之和小了,left 就向右移动,才能让三数之和大一些,直到left与right相遇为止。

这道题目的核心在于去重,

a的去重

a 如果重复了应该直接跳过去。但这里有一个问题,是判断 nums[i] 与 nums[i + 1]是否相同,还是判断 nums[i] 与 nums[i-1] 是否相同。

二者都是和 nums[i]进行比较,差异在于是比较它的前一个,还是比较他的后一个。这里分别进行考虑:

如果考虑后一个:

if (nums[i] == nums[i + 1]) { // 去重操作
    continue;
}

那就把三元组中出现重复元素的情况直接pass掉了。 例如{-1, -1 ,2} 这组数据,当遍历到第一个-1 的时候,判断 下一个也是-1,那这组数据就pass了。但是,我们要做的是 不能有重复的三元组,但三元组内的元素是可以重复的!

所以这里是有两个重复的维度。如果考虑前一个,

if (i > 0 && nums[i] == nums[i - 1]) {
    continue;
}

这么写就是当前使用 nums[i],我们判断前一位是不是一样的元素,再看 {-1, -1 ,2} 这组数据,当遍历到 第一个 -1 的时候,只要前一位没有-1,那么 {-1, -1 ,2} 这组数据一样可以收录到结果集里。这个才是需要的方法。

完整代码如下:

class Solution:
    def threeSum(self, nums: List[int]) -> List[List[int]]:
        nums.sort()
        result_l = []
        for i in range(len(nums)):
            if nums[i] > 0:
                return result_l
            if i>0 and nums[i]==nums[i-1]:
                continue
            left = i + 1
            right = len(nums) - 1
            while left < right:
                if nums[i] + nums[left] + nums[right] == 0:
                    result_l.append([nums[i], 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 nums[i] + nums[left] + nums[right] > 0:
                    right -= 1
                else:
                    left += 1
        return result_l

Leetcode 18 四数之和

这道题目是更加复杂的求和,但整体思想与三数之和类似。首先要做的是对数组进行排序,接着定义最外层指针k遍历数组。

image.png

由于这里的target并不一定是正数,所以一开始判断剪枝(提前去掉不可能的情况)时,需要判断target是否为正数。接着,需要先对最外层循环k去重,思路与三数之和的最外层循环类似。

然后,第二层循环就是三数之和的最外层循环,这里需要注意的是i是从k+1开始的,之后套入三数之和的代码即可,完整代码如下:

class Solution:
    def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
        result_l = []
        n = len(nums)
        nums.sort()
        for k in range(n):
            # 剪枝
            if target>0 and nums[k]>target:
                return result_l
            # 去重
            if k>0 and nums[k]==nums[k-1]:
                continue
            for i in range(k+1, n):
                if target>0 and nums[k]+nums[i]>target:
                    break
                # 去重
                if i>k+1 and nums[i]==nums[i-1]:
                    continue
                left = i + 1
                right = len(nums) - 1
                while left < right:
                    if nums[k] + nums[i] + nums[left] + nums[right] == target:
                        result_l.append([nums[k], nums[i], 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 nums[k] + nums[i] + nums[left] + nums[right] > target:
                        right -= 1
                    else:
                        left += 1
        return result_l

这里需要注意的是,第二层循环的剪枝break不能换成return,因为第一层循环中可能还存在符合情况,所以第二层剪枝应该使用break,而不是return。当然,剪枝的操作不是必须的,也仅能提升一点速度。