HashTable

Dictionary, Set, 例题

底层机制

set是简化版hashtable,而hashtable的本质其实是array(或者说Python里的list),因为list的本质就是array index和value的映射。所以关键问题是把key转换成index。一般分成3步:

  1. 通过key和hash function计算它的hash_number=hash(key) 这一步虽然可能有collision但是可能性比较低

  2. index=hash_number%N, N是array size。 这一步有很大概率有collision,取决于N的大小

  3. 如果有hash collision,两种方法来解决(正文); 如果没有,list[index]=value

Dictionary 使用场景

TF-IDF (Term Frequency- Inverse Document Frequency) 统计词频时,可以用list来统计频率,但是使用list的缺点是每次更新频率的时间复杂度都是O(n), 只能遍历一遍、找到词、给词频+1; 随着单词变多,这个过程就变得低效了。

在Python中,可以使用dictionary的结构来实现高效的“更新词频“的需求。

def words_to_frequencies (words):
    myDict={}
    for word in words:
        if word in myDict:
            myDict[word]+=1
        else:
            myDict[word]=1
    return myDict

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: “中心开花,左右出发“,以任何一个元素出发,往两边扩展

  1. 先放进集合里

  2. 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

  1. Key必须unique,不能重复;第二次的一样的值会把第一个覆盖掉

  2. 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

  1. One to one mapping

  2. <key, value> pair, key map to value

  3. Key, no duplicates

  4. Value, allow duplicates

  5. 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

  1. use a set, iterate over the input array Average O(n) Worst O(n^2)

  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)

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( n2n^2 ) Space O(n)

方法二:先排序,反正已经O( n2n^2 )了,就算先排序也不会影响这个时间复杂度,那就排序吧!可以让空间复杂都变成O(1)。

4 sum unsorted

方法一:走n遍的3 sum。O( n3n^3 )

方法二:变成2个2sum,如何找到pair of 2 sum, 和是target。 1. 先配对 O( n2n^2 ) 2. sort一遍或者hashset O( n2n^2 ) 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

  1. 创建set

  2. 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