HashTable
Dictionary, Set, 例题
底层机制
set是简化版hashtable,而hashtable的本质其实是array(或者说Python里的list),因为list的本质就是array index和value的映射。所以关键问题是把key转换成index。一般分成3步:
通过key和hash function计算它的hash_number=hash(key) 这一步虽然可能有collision但是可能性比较低
index=hash_number%N, N是array size。 这一步有很大概率有collision,取决于N的大小
如果有hash collision,两种方法来解决(正文); 如果没有,list[index]=value
Dictionary 使用场景
TF-IDF (Term Frequency- Inverse Document Frequency) 统计词频时,可以用list来统计频率,但是使用list的缺点是每次更新频率的时间复杂度都是O(n), 只能遍历一遍、找到词、给词频+1; 随着单词变多,这个过程就变得低效了。
在Python中,可以使用dictionary的结构来实现高效的“更新词频“的需求。
Time Complexity: O(n)
Space Complexity: O(1) 如果我们不算return值的话,那没有额外空间的消耗
Example: 查找高频
如果要找到频率最高的word,分两步1. 遍历,找到最大的值; 2. 找到list,返回
List comprehension的写法:[expression for elem in collection if condition]
相类似的字典也有dictionary comprehension,只需要把[] 换成{}
{key_expression: value_expression for value in collection if condition}
Example:合并大小写的频次
mydic = {'a': 10, 'b': 6, 'A': , 'B': }
get():
Python 字典(Dictionary) get() 函数返回指定key的value,如果值不在字典中返回默认值。
语法
参数
key -- 字典中要查找的键。
default -- 如果指定键的值不存在时,返回该默认值。
返回值
返回指定键的值,如果值不在字典中返回默认值None。
实例
以上实例输出结果为:
第一步:先把词频算出来
第二步:sorting,按照单词的频率排序,而且是频率加负号,按负的频率排序,频率越大顺序越小。 或者也可以用key = lambda kv: kv[1], reverse=True
sorted的syntax: sorted(iterable, key=key, reverse=reverse)
再次注意,dictionary没有顺序!所以不能直接输出前x个。
前面部分是O(n), sort部分都是O(nlogn), 最后一步还是O(n), 最终就是O(nlogn)
这里输出的是dict_keys
如果使用collections这个package,然后把items转成list 可以通过。
另外还可以用heap做这道题,也就是把整理好的dict一个个push到max heap或者min heap里。因为Python内置函数的heap是一个min heap,如果想用它做max heap,在每个元素push进去的时候对value取负号。然后在有元素可pop并且pop出来的元素数量<=k的前提下一个个pop元素, append到result上,然后反过来输出。
Example: Palindromic Testing
给一个单词,测试它能否被写成回文数的形式。能满足回文的条件,那就必须满足除了中间的数可能一个外,其余的都是偶数个;换句话说,奇数的字母只有1个,其余都是偶数个。所以我们需要建立字母到频率的字典,计算频率的个数。
Time Complexity: O(n)
Space Complexity: O(C) C是distinct chars in string
Given an array nums and a target value k, find the total number of subarrays that sums to k.
Nums = [1,6,5,2,3,4,0] k=7, return 4 prefixS = [0,1,7,12,14,17,21,21] prefixS[j]-prefixS[i]==target <--->prefixS[j]-target==prefixS[i]
O(n) time, O(C) space, c is the number of distinct values
Example: Nearest repeated entries in an array
重复的单词最近的距离 key是单词,value是位置,距离只需要减一下、更新为距离。
Time Complexity: O(n)
Space Complexity: O(C) C是distinct entries in arr
return index, O(n) time and O(n) space
Example: Nearest repeated entries in an array
Example: Longest Contained Range
brute-force: sort, O(nlogn), search, O(n)
set: “中心开花,左右出发“,以任何一个元素出发,往两边扩展
先放进集合里
pop 任取元素,左右扩展
时间 O(n)
空间 O(C)
list vs. dictionary
list存储的是单独的元素,而字典存储的是对应关系(mapping),或者叫它映射。我们叫它Key-value pair,键值-存储值。
对任何一个数据结构,都需要知道 创建、增、删、查、改
创建
查找
字典没有顺序,只能按key查找,所以必须是存在在字典里的元素,不然直接run下行就会出现key error
还有一个函数,get 如果找不到,可以给None
判断key在不在字典里,就可以用lookup的操作,
增改
加一个新的entry、改已有的syntax,都可以很简单的
删除
拿到key的集合
python2 用grades.keys() return的是[‘A’, 'A', 'A+', 'B'] (value的list)
python3 的return是 (value的view)
view都不会占额外的空间,就像是range和xrange的区别
当然也可以用list(dict.keys()) ,占空间呗
拿到value的集合
类似👆
requirements for keys
Key必须unique,不能重复;第二次的一样的值会把第一个覆盖掉
Key必须是immutable的类型 所以X={[1]:0}不对,list unhashable
像Y={(1,2):10}就行,str也行
Dictionary Implementation
字典,hash的实现里,不需要去查找每个元素了,而是有一系列的buckets。每次有一个key,先算hashfunction(key),通过这个值,来得到bucket的序号。hashfunc可以接收任何变量,最后它能计算出一个integer,下面的code中,array_size是一个常数,也就是bucket size. 用算好的hash来%array_size, 得到index,意味着最后要把它放到index的桶里去找。
Hash Collision
假设两个key,x和y,本身x!=y, 但是hash(x)==hash(y), 就有了collision,因为它们被映射到了同一个bucket里,所以在第二行一定一样。
此外,因为array_size是一个定值,即使1不一样了,到2还是有可能一样,那还是会collision。
如果有perfect hashing, array_size又是无穷大(这在实际情况中是不存在的),就不会有冲突。
那么 现实中如何解决hash collision?
策略一: open addressing
如果算出来的数值被占了,发生了hash collision,就占用它最近的没有被占用的单元。
策略二:separate chaining
在每一个bucket里,就做成链表,把下一个人直接占在它后面 worst case就是,如果bucket=1(概率极低), 那么就变成了singly linked list,那么查找的时间复杂度又是O(n)了 不过,on average 还是可以O(1)
Time Complexity of different operations on dictionary
|Operation | Avg | Worst |
Search O(1) O(n)
Add O(1) O(n)
Delete O(1) O(n)
Update O(1) O(n)
Set
有时我们并不关心value,只关心key本身,这个时候dict就退化成了set,集合。集合就是只包含了key的hashtable。需要注意的是,集合本身可变mutable,但是里面包含的元素不变。所以增删查改咯~
Add
remove
如果remove的元素不在,使用x.remove会报keyerror; 但是使用x.discard("a")不会报
如果从中随意删除任何一个(因为set没有顺序,所以真的不知道删了哪个... ),x.pop(); 如果是清空,x.clear() ;
union
下面是集合操作的取并集 这个x和y是用set()把list变成集合
x = set(["Postcard", "Radio", "Telegram"])
y = set([ "Radio", "Television"])
print x.union(y)
更多
set也有set comprehension
{expression for value in collection if condition}
Recap
One to one mapping
<key, value> pair, key map to value
Key, no duplicates
Value, allow duplicates
hash_set is a set {1,3}, it only contains keys
In python, hash_set is set; hash_table is dictionary
题目
find one missing from unsorted array
use a set, iterate over the input array Average O(n) Worst O(n^2)
for each number from 1 to n: find if it's in the set Average O(n) Worst O(n^2)
优化 use a boolean[]
因为连续,可以放进物理连续的boolean array里 Step 1: for each element in the input array (value=i), put it into the seen[i]=true O(n) Step 2: for each element from 1...n: check if seen[i]==true O(n)
数学
等差数列求和,减去sum of input array,剩下的就是output Space O(1) but it may overflow
bit operation XOR
交换律 a XOR b = b XOR a
结合律 a XOR (b XOR c) = (a XOR b) XOR c
0是单元 0 XOR a = a
a XOR a =0
find common numbers between 2 sorted arrays a[M], b[N]
需要确认 如果有重复元素怎么办
Assume没有重复 或者 有重复元素只输出一次
Hashset
比较大小,把小的放进hashset
Step 1: Insert all elements from smaller array a into the hash set
Step 2: For each element in array b, check if it's in the HashSet or not
Time = O(M+N) average, worst O(M^2+N*M) Space = O(M)
谁小移谁
不相等就谁小移谁,相等就分别往前
Time = O(M+N) Space = O(1)
如果M<<<N Binary Search
For each element in b[N], run binary search M times.
Time = O(MlogN) Space = O(1)
2 sum sorted
因为是sorted,所以可以用2 pointers的方法,利用增加和减小的单调性:一个往前移一个往后移;如果现在的和比target小,移动i往后;如果比现在的target大,移动j往前。
2 sum unsorted
利用set的性质,经过的数字都存下来;用target的数字和当前经过的数字相减,如果得到的结果在之前的set里,就找到了,return true。否则,false。
[10, 2, 2, 13, 2], target 4
如果题目要output多少对,用一个dict,存count: {10:1, 2:2, ...}
如果题目要output哪些index的组合,dict里是list of index: {10:(0), 2:(1,2), ...}
3 sum unsorted
方法一:走n遍的2 sum。Time O( ) Space O(n)
方法二:先排序,反正已经O( )了,就算先排序也不会影响这个时间复杂度,那就排序吧!可以让空间复杂都变成O(1)。
4 sum unsorted
方法一:走n遍的3 sum。O( )
方法二:变成2个2sum,如何找到pair of 2 sum, 和是target。 1. 先配对 O( ) 2. sort一遍或者hashset O( ) 3. 在配对的时候要检查它是否是用过的 O(1)
k sum
在array里选k个数字组成sum=target。DFS,k个数字的subset
2 difference sorted
2 pointers,错一位同向而行。因为如果是相向而行,目前的值大于target,那么移动i或者j都行,所以到底移动哪个? 两个都尝试时间复杂度就会变高。同向而行,如果当前值大于target,移动i向前;如果当前值小于target,移动j向前。
2 difference unsorted
创建set
loop array,检查set里的元素是否等于array[i]-target或者array[i]+target
Longest sublist without duplicate values.
1 2 3 1 4 3 longest is 2 3 1 4
Last updated