leetcode刷题-哈希表02

代码随想录第六天哈希表part01|454.四数相加II、383. 赎金信、15.三数之和、18.四数之和

454.四数相加II

leetcode题目链接
代码随想录文档讲解

思路

与四数之和的区别:四数之和要考虑去重
①暴力法求解:4层for循环,时间复杂度O(n^4)
②利用哈希表求解:假设数组列表为:A,B,C,D
遍历A,B数组,将数组相加后的值放到一个集合里,遍历C,D数组,判断集合里有没有我们想要的元素。
数据结构的选择:因为数组中存储的值可能会很大,考虑set或map;因为要统计A+B出现过的次数,选择map数据结构。
注意: 在判断map中存在我们需要的值之后,不是count=count+1,而是count=count+map[key]

伪代码(C++):

unordered_map(int, int)
for(a:A)
	for(b:B)
		map[a+b]++;
for(c:C)
	for(d:D)
		target = 0 - (c+d)
		if(map.find(target != map.and)) // 在map中找到了target
			count += map[target]

python代码

补充:python中字典的一些方法:
①dict.get(key[, value])
key – 字典中要查找的键。
value – 可选,如果指定键的值不存在时,返回该默认值。
②key in dict:判断字典dict中是否存在key这个键

class Solution:
    def fourSumCount(self, nums1: List[int], nums2: List[int], nums3: List[int], nums4: List[int]) -> int:
        hashmap = {}
        count = 0
        for a in nums1:
            for b in nums2:
                hashmap[a+b] = hashmap.get(a+b, 0) + 1
        for c in nums3:
            for d in nums4:
                target = 0 - c - d
                count += hashmap.get(target, 0)
        return count

// 版本二:还没细看
from collections import defaultdict 
class Solution:
    def fourSumCount(self, nums1: list, nums2: list, nums3: list, nums4: list) -> int:
        rec, cnt = defaultdict(lambda : 0), 0
        for i in nums1:
            for j in nums2:
                rec[i+j] += 1
        for i in nums3:
            for j in nums4:
                cnt += rec.get(-(i+j), 0) 
        return cnt

383. 赎金信

leetcode题目链接
代码随想录文档讲解

思路
该题较为简单,考虑用哈希表来做,需要记录元素出现的次数,可以使用数组或者map,由于这里固定randomNote和magazine都只由小写英文字母组成,可以选择数组这一数据结构。
一些同学可能想,用数组干啥,都用map完事了,其实在本题的情况下,使用map的空间消耗要比数组大一些的,因为map要维护红黑树或者哈希表,而且还要做哈希函数,是费时的!数据量大的话就能体现出来差别了。 所以数组更加简单直接有效!

python代码
补充:
①python中字典的get()方法dict.get(key[, value])
key – 字典中要查找的键。
value – 可选,如果指定键的值不存在时,返回该默认值。
②用ord(‘a’)获得字符a的ASCII码

// 使用数组
class Solution:
    def canConstruct(self, ransomNote: str, magazine: str) -> bool:
        hashlist = [0]*26
        for b in magazine:
            hashlist[ord(b) - ord('a')] = hashlist[ord(b) - ord('a')] + 1
        for a in ransomNote:
            if hashlist[ord(a) - ord('a')]:
                hashlist[ord(a) - ord('a')] -= 1
            else:
                return False
        return True

# 使用字典 map
class Solution:
    def canConstruct(self, ransomNote: str, magazine: str) -> bool:
        hashmap = dict()
        for b in magazine:
            hashmap[b] = hashmap.get(b,  0) + 1
        for a in ransomNote:
            if hashmap.get(a, 0):
                hashmap[a] = hashmap[a] - 1
            else:
                return False
        return True

15. 三数之和

leetcode题目链接
代码随想录文档讲解

思路:

两个for循环确定a+b,判断a,b,c是否同时满足时,只需判断a+b是否在集合中存在。该题的关键也是在于去重,不太建议使用哈希法做该题,因为去重的细节太多了,很难做到bug free。可以使用双指针法

双指针法:

思路:
对数组进行排序,该题返回的是元素值而不是下标所以可以排序。
指针i得到a,剩下的位置,left,right
数组下标表示
如果 nums[i] + nums[left] + nums[right] > 0,让right前移一位,right–
如果 nums[i] + nums[left] + nums[right] < 0,让left后移一位,left++
如果 nums[i] + nums[left] + nums[right] = 0,结果:nums[i], nums[left], nums[right
去重:结果集中不能有重复的三元组,

伪代码 (C++)

注意: ①nums[i]的去重 注意是i-1不是i+1;②nums[left]和nums[right]的去重,注意是while不是if,并且nums[right] = nums[right-1]而不是 nums[right] = nums[right+1],同理right

sort(nums)
result = [ ]
for(i=0; i<nums.size; i++)
	if(nums[i]>0) // 第一个数大于0,无解
		return 
	if(i>0 && nums[i] == nums[i-1]) // nums[i]的去重 注意是i-1不是i+1
		continue
	left = i+1
	right = num.size-1
	while(left<right) // 区间的边界性问题
		if(nums[i] + nums[left] + nums[right] > 0) right--
		else if(nums[i] + nums[left] + nums[right] > 0) left ++
		else 
			result.push([nums[i], nums[left], nums[right]])
			// left和right的去重
			while(left<right && nums[right] == nums[right-1]) right--
			while(left<right && nums[left] == nums[left+1]) left++
			right-- left ++

python代码

python中divmod函数介绍:divmod()函数把除数和余数运算结果结合起来
例如,divmod(a, b),返回一个包含商和余数的元组(a // b, a % b)

class Solution:
    def threeSum(self, nums):
        nums.sort()
        n = len(nums)
        result = []
        for i in range(n-1):
            if nums[i]>0: 
                return result
            if i>0 and nums[i] == nums[i-1]:
                continue
            l, r = i+1, n-1
            while l<r:
                if nums[i]+nums[l]+nums[r] >0 :
                    r-=1
                elif nums[i]+nums[l]+nums[r] < 0:
                    l+=1
                else:
                    result.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
        return result

# 以前写的代码:关键在于去重
class Solution:
    def threeSum(self, nums):
        n = len(nums)
        result = []
        nums.sort()

        for i in range(n-2):
            if nums[i] + nums[i+1] + nums[i+2] > 0:
                break
            if nums[i] + nums[n-2] + nums[n-1] < 0:
                continue
            if i > 0 and nums[i] == nums[i-1]: # 对于[-4,-1,-1,0,1,2] 去重,当起始的值等于前一个元素,那么得到的结果将会和前一次相同 [-1,0,1] [-1,0,1]
                continue
            l, r = i+1, n-1
            while l < r:
                tmp = nums[i] + nums[l] + nums[r]
                if tmp == 0:
                    result.append([nums[i], nums[l], nums[r]])
                    while l+1 < r and nums[l] == nums[l+1]:
                        l += 1
                    l+=1
                    while l<r-1 and nums[r] == nums[r-1]:
                        r -= 1
                    r-=1
                elif tmp < 0:
                    l += 1
                else:
                    r -= 1
        return result 

18. 四数之和

leetcode题目链接
代码随想录文档讲解

思路:

延续三数之和的思路,再套用一个for循环。
主要在于剪枝和去重的操作,剪枝误区: 误以为排序后nums[0]>target不符合题意,若nums[0]和target都为负数,并且nums[1]也<0,则会出现两数相加后更小。正确剪枝:nums[k]>0&&target>0

python代码

注意while中的边界条件需要仔细考虑,while curr而不是while curr.next,因为最后一个是空指针,temp = curr.next

# 版本一 双指针
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
# 以前的代码
class Solution:
    def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
        result = []
        nums.sort()
        n=len(nums)
        for i in range(n-3):
            if nums[i]+nums[i+1]+nums[i+2]+nums[i+3]>target:
                break
            if nums[i]+nums[n-1]+nums[n-2]+nums[n-3]<target:
                continue
            if i>0 and nums[i] == nums[i-1]: #去重,当起始的值等于前一个元素,那么得到的结果将会和前一次相同
                continue
            for j in range(i+1,n-2):
                if j>i+1 and nums[j] == nums[j-1]: #去重,当起始的值等于前一个元素,那么得到的结果将会和前一次相同
                    continue
                l=j+1
                r=n-1
                while(l<r):
                    total = nums[i]+nums[j]+nums[l]+nums[r]
                    if total==target:
                        result.append([nums[i],nums[j],nums[l],nums[r]])
                        while l+1<r and nums[l]==nums[l+1]:
                            l+=1
                        l+=1
                        while r-1>l and nums[r]==nums[r-1]:
                            r-=1
                        r-=1
                    elif total<target:
                        l+=1
                    else:
                        r-=1
        return(result)
       
# 版本二 使用字典
class Solution(object):
    def fourSum(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: List[List[int]]
        """
        # 创建一个字典来存储输入列表中每个数字的频率
        freq = {}
        for num in nums:
            freq[num] = freq.get(num, 0) + 1
        
        # 创建一个集合来存储最终答案,并遍历4个数字的所有唯一组合
        ans = set()
        for i in range(len(nums)):
            for j in range(i + 1, len(nums)):
                for k in range(j + 1, len(nums)):
                    val = target - (nums[i] + nums[j] + nums[k])
                    if val in freq:
                        # 确保没有重复
                        count = (nums[i] == val) + (nums[j] == val) + (nums[k] == val)
                        if freq[val] > count:
                            ans.add(tuple(sorted([nums[i], nums[j], nums[k], val])))
        
        return [list(x) for x in ans]

时间复杂度:O(n^3)
空间复杂度:O(n)

总结

一般来说哈希表都是用来快速判断一个元素是否出现集合里。
对于哈希表,要知道哈希函数和哈希碰撞在哈希表中的作用。
哈希函数是把传入的key映射到符号表的索引上。
哈希碰撞处理有多个key映射到相同索引上时的情景,处理碰撞的普遍方式是拉链法和线性探测法。

接下来是常见的三种哈希结构:
数组
set(集合)
map(映射)
在C++语言中,set 和 map 都分别提供了三种数据结构,每种数据结构的底层实现和用途都有所不同。

数组作为哈希表
在242.有效的字母异位词中,我们提到了数组就是简单的哈希表,但是数组的大小是受限的!
这道题目包含小写字母,那么使用数组来做哈希最合适不过。
在383.赎金信 (opens new window)中同样要求只有小写字母,那么就给我们浓浓的暗示,用数组!
本题和242.有效的字母异位词很像,242.有效的字母异位词是求字符串a 和字符串b 是否可以相互组成,在383.赎金信中是求字符串a能否组成字符串b,而不用管字符串b 能不能组成字符串a。

一些同学可能想,用数组干啥,都用map不就完事了。
上面两道题目用map确实可以,但使用map的空间消耗要比数组大一些,因为map要维护红黑树或者符号表,而且还要做哈希函数的运算。所以数组更加简单直接有效!

set作为哈希表
在349. 两个数组的交集 (opens new window)中我们给出了什么时候用数组就不行了,需要用set。
这道题目没有限制数值的大小,就无法使用数组来做哈希表了。
主要因为如下两点:
数组的大小是有限的,受到系统栈空间(不是数据结构的栈)的限制。
如果数组空间够大,但哈希值比较少、特别分散、跨度非常大,使用数组就造成空间的极大浪费。
所以此时一样的做映射的话,就可以使用set了。
在202.快乐数中,我们再次使用了unordered_set来判断一个数是否重复出现过。

map作为哈希表
在1.两数之和 (opens new window)中map正式登场。
来说一说:使用数组和set来做哈希法的局限。
数组的大小是受限制的,而且如果元素很少,而哈希值太大会造成内存空间的浪费。
set是一个集合,里面放的元素只能是一个key,而两数之和这道题目,不仅要判断y是否存在而且还要记录y的下标位置,因为要返回x 和 y的下标。所以set 也不能用。
map是一种<key, value>的结构,本题可以用key保存数值,用value在保存数值所在的下标。所以使用map最为合适。

C++提供如下三种map:(详情请看关于哈希表,你该了解这些!
std::map
std::multimap
std::unordered_map
std::unordered_map 底层实现为哈希,std::map 和std::multimap 的底层实现是红黑树。
同理,std::map 和std::multimap 的key也是有序的(这个问题也经常作为面试题,考察对语言容器底层的理解),1.两数之和 (opens new window)中并不需要key有序,选择std::unordered_map 效率更高!

在454.四数相中我们提到了其实需要哈希的地方都能找到map的身影。
本题咋眼一看好像和18. 四数之和,15.三数之和差不多,其实差很多!
关键差别是本题为四个独立的数组,只要找到A[i] + B[j] + C[k] + D[l] = 0就可以,不用考虑重复问题,而18. 四数之和 (opens new window),15.三数之和 (opens new window)是一个数组(集合)里找到和为0的组合,可就难很多了!

用哈希法解决了两数之和,很多同学会感觉用哈希法也可以解决三数之和,四数之和。
其实是可以解决,但是非常麻烦,需要去重导致代码效率很低。
在15.三数之和 (opens new window)中我给出了哈希法和双指针两个解法,大家就可以体会到,使用哈希法还是比较麻烦的。
所以18. 四数之和,15.三数之和都推荐使用双指针法!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值