一、什么是哈希表
哈希表也叫散列表,哈希表是一种数据结构,它提供了快速的插入操作和查找操作,无论哈希表总中有多少条数据,插入和查找的时间复杂度都是为O(1),因为哈希表的查找速度非常快,所以在很多程序中都有使用哈希表,例如拼音检查器。
哈希表也有自己的缺点,哈希表是基于数组的,我们知道数组创建后扩容成本比较高,所以当哈希表被填满时,性能下降的比较严重。
哈希表采用的是一种转换思想,其中一个中要的概念是如何将「键」或者「关键字」转换成数组下标?在哈希表中,这个过程有哈希函数来完成,但是并不是每个「键」或者「关键字」都需要通过哈希函数来将其转换成数组下标,有些「键」或者「关键字」可以直接作为数组的下标。我们先来通过一个例子来理解这句话。
我们上学的时候,大家都会有一个学号「1-n号」中的一个号码,如果我们用哈希表来存放班级里面学生信息的话,我们利用学号作为「键」或者「关键字」,这个「键」或者「关键字」就可以直接作为数据的下标,不需要通过哈希函数进行转化。如果我们需要安装学生姓名作为「键」或者「关键字」,这时候我们就需要哈希函数来帮我们转换成数组的下标。
总之,哈希表是一个便于记录和高效查询的数据结构,插入和查找的时间复杂度都是为O(1), 使用时可以粗略的认为哈希表记录的键是问题的条件,值是问题的解,通过不断插入和搜索最终获得所需答案。
二、哈希表解题实例
1. 两数之和
题目描述
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。
示例
输入:nums = [2,7,11,15], target = 9 输出:[0,1] 解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。
思路
暴力思路是最容易想到的方法是枚举数组中的每一个数 x,寻找数组中是否存在 target - x。
当我们使用遍历整个数组的方式寻找 target - x 时,需要注意到每一个位于 x 之前的元素都已经和 x 匹配过,因此不需要再进行匹配。而每一个元素不能被使用两次,所以我们只需要在 x 后面的元素中寻找 target - x。
但是这种思路时间复杂度太高,我们使用哈希表法解决本题:
注意到暴力方法的时间复杂度较高的原因是寻找 target - x 的时间复杂度过高。因此,我们需要一种更优秀的方法,能够快速寻找数组中是否存在目标元素。如果存在,我们需要找出它的索引。
使用哈希表,可以将寻找 target - x 的时间复杂度降低到从 O(N)降低到 O(1)。
这样我们创建一个哈希表,对于每一个 x,我们首先查询哈希表中是否存在 target - x,然后将 x 插入到哈希表中,即可保证不会让 x 和自己匹配。
代码
# 两数之和 def twoSum(nums, target): h = dict() for i in range(len(nums)): if target - nums[i] in h: return [i, h[target - nums[i]]] else: h[nums[i]] = i
2. 整数转罗马字母
题目描述:
罗马数字包含以下七种字符: I, V, X, L,C,D 和 M。
字符 数值
I 1
V 5
X 10
L 50
C 100
D 500
M 1000
例如, 罗马数字 2 写做 II ,即为两个并列的 1。12 写做 XII ,即为 X + II 。 27 写做 XXVII, 即为 XX + V + II 。
通常情况下,罗马数字中小的数字在大的数字的右边。但也存在特例,例如 4 不写做 IIII,而是 IV。数字 1 在数字 5 的左边,所表示的数等于大数 5 减小数 1 得到的数值 4 。同样地,数字 9 表示为 IX。这个特殊的规则只适用于以下六种情况:
I 可以放在 V (5) 和 X (10) 的左边,来表示 4 和 9。
X 可以放在 L (50) 和 C (100) 的左边,来表示 40 和 90。
C 可以放在 D (500) 和 M (1000) 的左边,来表示 400 和 900。
给你一个整数,将其转为罗马数字。
思路
首先建立题目中已经给定的数值与罗马字符对应的哈希表,然后再此表中根据规律找到解。
我们用来确定罗马数字的规则是:对于罗马数字从左到右的每一位,选择尽可能大的符号值。对于 140,最大可以选择的符号值为 C=100。接下来,对于剩余的数字 40,最大可以选择的符号值为 XL=40。因此,140 的对应的罗马数字为C+XL=CXL。
代码
def intToRoman(num): # 构建相应哈希表 h = dict() h[1] = 'I' h[5] = 'V' h[10] = 'X' h[50] = 'L' h[100] = 'C' h[1000] = 'M' h[500] = 'D' h[4] = 'IV' h[9] = 'IX' h[40] = 'XL' h[90] = 'XC' h[400] = 'CD' h[900] = 'CM' if num in h.keys(): return h[num] # 答案 res = '' # 模拟,在哈希表中找到最大值,num减去最大值,结果res加上哈希表中最大值对应的字符 while num != 0: for i in sorted(h, reverse=True): if num >= i: num -= i res += h[i] break return res
3.二倍数对数组
题目描述
给定一个长度为偶数的整数数组 arr,只有对 arr 进行重组后可以满足 “对于每个 0 <= i < len(arr) / 2,都有 arr[2 * i + 1] = 2 * arr[2 * i]” 时,返回 true;否则,返回 false。
示例
输入:arr = [3,1,3,6] 输出:false 输入:arr = [4,-2,2,-4] 输出:true 解释:可以用 [-2,-4] 和 [2,4] 这两组组成 [-2,-4,2,4] 或是 [2,4,-2,-4]
思路
哈希表中按小到大排列记录出现个数,遍历哈希表,每个数都和它的两倍的个数或者它的一半的个数相等,当前个数小于0说明不够用,不能组合,如果不存在当前数的两倍或一半也不能组合,否则当前数的两倍或一半的数减去当前数的个数。
代码
# 二倍数对数组 哈希表的应用 def canReorderDoubled(arr): ant = Counter(arr) if ant[0] % 2 != 0: return False for i in sorted(ant, key=abs): if ant[2 * i] < ant[i]: return False else: ant[2 * i] -= ant[i] print(ant) return True