哈希表当时数据结构好像没有学很多,看了基础知识部分,感觉是数据库的时候学的,数据库存储方式有哈希表,树等,不同组织方式查询效率不同。
基础知识基本上记得,但是随想录中的具体语言的set,map等方法的底层实现不了解,还是要对某一门语言精通,以后还要学习(买书学学C++)。
哈希表基础
哈希表
- 定义:哈希表是根据关键码的值而直接进行访问的数据结构
- 解决问题:一般哈希表都是用来快速判断一个元素是否出现集合里
- 优点:查询操作只需要**O(1)**的时间复杂度
哈希函数
- 定义:将关键码直接映射为哈希表索引的函数
- hash函数映射原理:
通过hashCode把名字转化为数值,一般hashcode是通过特定编码方式,可以将其他数据格式转化为不同的数值,这样就把学生名字映射为哈希表上的索引数字了。
为了保证映射出来的索引数值都落在哈希表上,我们会在再次对数值做一个取模的操作,就要我们就保证了学生姓名一定可以映射到哈希表上了。(防止hashCode得到的数值大于哈希表的大小,即tableSize)
哈希碰撞
- 解决现象:元素数量大于哈希表大小,不同元素同时映射到同一索引下标的位置
- 解决方法:拉链法和线性探测法
1 拉链法
定义:发生冲突元素存在链表中
注意:选择适当的哈希表的大小,使得不会因为数组空值浪费大量内存,也不会因为链表太长而在查找上浪费太多时间
2 线性探测法
定义:依靠哈希表中的空位解决碰撞问题,如果冲突,则向下找一个空位置去放冲突元素。
注意:使用线性探测法,一定要保证tableSize大于dataSize
常见的三种哈希结构
哈希法解决问题常用数据结构:
- 数组:数据量较小,数据分布均匀用,数组快,优先用
- set(集合):无重复的一组key值,数据量较大,数据分散时用,key不能更改
- map(映射):有key value时用,key不能更改
1 set(集合)
- C++中set提供三种数据结构,底层实现以及优劣如下:
std::unordered_set底层实现为哈希表
std::set 和std::multiset 的底层实现是红黑树,红黑树是一种平衡二叉搜索树,所以key值是有序的,但key不可以修改,改动key值会导致整棵树的错乱,所以只能删除和增加。
- 当我们要使用集合来解决哈希问题的时候,优先使用unordered_set,因为它的查询和增删效率是最优的,如果需要集合是有序的,那么就用set,如果要求不仅有序还要有重复数据的话,那么就用multiset。
2 map(映射)
- C++中set提供三种数据结构,底层实现以及优劣如下:
std::unordered_map 底层实现为哈希表
std::map 和std::multimap 的底层实现是红黑树。同理,std::map 和std::multimap 的key也是有序的(这个问题也经常作为面试题,考察对语言容器底层的理解)。
- 在map 是一个key value 的数据结构,map中,对key是有限制,对value没有限制的,因为key的存储方式使用红黑树实现的。
总结
- 当我们遇到了要快速判断一个元素是否出现集合里的时候,就要考虑哈希法
- 哈希法也是牺牲了空间换取了时间,因为我们要使用额外的数组,set或者是map来存放数据,才能实现快速的查找
LeetCode242-有效字母的异位词
题目描述:给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。
注意:若 s 和 t 中每个字符出现的次数都相同,则称 s 和 t 互为字母异位词。
输入: s = “anagram”, t = “nagaram”
输出: true
题目链接:https://leetcode.cn/problems/valid-anagram/
解题思路
- map解决
- 数组哈希
1 map解决
- 用两个map分别存储s,t中的字符及相应个数
- 比较两个map中的key,value是否完全一致
python写法1
class Solution(object):
def isAnagram(self, s, t):
"""
:type s: str
:type t: str
:rtype: bool
"""
hashs = dict();
hasht = dict();
for ch in s:
if ch in hashs:
hashs[ch] += 1;
else:
hashs[ch] = 1;
for ch in t:
if ch in hasht:
hasht[ch] += 1;
else:
hasht[ch] = 1;
if len(hashs) < len(hasht):
temp = hashs;
hashs = hasht;
hasht = temp;
for key,value in hashs.items():
if key in hasht and hasht[key] == value:
continue;
else:
return False;
return True;
class Solution:
def isAnagram(self, s: str, t: str) -> bool:
hashmap = dict()
# 将s中所有的字母加入hash表
for i in s:
if i not in hashmap:
hashmap[i] = 1
else:
hashmap[i] += 1
# 判断 j 中的元素是否在hash表出现
for j in t:
if j not in hashmap:
hashmap[j] = 1
else:
hashmap[j] -= 1
for k in hashmap:
if hashmap[k] != 0:
return False
return True
python写法2
- collections 中的 defaultdict 方法
class Solution:
def isAnagram(self, s: str, t: str) -> bool:
from collections import defaultdict
s_dict = defaultdict(int)
t_dict = defaultdict(int)
for x in s:
s_dict[x] += 1
for x in t:
t_dict[x] += 1
return s_dict == t_dict
注意:defaultdict 与 dict 的区别
① defaultdict 括号里必须标明类型
② 如果访问字典dict中不存在的键,会引发KeyError异常,所以我们要先处理没有元素的情况;collections 库中的 defaultdict 方法则可以直接写加的操作。
python写法3
- collections 中的 Counter 方法
class Solution(object):
def isAnagram(self, s: str, t: str) -> bool:
from collections import Counter
a_count = Counter(s)
b_count = Counter(t)
return a_count == b_count
2 数组哈希
- 把字符映射到数组,也就是哈希表的索引下标上(字符a到字符z的ASCII是26个连续的数值,所以字符a映射为下标0,相应的字符z映射为下标25)
- 遍历字符串s时,将 s[i] - ‘a’ 所在的元素做+1操作
- 检查字符串t中是否出现了这些字符,在遍历字符串t的时候,对t中出现的字符映射哈希表索引上的数值再做-1的操作
- 最后,record数组如果有的元素不为零0,说明字符串s和t一定是谁多了字符或者谁少了字符,return false;如果record数组所有元素都为零0,说明字符串s和t是字母异位词,return true
class Solution:
def isAnagram(self, s: str, t: str) -> bool:
record = [0] * 26
for i in s:
#并不需要记住字符a的ASCII,只要求出一个相对数值就可以了
record[ord(i) - ord("a")] += 1
for i in t:
record[ord(i) - ord("a")] -= 1
for i in range(26):
if record[i] != 0:
#record数组如果有的元素不为零0,说明字符串s和t 一定是谁多了字符或者谁少了字符。
return False
return True
3 心得体会
没有想到数组hash法,数据量小的话哈希法要优先考虑数组,数组速度快。
哈希法:重复元素记录次数。
暴力解法没写,我感觉写起来还比较麻烦,感觉内循环要做一些删除操作才行,二刷可以写一下。
LeetCode349-两个数组的交集
题目描述:给定两个数组 nums1 和 nums2 ,返回它们的交集 。输出结果中的每个元素一定是唯一的。我们可以不考虑输出结果的顺序 。
输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2]
题目链接:https://leetcode.cn/problems/intersection-of-two-arrays/
解题思路
- set解决
- 数组解决
- 暴力解法
- map解决
1 set解决
- 将两个列表均转化为set,再做交集即可
class Solution(object):
def intersection(self, nums1, nums2):
"""
:type nums1: List[int]
:type nums2: List[int]
:rtype: List[int]
"""
l = [];
nums1 = set(nums1);
nums2 = set(nums2);
for key in nums1:
if key in nums2:
l.append(key);
return l;
class Solution:
def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
return list(set(nums1) & set(nums2)) # set交集操作
2 数组解决
- 题目中有元素值<1000的限制,所以可以用数组方法,更快
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
unordered_set<int> result_set; // 存放结果,之所以用set是为了给结果集去重
int hash[1005] = {0}; // 默认数值为0
for (int num : nums1) { // nums1中出现的字母在hash数组中做记录
hash[num] = 1;
}
for (int num : nums2) { // nums2中出现话,result记录
if (hash[num] == 1) {
result_set.insert(num);
}
}
return vector<int>(result_set.begin(), result_set.end());
}
};
3 暴力解法
- 时间复杂度:O(n^2)
class Solution:
def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
res = []
for i in range(len(nums1)):
for j in range(len(nums2)):
if nums1[i] == nums2[j] and nums2[j] not in res:
res.append(nums2[j])
return res
4 map解决
- 原理与set相同,只不过map存储了value
- 注意dict() 是按照出现元素的顺序进行存储
class Solution:
def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
# 哈希法
record = dict()
res = []
# 若 nums1 = [3, 6, 2, 2, 1]
for i in range(len(nums1)):
if nums1[i] not in record:
record[nums1[i]] = 1
print(record) # {3: 1, 6: 1, 2: 1, 1: 1}
for j in range(len(nums2)):
if nums2[j] in record and nums2[j] not in res:
res.append(nums2[j])
return res
5 心得体会
这道题想到hash法并不难,因为要判断一个元素是否在另一个集合中,难点在于数据结构的选择。
哈希表数据结构选择:
数组:适用于数据量小且数据分布均匀的情况,此情况下数组速度更快,因为它不需要像set,map那样算哈希函数,也不用转成内部存储的结构。
set:适用于数据量大或者数据量小且分散的情况。python里set底层就是hash,可以不用像c++,java一样考虑其他set的实现方法。
map:需要 key value 去记录次数的情况。
LeetCode202-快乐数
题目描述:编写一个算法来判断一个数 n 是不是快乐数。
「快乐数」 定义为:
对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
如果这个过程 结果为 1,那么这个数就是快乐数。
如果 n 是 快乐数 就返回 true ;不是,则返回 false 。
输入:n = 19
输出:true
解释:
1 2 + 9 2 = 82 1^2 + 9^2 = 82 12+92=82
8 2 + 2 2 = 68 8^2 + 2^2 = 68 82+22=68
6 2 + 8 2 = 100 6^2 + 8^2 = 100 62+82=100
1 2 + 0 2 + 0 2 = 1 1^2 + 0^2 + 0^2 = 1 12+02+02=1
题目链接:https://leetcode.cn/problems/happy-number/
解题思路
- 熟悉个位数求和
- 无限循环:出现重复元素,即当出现重复元素时不是快乐数
class Solution(object):
def isHappy(self, n):
"""
:type n: int
:rtype: bool
"""
def getSum(n):
sum = 0;
while(n):
x = n % 10;
sum += x**2;
n = n // 10;
return sum;
sum = n;
s = set();
while(1):
sum = getSum(sum);
if sum == 1:
return True;
if sum not in s:
s.add(sum);
else:
return False;
心得体会
主要是个位数求和操作要熟悉,加上理解循环终止条件,无限循环的意思。
LeetCode1-两数之和
题目描述:给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1]
题目链接:https://leetcode.cn/problems/two-sum/
解题思路
- 暴力求解
- map解决
1 暴力求解
- 两层循环
class Solution(object):
def twoSum(self, nums, target):
"""
:type nums: List[int]
:type target: int
:rtype: List[int]
"""
res = [];
for i in range(len(nums)):
for j in range(len(nums)):
if i != j and nums[i] + nums[j] == target:
res.append(i);
res.append(j);
return res;
2 map解决
- 因为要存元素和元素下标,所以要用map
- 注意字典是没有重复元素的,如果相等后面的元素会覆盖前面的元素
# map存所有元素,需要判断下标是否相同
class Solution(object):
def twoSum(self, nums, target):
"""
:type nums: List[int]
:type target: int
:rtype: List[int]
"""
res = [];
s = dict();
for i in range(len(nums)):
s[nums[i]] = i;
for i in range(len(nums)):
if target - nums[i] in s.keys() and s[target - nums[i]] != i:
return i, s[target - nums[i]];
# map存遍历过的元素,不用判断下标是否相同了
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
records = dict()
for index, value in enumerate(nums):
if target - value in records: # 遍历当前元素,并在map中寻找是否有匹配的key
return [records[target- value], index]
records[value] = index # 遍历当前元素,并在map中寻找是否有匹配的key
return []
3 心得体会
需要判断一个值是否在一个集合里,用哈希法;
当需要存对应的两个值时,选择map数据结构。
总结
要注意哈希法的使用条件:判断一个元素是否出现在另一个集合中/判断重复出现元素。注意数组、set、map的适用情况。