算法刷题打卡005 | 哈希表相关题目4道

文章介绍了哈希表的基础知识,包括Python中的dict、defaultdict和OrderedDict。通过LeetCode的几道题目(有效字母异位词、两个数组的交集、快乐数、两数之和)展示了哈希表在解决算法问题中的应用,如统计字符出现次数、找交集和判断数的性质等,强调了哈希表在效率和冲突解决上的重要性。
摘要由CSDN通过智能技术生成

今天复习哈希表,Python中哈希表常用dict(各种defaultdict, OrderedDict等等),先看看哈希表基础内容。简单来说,哈希表又叫散列表,将键值对中的键映射到散列表中的一个位置,可以加快查找的速度。对应的映射函数称为哈希函数(散列函数),类似于数组中直接用index获取元素值,dict中代入key获得的哈希函数值就是index,可以O(1)的时间获取键所对应的值。经过哈希函数映射之后,不同的键key如果对应到了同一个散列地址,就会出现哈希冲突,哈希函数的选择比较关键,已知的解决冲突的办法有拉链法(用列表存储散列地址相同的值)和线性探测法(搜索下一个空位),详见代码随想录哈希表章节讲解。

Python中的字典dict是用哈希表实现的,python3.8中关于dict的介绍可以参考Built-in Types — Python 3.8.16 documentation,是当前唯一一种标准的mapping类型。其他特殊的dict位于collections模块中,包括defaultdict和OrderedDict。defaultdict继承于dict,主要特点是可以在定义时指定默认值,比如设定默认值为空列表:d = defaultdict(list),具体使用方法可以参考collections — Container datatypes — Python 3.8.16 documentation;OrderedDict中的键是有序的,可以通过popitem()和move_to_end(),reversed()调整顺序。(collections — Container datatypes — Python 3.8.16 documentation

LeetCode 242 有效的字母异位词

题目链接:242. 有效的字母异位词 - 力扣(Leetcode)

是很明确使用哈希表的一道题,只要统计并两个字符串的字符出现次数是否相同即可。常规做法是分别遍历两个字符串进行统计,题目中仅包含小写字母,还能使用大小固定的数组来统计。

class Solution:
    def isAnagram(self, s: str, t: str) -> bool:
        n1, n2 = len(s), len(t)
        c1, c2 = dict(), dict()
        if n1 != n2:
            return False
        # 统计
        for i in range(n1):
            c1[s[i]] = c1.get(s[i], 0) + 1
            c2[t[i]] = c2.get(t[i], 0) + 1
        # # 或直接调用Counter
        # c1, c2 = collections.Counter(s), collections.Counter(t)
        return c1 == c2

总体的时间复杂度是O(n+k),n是字符串的长度,k是不同字符的数量(字典中键的长度,只有小写字母时,k=26)。当输入字符串包含unicode字符时就不好用长度固定的数组了,用哈希表才是更好的选择。看题解还有更巧妙的做法,就是遍历s时做加法,遍历t时做减法,看遍历结束后是否每个字母个数都是0。

LeetCode 349 两个数组的交集

题目链接:349. 两个数组的交集 - 力扣(Leetcode)

题目数据量比较小,直接暴力哈希表统计之后,比较两个哈希表的键找交集:

class Solution:
    def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
        c1, c2 = collections.Counter(nums1), collections.Counter(nums2)
        res = []
        for k in c1:
            if k in c2:
                res.append(k)
        return res

 另一种做法是在哈希统计之后,以元素数量较小的哈希表作为查找对象,二分搜索另一个哈希表的键,找到就能加入结果数组。具体实现时用set更方便,直接获取无重复的集合:

class Solution:
    def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
        # c1, c2 = collections.Counter(nums1), collections.Counter(nums2)
        res = []
        # 二分
        c1, c2 = set(), set()
        for n in nums1:
            c1.add(n)
        for n in nums2:
            c2.add(n)
        c2 = sorted(c2)
        for k in c1:
            left, right = 0, len(c2) - 1
            while left <= right:
                mid = left + (right - left) // 2
                if c2[mid] == k:
                    res.append(k)
                    break
                elif c2[mid] < k:
                    left = mid + 1
                else:
                    right = mid - 1
        return res

 以数组长度分别为n1,n2,nums1,nums2中不同元素的数量分别为k1,k2,那么第一种实现的具体时间复杂度是O(n1+n2+k1*k2),第二种实现就是O(n1+n2+k2*logk2+k1*logk2)=O(n1+n2+ (k1+k2)*logk2)。

*更正:后面查资料发现,Python的set查找效率不是和list等价的,用in在list中查找是O(n),而在set中仅为O(logn),因此即便实现2不用二分也可以达到logk2。而实现一中dict.keys()比较复杂,它的in操作时间复杂度有待查证,如果实现1也用set,那么它的时间复杂度应为O(n1+n2+k1*logk2),这样看来实现2的二分解法徒增一个排序的时间复杂度了。

LeetCode 202 快乐数

题目链接:202. 快乐数 - 力扣(Leetcode)

重新做题时,可以明确非快乐数在一定次数替换后会进入循环,因此当新数在过往出现过就能认定非快乐数了:

class Solution:
    def calSquareSum(self, num):
        ans = 0
        while num:
            ans += (num % 10) ** 2
            num //= 10
        return ans

    def isHappy(self, n: int) -> bool:
        if n == 1:
            return True
        occurred = dict()  # key: current n, value: next n, 当映射关系已经出现过,证明陷入无限循环,可以返回了
        while n > 1:
            next_n = self.calSquareSum(n)
            if n in occurred:  # and occurred[n] == next_n:
                return False
            occurred[n] = next_n
            n = next_n
        return True  # 跳出循环且没返回时n==1(正整数规定n不为0)

实际上上面的代码还写复杂了,不需要用dict,直接用列表就行,因为查看键n是否在dict中,本质上相当于在key列表中查找。

LeetCode 1 两数之和

题目链接:1. 两数之和 - 力扣(Leetcode)

直观做法是,遍历每一个数nums[i],查找数组中是否存在target-nums[i],存在即可返回。因为需要返回下标,所以遍历时用哈希表存储每个数对应的下标:

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

由于题目明确说明用例有且仅有一个有效答案,因此没有什么边界条件。还有一种暴力解法是两个for循环枚举所有可能的组合之和,满足条件的返回下标。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值