一、哈希表理论基础
建议:了解哈希表的内部实现原理,哈希函数,哈希碰撞,以及常见哈希表的区别,数组,set 和map。什么时候想到用哈希法:当我们遇到了要快速判断一个元素是否出现集合里的时候,就要考虑哈希法。 这句话很重要,在做哈希表题目都要思考这句话。
文章讲解:代码随想录
1. 哈希表
what:哈希表是根据关键码的值而直接进行访问的数据结构。
比如数组就是一张哈希表,关键码就是数组的索引下标,通过下标直接访问数组中的元素。
when:当需要判断一个元素是否在集合中出现的时候,需要考虑哈希法。
2. 哈希函数
哈希函数将学生的名字映射到哈希表上为哈希表的索引,然后可以通过索引下标快速找到同学的名字。
哈希函数通过hashcode将名字转化为数值,有特定的编码格式。
如果hashcode映射得到的数值大于哈希表的大小了,则对其取模;
如果学生数量超过哈希表的大小了,此时就算哈希函数计算的再均匀,也避免不了会有几位学生的名字同时映射到哈希表同一个索引下标的位置。--> 哈希碰撞
3. 哈希碰撞
不同对象映射到了同一索引位置,称为哈希碰撞。
解决方法:拉链法、线性探测法。
(1)拉链法:将冲突的元素存放在链表当中,就可以通过索引找到。
拉链法就是寻找合适内存大小的数组,这样就不会因为数组内存空值多而浪费内存空间,也不会因为链表过长而导致查找浪费时间。
(2)线性探测法
使用线性探测法必须保证tablesize大于datasize,利用哈希表的空位来解决哈希碰撞。例如两个对象被映射到了同一位置,那么需要寻找该位置的下一个空位置来存放冲突数据。
4. 常见的三种哈希表结构
当想要使用哈希表解决问题时,一般选用以下三种数据结构:
(1)数组
(2)集合(set)
(3)映射(map)
二、242.有效的字母异位词
建议: 这道题目,可以感受到 数组 用来做哈希表 给我们带来的遍历之处。题目链接/文章讲解/视频讲解: 代码随想录
1. 看到这道题的第一想法
分别遍历两个字符串,然后对每个字符串中的元素进行计数,如果两个字符串的每个元素计数相等,那么就是有效的字母异位词,返回true,否则返回false。
2. 看完代码随想录的想法
(1)方法1:
A. 因为字母一共最多有26个,所以直接定义一个长度为26的数组,用于每个字母计数;
B. 分别遍历s、t字符串:通过ord函数返回字符串中对应字符的ASCII码,并与字符"a"的ASCII码做减法运算,得到的数值正好对应26个字母的顺序下标,然后每循环一次,就为对应的字母出现的频数增加1。
for i in s:
record[ord(i)-ord('a')] += 1
接下来,遍历t字符串,只需要在遍历完s字符串的record数组基础上,遍历一遍t字符串,并向对应的字母位置进行-1操作。
C. 最后,对record数组进行遍历,一旦遇到数组元素(频数)不等于0的,直接返回False;而循环完成后发现record全部为0,则说明s和t是有效的字母异位词,返回True即可。
(2)方法2:
引入collections中的Counter函数:Counter函数是对用于统计可哈希对象(例如列表、元组、字符串等)的元素出现的次数。注意:最后返回的是字典K-V形式。
class Solution:
def isAnagram(self, s: str, t: str) -> bool:
from collections import Counter
a=Counter(s)
b=Counter(t)
return a==b
3. 实现过程中遇到的困难及解决
(1)不会定义数组,不会将数组初始化全部为0;
(2)不清楚如何对每个字符串循环它的元素并为每个元素进行计数:这里用到了ASCII码,不需要去记住26个字母的ASCII码,只需要直到26个字母的ASCII码是连续递增,相隔为1的,因此只需要用ord函数取ASCII码与a的ASCII码相减,刚好得到26个字母的索引下标[0,25]。
(3)这道题目的方法1 巧妙地将暴力解法双层嵌套循环结构,改成两个循环结构,无嵌套,时间复杂度为O(n)。
三、349. 两个数组的交集
建议:本题就开始考虑 什么时候用set 什么时候用数组,本题其实是使用set的好题,但是后来力扣改了题目描述和 测试用例,添加了 0 <= nums1[i], nums2[i] <= 1000 条件,所以使用数组也可以了,不过建议大家忽略这个条件。 尝试去使用set。题目链接/文章讲解/视频讲解:代码随想录
1. 看到题目的第一想法
分别对数组里面的数字出现的频数计数,然后取出频数在两个数组都不为0的数字,将其放到一个新数组进行输出。
2. 看完代码随想录的想法
(1)首先定义两个数组用于对数组里面的数字出现的频数进行计数,分别记为record1和record2,并且他们的长度根据nums1和nums2的最大上限定义;然后定义一个空数组,用于存放最终结果。
(2)分别对两个数组中的数字进行计数:
for i in range(len(nums)):
record1[nums1[i]]+=1
(3)频数统计完毕后,得到的数组应该是record的下标正是统计的数字,而它的值就是频数;
(4)关键的一步:要同时遍历record1和record2,不能嵌套循环,否则无法做到同时遍历这两个数组,那么只能下标统一从0开始一直到1001,同时遍历两个数组,让对应的值相乘,只要不为0,说明这个数字在两个数组中均出现过,并且出现过几次不重要,最后只返回一次这个数字即可;并将这个数字添加至结果集合中。
(5)最终要返回这个集合。
四、202. 快乐数
建议:这道题目也是set的应用,其实和上一题差不多,就是套在快乐数一个壳子
题目链接/文章讲解:代码随想录
1. 看到这道题的第一想法
首先要将输入的n拆分成多个数字存放进数组中,也就是将不同位的数字转化为单个数字组成的数组;然后对数组每个数字平方后进行循环加和,直到同样的加和出现超过一次,说明陷入了死循环,直接返回False;否则一直到加和对于1,说明是快乐数,返回True。
2. 看完代码随想录的想法
(1)定义一个空数组record,用于存放平方后的加和;
(2)因为题目中说可能会存在无限循环(当加和sum出现超过一次时,就说明进入了死循环),因此以输入的n不在所用于记录的数组record中为进入循环的条件;
(3)将输入的数字存入record中,然后将输入的数字n转化为string类型,便于后续对它的每个字符进行遍历;
(4)初始化加和sum = 0;
(5)遍历数字n转化后的字符串,在循环中将字符串用int函数强制转化为整型用于进行平方和运算;
(6)对每个sum进行判断,如果等于1则直接输出true;如果一直循环最后退出sum一直不为1的话说明不是快乐数,输出False。
五、1. 两数之和
建议:本题虽然是 力扣第一题,但是还是挺难的,也是 代码随想录中 数组,set之后,使用map解决哈希问题的第一题。建议大家先看视频讲解,然后尝试自己写代码,在看文章讲解,加深印象。
题目链接/文章讲解/视频讲解:代码随想录
1. 看到题目的第一想法
暴力法:遍历nums数组,并在第一层循环的基础上,嵌套一次range(i+1,len(nums))的循环,然后找到对应下标的元素,如果相加等于target,则返回下标数组。
特别注意:第二层循环的起点是i+1, 而不是1!!
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
for i in range(len(nums)):
for j in range(i+1,len(nums)):
if nums[i]+nums[j]==target:
return [i,j]
2. 看完代码随想录的想法
(1)定义一个集合record = set()
(2)利用enumerate函数,将nums转化为组合为一个索引序列,同时列出数据和数据下标;然后对它的index和value进行遍历,如果target-value已经存在于集合中,那么就返回他俩的下标,如果没有存在于集合,则将值添加进集合。
3. 实现过程中遇到的问题及解决
(1)Python3集合:
A. 定义:集合(set)是一个无序的不重复元素序列。集合中的元素不会重复,并且可以进行交集、并集、差集等常见的集合操作。
B. 集合创建:可以使用set()函数或者大括号{}(中间元素用逗号分开)。
特别注意:创建空集合只能使用set()函数,不可以用大括号,大括号是用于创建空字典!!
C.判断元素在集合存在:
(2)Counter()函数,统计可迭代对象中各元素重复的次数,需要from collections import Counter,返回一个Counter对象,可以直接dict构建字典,也可以for k,v in Counter来遍历访问。