数据结构与算法-哈希表

学习计划:3个月内算法小成,完成leetcode hot100

当前进度:学完数组、链表

刷题语言:Python

时间:2024/12/07-2024/12/17

三、哈希表

学习链接:代码随想录

1.哈希表理论基础

哈希表(Hash table,散列表)

哈希表的关键就是使用索引直接访问元素,类似使用数组下标访问数组中的元素。

哈希表解决的问题:用于快速判断一个元素是否出现在集合内。类似查询一个名字是都在这所学校的名单上,如果使用枚举的话,时间复杂度是O(n),但是使用哈希表时间复杂度是O(1)。

哈希函数

哈希函数:把学生的姓名映射到哈希表上的索引,然后就可以通过查询索引快速知道这位同学是否在这个学校内了。

通过hashCode把名字转化成哈希值(一种编码方式),这样就可以把字符串映射到哈希表上的索引数字了。

如果哈希编码后得到的数值大于哈希表的大小tableSize,为了保证映射出来的索引数值都落在哈希表上,需要对哈希值做一个取模操作,这样就可以保证学生的姓名一定落在哈希表上了,但是可能会出现好几位学生的名字映射到同一个哈希值上,这就是哈希碰撞。

哈希碰撞

解决方法:拉链法,线性探测法

拉链法:将发生冲突的元素都保存在链表中,先通过哈希值定位到链表,再在链表中找。但是需要选择合适的哈希表大小,这样才不会应为数组空值而浪费大量内存空间,也不会因为链表太长而在查找上浪费大量时间。

线性探测法:使用线性探测法一定要保证tablesize大于datasize,我们需要依靠哈希表中的空位来解决碰撞问题。例如小李已经放在哈希表中1的位置,此时小张的哈希值也是1,那么就向下找一个空位放置小王的信息。

常见的三种哈希结构
  • 数组
  • set(集合)
  • map(映射)

2.有效的字母异位词

力扣题目链接

给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。

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

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

说明: 你可以假设字符串只包含小写字母。

class Solution:
    def isAnagram(self, s: str, t: str) -> bool:
        
        #使用数组作为哈希表
        res = [0]*26  #字母一共有26个,用一个长度26的列表保存每个字母的个数

        #对s进行遍历,对应的字母+1
        for i in range(len(s)):
            res[ord(s[i])-ord('a')] +=1    #ord()用于获取字母的ascii码值,a-z是按顺序递增的
        
        #对t进行遍历,对应的字母-1
        for i in range(len(t)):
            res[ord(t[i])-ord('a')] -=1

        #如果s和t的字母完全对应,则最后的res都是0
        for i in range(26):
            if res[i]!=0:
                return False
        return True

3.两个数组的交集

力扣题目链接

题意:给定两个数组,编写一个函数来计算它们的交集。

#改用set的时候还是得用set

class Solution:
    def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
        #set() 函数创建一个无序不重复元素集,可进行关系测试,删除重复数据,还可以计算交集、差集、并集等
        #&计算交集,|计算并集,-计算差集
        return list(set(nums1) & set(nums2))
class Solution:
    def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
        #使用字典作为哈希表
        table={}
        #nums1 = [1,2,2,1], nums2 = [2,2]
        for num in nums1:
            table[num] = table.get(num,0)+1
        
        #使用集合保存结果
        res = set() 
        for num in nums2:
            if num in table:
                res.add(num)
                del table[num]
        
        return list(res)

4.快乐树

力扣题目链接

编写一个算法来判断一个数 n 是不是快乐数。

「快乐数」定义为:对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和,然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。如果 可以变为  1,那么这个数就是快乐数。

如果 n 是快乐数就返回 True ;不是,则返回 False 。

例如19:

1^2 + 9^2 = 82
8^2 + 2^2 = 68
6^2 + 8^2 = 100
1^2 + 0^2 + 0^2 = 1

class Solution:
    def isHappy(self, n: int) -> bool:
        res = [] # res=set()
        while True:
            n = self.get_sum(n)
            if n==1:
                return True
            if n in res:
                return False
            else:
                res.append(n) #res.add(n)

    def get_sum(self,n:int) -> int:
        n_str = str(n)
        sum = 0
        for i in n_str:
            sum += int(i)**2
        return sum

5.两数之和

力扣题目链接

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

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。

class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        #使用字典作为哈希表
        record = {}
        for idx,val in enumerate(nums):
            #对nums中每个元素进行遍历
            #判断每个元素之前与之前的元素之和是否为target
            if target-val in record:
                return [idx,record[target-val]]
            record[val] = idx
        return []

6.四数相加

力扣题目链接

给定四个包含整数的数组列表 A , B , C , D ,计算有多少个元组 (i, j, k, l) ,使得 A[i] + B[j] + C[k] + D[l] = 0。为了使问题简单化,所有的 A, B, C, D 具有相同的长度 N,且 0 ≤ N ≤ 500 。所有整数的范围在 -2^28 到 2^28 - 1 之间,最终结果不会超过 2^31 - 1 。

思路:

  1. 首先定义一个map,key存放a和b两数的和,value存放a和b两数之和出现的次数;
  2. 遍历大A和大B数组,统计两个数组元素之和,和出现的次数,放到map中;
  3. 定义变量count,用来统计a+b+c+d=0出现的次数;
  4. 再遍历C和D数组,找到如果0-(c+d)出现在map中的情况,就用count把map中对应的value次数统计出来。
  5. 最后返回count就可以了。
class Solution:
    def fourSumCount(self, nums1: List[int], nums2: List[int], nums3: List[int], nums4: List[int]) -> int:
        map={}

        for i in nums1:
            for j in nums2:
                if i+j in map:
                    map[i+j] += 1
                else:
                    map[i+j]=1
        count = 0

        for i in nums3:
            for j in nums4:
                targrt = 0-i-j
                if targrt in map:
                    count+=map[targrt]
        return count

时间复杂度:O(n^2)

空间复杂度:O(n^2)

7.赎金信

力扣题目链接

给定一个赎金信 (ransom) 字符串和一个杂志(magazine)字符串,判断第一个字符串 ransom 能不能由第二个字符串 magazines 里面的字符构成。如果可以构成,返回 true ;否则返回 false。

(题目说明:为了不暴露赎金信字迹,要从杂志上搜索各个需要的字母,组成单词来表达意思。杂志字符串中的每个字符只能在赎金信字符串中使用一次。)

class Solution:
    def canConstruct(self, ransomNote: str, magazine: str) -> bool:
        #只有26个字母,使用数组即可
        str_count = [0]*26
        for st in ransomNote:
            str_count[ord(st) - ord('a')] += 1
        for st in magazine:
            str_count[ord(st) - ord('a')] -= 1
        

        #magazine的字母可以比ransomNote的字母多,所以只要把str_count都变成0或者负数就可以
        for i in str_count:
            if i>0:
                return False
        return True

8.三数之和

力扣题目链接

给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。

注意: 答案中不可以包含重复的三元组。

思路:哈希解法:两层for循环可以确定a+b,再利用0-a-b来判断c,主要问题是去重,需要重新定义一个变量并且判断是否重复,非常费时且容易超时。

三指针法:先排序,使用三个指针遍历所有可能性

class Solution:
    def threeSum(self, nums: List[int]) -> List[List[int]]:
        res = []
        nums.sort() #从小到大排序
        n=len(nums)
        #三指针变量所有情况:cur left right
        for cur in range(n):
            if nums[cur]>0:  #最小的大于0,那么就不可能和等于0
                return res

            if cur>0 and nums[cur]==nums[cur-1]: #相同的数就不需要再遍历了(去重)
                continue
            left = cur+1
            right = n-1
            while left<right:
                total = nums[cur]+nums[left]+nums[right]
                if total>0:
                    right-=1
                elif total<0:
                    left+=1
                else:
                    res.append([nums[cur],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
        return res

时间复杂度O(n^2)

空间复杂度O(1)

9.四数之和

力扣题目链接

题意:给定一个包含 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] 

思路:与三数之和一样,先排序,然后使用四个指针遍历所有情况,并且在遍历过程中去掉重复的组合

class Solution:
    def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
        nums.sort()
        res = []
        n = len(nums)
        for i in range(n):
            if nums[i]>target:
                return res
            if i>0 and nums[i]==nums[i-1]: #对i去重
                continue
            for j in range(i+1,n):
                if j>i+1 and nums[j]==nums[j-1]:#对j去重
                    continue
                left = j+1
                right = n-1 #四个指针i,j,left,right
                while left<right:
                    total = nums[left]+nums[right]+nums[i]+nums[j]
                    if total>target:
                        right-=1
                    elif total<target:
                        left+=1
                    else:
                        res.append([nums[i],nums[j],nums[left],nums[right]])
                        #对left 和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

        return res

10.总结

一般来说,哈希表是用来判断一个元素是否出现在集合里。

常用的三种哈希结构:

数组:固定了数值的长度,比如固定的26个字母

集合(set):计算两个列表的交并差

映射/字典(map):最常用

其他:三数之和四数之和需要去重时,哈希法去重很麻烦,不如使用指针法,先对数组进行sort排序,使用指针跳过下一个相同的数实现去重。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值