力扣哈希表篇——代码随想录

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

 


一、介绍哈希表

1.什么是哈希表?

        哈希表也是一种常见的数据结构,或者说也是一种思想,之前我们所探讨的数组,链表,主要是为了存储数据,而哈希表主要是为了查询某个数据是否在某一数据结构中,重点在于查询。

2.哈希表的类别

        在python中,以我现在的知识体系,我所知道的哈希表主要分为三种,列表(或者说数组),集合,字典。三者的区别呢,列表就完全可以当成数组,集合无序(这里的无序是说输出时的顺序和存储的顺序不一定一样),字典的话,分为key和value,两者一一对应。怎么说呢,这些也是python中存储数据的结构,但是如果用一些哈希算法来解释,好像就可以当成哈希表,其实我也不太清楚具体关系。

3.哈希算法

        这个感觉掌握的相对扎实一些吧,哈希算法主要是为了解决数据存储时冲突的问题。就比如我们有一个数组,遍历的时间复杂度是O(n),但是,如果如果把它改造成哈希表,我们查找某一元素的时间复杂度就是O(1)。那么具体怎么改造,怎么存储呢,下面我来具体说明下。

        还是用最简单的数组来举例,我们现在有一个数组,长度为8,那数组的下标就是0-6.现在我们有五个数,7,10,4,14,3。我们要把他们存到数组里,如果简单的直接存,当然可以,但是这时候比如我们要查找8,那就需要从数组下标为0的位置开始,依次向后遍历,很慢,这时候,我们就可以利用构造哈希算法来解决。最简单的哈希算法就是取模,就比如这道题,我们可以把每个数据所要存储位置的下标,用这个数对k取余得到,具体就是(比如K为7,貌似最好用小于数组长的最大质数)。7应该存储到哪呢,7%7 = 0,7就存到nums[0],10%7=3,10就存到nums[3],4%7 = 4,4就存储到nums[4],14%7=0,但是注意,nums[0]已经被前面的7给占据了,那这个时候我们怎么办呢?通常有两种方法,拉链法和开放定址法。先说比较简单的拉链法,就是这个哈希数组我们在创建的时候,他的每一个位置存储的是一个链表节点,这样我们就可以在0号位置,创建一个新节点,挂载到已占据位置节点的后边。再说开放定址法,我们先说其中最简单的线性探索法,就比如这道题,我们本来要把14存到下标为0的位置,但是0号位置被占用了,那我们就往后走以为,发现1号位置没有被占用,好,那我们就存储到一号位置,其他的开放定址法还有很多,此处就不一一赘述。然后就是存3,3%7=3,但是3号位置也已经被10占用了,根据刚才的线性探索法法,那我们应该去4号位置看看,但是4号被4占用了,那我们就接着再走,看5号位,5号位值没有被占用,那我们就存储到5号位。

        存完了,但是这只是哈希算法的一半,不要忘了我们最开始的问题,怎么在O(1)的时间复杂度来查找元素,其实也差不多,相当于逆向思维。下面我依然一一概述下。查7,因为7%7=0,所以我们看看nums[0]是否为7,为7,查找成功,但是有非常重要的易错点,如果nums[0]不为7呢,难道数组里就一定没有7吗,不一定,为什么,这就要考虑到之前我们解决哈希冲突时的线性探索法。那我们就拿查3举例子,3%7=3,但是nums[3] = 10,那我们就看nums[4]有没有存数据,如果没存,那此时才能说3一定不在数组中,因为如果在的话,根据线性探索法3应该存到4号位(前提,一定没有对哈希数组进行元素删除操作),此时nums[4]也存了,为4,依然不为3,那我们就接着向后查,nums[5]为3,这时候我们成功查找到了元素3。

        说的不是太清楚,总的来说我们要首先先了解列表,集合,字典的相关特性,之后理解怎么把数组改造成哈希数组,并且注意改造时的哈希冲突问题,我们是利用拉链法还是开放定址法解决。最后就是查找时一定注意,如果在原本的下标位置查找不到,那不一定不存在,有可能是因为我们哈希冲突,被散列到别的位置了,只有当按照特定散列算法查找不到的时候,才能说不存在。


二、题目分析与心得

(1).242. 有效的字母异位词 - 力扣(LeetCode)

9425355c40a94a198a69d13d270e9d51.png

分析:最直接的思路,直接把两个字符串排序为列表,然后比较两个列表是否相等,也就是

return sored(s) == sored(t)

但是既然我们这篇是在讲哈希表,那怎么使用哈希表来解决问题呢?首先我们要先考虑使用哪种哈希结构,列表?集合?字典?因为此题字符串的元素都是小写字母,也就是说元素的个数是固定的,所以我们使用列表(数组)比较方便。那我们怎么散列呢?可以使用每个字幕的ascll码与a的ascll码的差值来散列,字符串s对哈希表的值进行加,字符串t对哈希表的数值进行减,最后看哈希表每一个位置的值是否都是0即可。

代码实现:

class Solution:
    def isAnagram(self, s: str, t: str) -> bool:
        result = [0] * 30
        for i in s:
            result[ord(i) - ord('a')] += 1
        for i in t:
            result[ord(i) - ord('a')] -= 1
        for i in result:
            if i != 0:
                return False
        return True

心得:此题是数组哈希结构的应用,后边还会接触集合,字典,主要一定要注意,哈希表最大的好处就是可以快速查询到某个数据是否被存储,还有怎么进行散列。


(2).349. 两个数组的交集 - 力扣(LeetCode)

558e7289fd7541e2a6890f4770bb925a.png

87a60ddac48c4b2a8a725191336a2eff.png

 

 

分析:此题要抓住两个要点,问的是元素是否出现过,数组中的元素种类有限,那我们就可以使用列表来做哈希结构,具体实现方法与上一题类似,如果数组中没有给出[0,1000]的限制,那我们就只能使用字典了,在代码实现中,两个方法都会进行实现。此外,还有一点就是,因为我们要返回的是无重复的结果集,所以要使用集合,使用列表也可以,但是需要添加去重细节。

代码实现:

使用列表作为哈希表

class Solution:
    def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
        result = set()
        val = [0] * 1001
        for i in nums1:
            val[i - 0] = 1
        for i in nums2:
            if val[i - 0] == 1:
                result.add(i)
        return list(result)  

使用字典作为哈希表

class Solution:
    def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
        result = set()
        val = {}
        for i in nums1:
            val[i] = 1
        for i in nums2:
            if i in val.keys():
                result.add(i)
        return list(result)      

心得:注意体会什么时候用数组作为哈希表,什么时候用字典作为哈希表。当时目标元素种类有限时,可以使用数组,当目标元素种类无限或者说有限但是散列之后很分散的话,使用字典。此外,如果结果集中要求去重的话,可以使用集合,此处需要注意如果创建一个空的集合,只能使用set(),而不是{},因为{}表示的是一个空字典。


(3).202. 快乐数 - 力扣(LeetCode)

51b98a9717fe4e5a895b227aa7b2ddda.png

 

分析:此题主要明确三点问题

1.什么时候才能确定是无限循环:当在某一时刻,该数又重复出现的时候(重复出现,可以考虑哈希表)

2.用哪种哈希结构(因为在判断快乐数的时候,任何正整数都有可能出现,所以元素种类是无限的,不能使用数组,只能使用字典

3.怎么求各位的平方和(这个要对取模%和取整//操作比较熟悉)

代码实现:

class Solution:
    def isHappy(self, n: int) -> bool:
        def summ(n):
            result = 0
            while n != 0:
                val = n % 10
                n = n // 10
                result += val*val
            return result
        tmp = {}
        while n not in tmp.keys():
            if n == 1:
                return True
            tmp[n] = 1
            n = summ(n)
        return False

心得:怎么从题目中发现是要求元素是否出现过这一问题核心是比较难的,还有就是对哈希表结构的选用。


(4).1. 两数之和 - 力扣(LeetCode)

e718d277582244b59ad54f7b0f6774ae.png

 

分析:此题有两个问题需要注意

1.观察出使用哈希表的特点,也就是判断元素是否出现过,虽然此题表面上不是判断数组中某一个元素是否出现过,但是通过分析发现,我们需要判断的是target - nums[i]的值是否出现过,所以依然是哈希表的特点

2.用什么哈希表,因为数组中的数的种类无限,所以使用字典,但是一定要注意,字典的关键字和值表示的是什么,因为我们要判断的是target - nums[i]是否出现过,要求的是此值在数组中的下标,所以字典中的关键字应该是数组中元素的值,字典中的值应该是数组中的下标

代码实现:

class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        i = 0
        val = {}
        while i < len(nums):
            tmp = target - nums[i]
            if tmp in val.keys():
                return [i, val[tmp]]
            val[nums[i]] = i
            i += 1

心得:从题目当中发现哈希表的特点,如果使用字典的话,一定要分析好什么作为关键字,什么作为值。


(5).454. 四数相加 II - 力扣(LeetCode)

61d13b8c33604a429a2aa9017570902c.png

 

分析:该题和两数之和类似吧,四数相加可以看成两对两数之和,也就是说,先算nums1 + nums2的所有可能,再算nums3 + nums4的所有可能,最后再进行判断

代码实现:

class Solution:
    def fourSumCount(self, nums1: List[int], nums2: List[int], nums3: List[int], nums4: List[int]) -> int:
        def mapp(nums1, nums2):
            result = {}
            for i in nums1:
                for j in nums2:
                    val = i + j
                    if val in result.keys():
                        result[val] += 1
                    else:
                        result[val] = 1
            return result
        n = 0
        mpp1 = mapp(nums1, nums2)
        mpp2 = mapp(nums3, nums4)
        for i in mpp1.keys():
            if -i in mpp2.keys():
                n = n + mpp1[i] * mpp2[-i]
        return n

一定要注意一点,比如nums1 + nums2 = 5的可能有三种,nums3 + nums4 = -5的可能有四种,则单这种nums1 + nums2 + nums3 + nums4 = 0的情况就是3 * 4 = 12种

心得:化繁为简,依然是有点像多路归并算法,体会精髓,八数之和也迎刃而解


(6).15. 三数之和 - 力扣(LeetCode)

a385f541cd324f19a85d8cf8ae7349d7.png

 

分析:三数之和和上题最大的不同,一是要说明哪些数之和为0,二是还要去重,所以用哈希表的话很麻烦,这里采用的是双指针法。首先先把数组排序,以i=0号位值为基准开始,left=i+1,right = len(nums) - 1,计算三数之和并根据条件,left和right相互靠近,当left=right时,i再变成1,以1为基准再进行遍历。细节方面就是对i,left,right的去重操作,我也说不清楚hhh

代码实现:

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

心得:去重很烦人!!!麻了


(7).18. 四数之和 - 力扣(LeetCode)

99d0e6e2211c412988d51872e83e4d2c.png

 

分析:四数之和和三数之和有点类似,可以当作先限制k=0,然后在1到n-1位置再利用三数之和逻辑解题。最重要的还是要注意去重!!!

代码实现:

class Solution:
    def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
        result = []
        n = len(nums)
        nums.sort()
        for k in range(n):
            if nums[k] > 0 and nums[k] > target:
                break
            if k > 0 and nums[k] == nums[k - 1]:
                continue
            for i in range(k + 1, n):
                if i > k + 1 and nums[i] == nums[i - 1]:
                    continue
                left, right = i + 1, n - 1
                while left < right:
                    val = nums[k] + nums[i] + nums[left] + nums[right]
                    if val > target:
                        right -= 1
                    elif val < target:
                        left += 1
                    else:
                        result.append([nums[k], nums[i], nums[left], nums[right]])
                        while left < right and nums[right] == nums[right - 1]:
                            right -= 1
                        while left < right and nums[left] == nums[left + 1]:
                            left += 1
                        right -= 1
                        left += 1
        return result

left和right的去重操作没有变,但是注意k和i的去重逻辑。

心得:之前我们从两数之和推导出了四数相加的思路,现在我们从三数之和推导出了四数之和的解题逻辑。要善于从之前做过的题中把我精髓,之后遇到相似的题套用之前逻辑解题(虽然我还是必须要看答案才懂hhh)。再就是去重!!!


总结

1.首先是对哈希表类别的了解,列表,集合,字典,懂得他们之前的区别

什么时候用列表:元素种类有限

什么时候用集合:集合里的元素不重复,可以简单的自动去重(但是注意,集合里的元素不能是列表)

什么时候用字典:元素种类无限,或者把元素散列后很分散,注意用字典的时候,什么作为key,什么作为value

2.一些概念上的东西,比如散列算法,解决哈希冲突的算法等等

3.去重逻辑

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值