LeetCode算法刷题(python) Day6|03哈希表|242.有效的字母异位词、349. 两个数组的交集、 202. 快乐数、1. 两数之和

文档讲解:代码随想录 哈希表理论基础代码随想录 有效的字母异位词代码随想录 两个数组的交集代码随想录 快乐数代码随想录 两数之和
视频讲解:双指针法经典题目 | LeetCode:977.有序数组的平方学透哈希表,数组使用有技巧!Leetcode:242.有效的字母异位词学透哈希表,set使用有技巧!Leetcode:349. 两个数组的交集梦开始的地方,Leetcode:1.两数之和,学透哈希表,map使用有技巧!

哈希表理论基础

哈希表

哈希表也叫散列表(Hash table),根据关键码的值而直接进行访问的数据结构。哈希表可以用来快速判断一个元素是否是现在集合里。其实数组就是一张哈希表,哈希表中关键码就是数组的索引下标,然后通过下标直接访问数组中的元素。
举例,要查询一个名字是否在这所学校里,用哈希表查找的方式 O ( 1 ) O(1) O(1)就可以做到。

哈希函数

哈希函数,可以将学生的姓名直接映射为哈希表上的索引,然后通过查询索引下标快速知道这位同学是否在这所学校里。
哈希函数,通过hashCode把名字转化为数值,一般hashcode通过特定的编码方式,将其他数据格式转化为不同数值,这样就把学生名字映射为哈希表上的索引数字了。

如果hashcode得到的数值大于哈希表大小了,为了保证映射出来的索引值都落在哈希表上,我们对数值做一个取模操作。

常见的哈希函数

  • 除法哈希法: h ( k ) = k % m h(k) = k \% m h(k)=k%m
  • 乘法哈希法: h ( k ) = f l o o r ( m ∗ ( A ∗ k e y % 1 ) ) h(k) = \rm{floor}(m*(A*key\%1)) h(k)=floor(m(Akey%1))
  • 全域哈希法: h a , b ( k ) = ( ( a ∗ k e y + b ) m o d   p ) m o d   m a , b = 1 , 2 , . . . , p − 1 h_{a,b}(k)=((a*key + b) \rm{mod} \, p) mod\, m \quad a, b = 1, 2, ..., p-1 ha,b(k)=((akey+b)modp)modma,b=1,2,...,p1

哈希碰撞

如果学生数量大于哈希表大小,无法避免有几位同学映射到哈希表同一个索引下标,这种现象叫哈希碰撞。一般有两种解决方法,拉链法和线性探测法。

拉链法

发生冲突的元素储存在一个链表中。本质上拉链法就是要选择适当的哈希表大小,这样既不会因为数组空值而浪费大量内存,也不会因为链表太长而在查找上浪费太多时间。
在这里插入图片描述

线性探测法

使用线性探测法,一定要保证tablesize > datasize。对于冲突的位置,向下寻找一个空位放另一个数值,所以如果不满足tablesize > datasize,哈希表上就没有空位置存放冲突的数据了。

二次探测法

如果位置 i i i被占用,则探查 i + 1 2 i+1^2 i+12 i − 1 2 i-1^2 i12 i + 2 2 i+2^2 i+22 i − 2 2 i-2^2 i22

二度哈希

n n n个哈希函数,当使用第1个哈希函数h1发生冲突时,则尝试使用h2, h3 …

常见的三种哈希结构

  • 数组
  • set (集合)
  • map (映射)
    在python中,字典与集合都是通过哈希表来实现的。

LeetCode 242.有效的字母异位词

力扣题目链接
题目描述:给定两个字符串 st ,编写一个函数来判断 t 是否是 s 的字母异位词。

注意:若 st 中每个字符出现的次数都相同,则称 st 互为字母异位词。

示例1:
输入: s = "anagram", t = "nagaram"
输出: true

示例 2:
输入: s = "rat", t = "car"
输出: false

提示:
1 <= s.length, t.length <= 5 * 10^4
s 和 t 仅包含小写字母

解题思路

  • 本题可以遍历字符串s中每个字母出现的次数统计为哈希表,然后遍历t中的每个字母出现则将哈希表中的次数减1,若最终哈希表中所有元素都为0,则互为字母异位词,否则就不是。
  • 由于本题涉及字母只有26个,所以可以用数组的方式构建哈希表,python中ord("a")可以获得对应字母的ASCII码。
    代码如下:
# 解法一:数组哈希表
class Solution:
    def isAnagram(self, s: str, t: str) -> bool:
        record = [0] * 26

        for i in s:
            record[ord(i) - ord('a')] += 1
        
        for i in t:
            record[ord(i) - ord('a')] -= 1
        
        for i in record:
            if i != 0:
                return False
        return True
# 解法二:Counter字典哈希表
from collections import Counter
class Solution:
    def isAnagram(self, s: str, t: str) -> bool:
        s_cnt = Counter(s)
        t_cnt = Counter(t)
        return s_cnt == t_cnt

相关题目

LeetCode 383. 赎金信

力扣题目链接
本题还是很简单,使用数组哈希表即可,代码如下:

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

LeetCode 49. 字母异位词分组

力扣题目链接
由于本题中的单词只包含小写字母,所以最简单的方式是用数组哈希表表示每个单词,这样字母异位词对应的数组是完全相同的,代码如下:

from collections import defaultdict
class Solution:
    def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
        mp = defaultdict(list)

        for string in strs:
            counts = self.convert(string)
            mp[tuple(counts)].append(string)
        return list(mp.values())

    def convert(self, string):
        record = [0] * 26
        for i in string:
            record[ord(i) - ord('a')] += 1
        return record

LeetCode 438. 找到字符串中所有字母异位词

力扣题目链接

简单用数组哈希会超时:

class Solution:
    def findAnagrams(self, s: str, p: str) -> List[int]:
        n = len(p)
        target = self.convert(p)
        result = []
        if len(s) < n:
            return result

        for i in range(len(s) - n + 1):
            if self.convert(s[i:i+n]) == target:
                result.append(i)
        return result

    def convert(self, string):
        record = [0] * 26
        for i in string:
            record[ord(i) - ord('a')] += 1
        return record

滑动窗口 + 数组哈希:

class Solution:
    def findAnagrams(self, s: str, p: str) -> List[int]:
        p_len = len(p)
        s_len = len(s)
        result = []
        if s_len < p_len:
            return result
        p_count = [0] * 26
        s_count = [0] * 26
        for i in range(p_len):
            p_count[ord(p[i]) - ord('a')] += 1
            s_count[ord(s[i]) - ord('a')] += 1
        if s_count == p_count: result.append(0)
        for i in range(s_len - p_len):
            s_count[ord(s[i]) - ord('a')] -= 1
            s_count[ord(s[i+p_len]) - ord('a')] += 1

            if s_count == p_count:
                result.append(i+1)
        return result


LeetCode 349. 两个数组的交集

力扣题目链接
题目描述:给定两个数组 nums1nums2 ,返回 它们的交集 。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序

示例1:
输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2]

示例 2:
输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出:[9,4]
解释:[4,9] 也是可通过的

提示:
1 <= nums1.length, nums2.length <= 1000
0 <= nums1[i], nums2[i] <= 1000

解题思路

本题还是较为简单的,注意的是如果没有提示中1 <= nums1.length, nums2.length <= 1000的限制,本题是不适合用数组的哈希方式的,因为如果数据比较分散会浪费很多空间,本题最合适的哈希方式是set。
代码如下:

# 集合
class Solution:
    def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
        return list(set(nums1) & set(nums2))

# 字典
from collections import Counter
class Solution:
    def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
        result = []
        cnt = Counter(nums1)
        for num in nums2:
            if cnt[num] > 0 and num not in result:
                result.append(num)
        return result

相关题目

LeetCode 350. 两个数组的交集 II

题目描述:给你两个整数数组 nums1nums2 ,请你以数组形式返回两数组的交集。返回结果中每个元素出现的次数,应与元素在两个数组中都出现的次数一致(如果出现次数不一致,则考虑取较小值)。可以不考虑输出结果的顺序。

示例 1:
输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2,2]

示例 2:
输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出:[4,9]

解题思路:本题可以用一个字典记录出现元素的次数,遍历另一个数组时每当遇到一个出现过的元素,将次数减一,并记录这个数即可。
代码如下:

from collections import Counter
class Solution:
    def intersect(self, nums1: List[int], nums2: List[int]) -> List[int]:
        result = []
        cnt = Counter(nums1)
        for num in nums2:
            if cnt[num] > 0:
                result.append(num)
                cnt[num] -= 1
        return result

LeetCode 202. 快乐数

力扣题目链接
题目描述
编写一个算法来判断一个数 n 是不是快乐数。

「快乐数」 定义为:

  • 对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
  • 然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
  • 如果这个过程 结果为 1,那么这个数就是快乐数。
    如果 n 是 快乐数 就返回 true ;不是,则返回 false
    在这里插入图片描述

解题思路

本题有三种可能:

  • 最终会得到1
  • 最终会进入循环
  • 值会越来越大,最后接近无穷大

然而第三种是否存在呢?

DigitsLargestNext
1981
299162
3999243
49999324
1399999999999991053

通过这个表可以看出,三位数的位平方和不可能大于243,这也是一个三位数,那么它的位平方和就会更小,直到进入循环或者跌到1。对于位数比较大的数来说,即使是13位数,它的位平方和也不会超过1053,再次计算又会跌入三位数。由此可见上述的第三种情况是不会出现的。

在这里插入图片描述
在这里插入图片描述
求解这道题只需要记录每次的位平方和,如果得到1就直接返回True,如果出现重复数字了,说明陷入循环了,返回False。
代码如下:

class Solution:
    def isHappy(self, n: int) -> bool:
        record = set()
        
        while True:
            n = self.bitSquareSum(n)
            if n == 1:
                return True
            
            if n in record:
                return False
            record.add(n)
        
    def bitSquareSum(self, num):
        result = 0
        res = num
        
        while res != 0:
            result += (res % 10)**2
            res = res // 10
        return result

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]

提示:
2 <= nums.length <= 10^4
-10^9 <= nums[i] <= 10^9
-10^9 <= target <= 10^9

解题思路

本题使用map这种哈希结构(python中的字典),对于每一次遍历到的数字,我们在哈希表中key存放数值,value存放下标,同时在哈希表中查找 target - num,如果找到直接返回二者的下标即可。
代码如下:

class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        hash_table = {}
        for i, num in enumerate(nums):
            if target - num in hash_table.keys():
                return [hash_table[target - num], i]
            else:
                hash_table[num] = i

今日毕,加油!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值