哈希算法:
比较
常见的数据查找算法,如顺序查找(最简单,效率低),二分查找(效率高,要求数据有序),深度优先广度优先搜索(对于大数据量查找效率不高),哈希算法(查找快,查询插入删除等简单)
存储
预先设置一个长度M的数组,使用哈希函数F并以数据关键字为自变量,得到唯一返回值(0~M-1)。
下图1
哈希冲突
链地址法,有N个哈希地址就有N个链表,数组Hash存放每个链表头指针
下图2
问题一:两个数的和
如给定五个数字:3,4,5,7,10。选择两个数和为N,保证有且只有一个答案
如选择两个数,和为11
解法一:
普通解法:数组排序,left和right指针分别指向第一个元素和最后一个元素,如果两个数和恰好与目标相等,则输出;若和较小,则left向右移(增大);若较大,right向左移
nums = [3, 4, 5, 7, 10]
def twoSum(nums, target):
res = [] # 存放结果编号
newnums = nums.copy()
newnums.sort() # 排序
left = 0 # 左指针
right = len(newnums) - 1 # 右指针
while left < right:
if newnums[left] + newnums[right] == target: # 相等
for i in range(len(newnums)): # 储存left下标
if nums[i] == newnums[left]:
res.append(i)
break
for i in range(len(newnums)-1, -1, -1): # right
if nums[i] == newnums[right]:
res.append(i)
break
break
elif newnums[left] + newnums[right] < target: # left右移
left += 1
elif newnums[right] + newnums[left] > target: # right左移
right -= 1
return(res[0]+1, res[1]+1)
print(twoSum(nums, 11))
解法二:哈希
运用字典(键值对)存放数据和下标的对应关系,给定一个数m,若(target-m)在字典中,就找到了
def twoSumHash(nums, target):
dict = {}
for i in range(len(nums)):
m = nums[i]
if target-m in dict: # 直接判断的是键
return(dict[target-m]+1 , i+1)
dict[m] = i
print(twoSum(nums, 11))
关于if .. in dict判断的是键还是值,可以试验一下:
>>> d = {2:3, 4:5}
>>> 2 in d
True
>>> 5 in d
False
>>> 5 in d.values()
True
问题二:单词模式匹配
模式匹配问题是一个经典的问题,即求目标字符串中单词出现的规律是否符合单词模式字符串中的规律。
哈希解法:
建立哈希表存储数据,需要排除三个问题:排除一个模式对应多个字符串的情况;排除多个模式对应一个字符串的情况。因此需要建立两个哈希表:hash和used (hash用来存储模式字符串和目标字符串的对应关系;used记录目前已经使用的字符串);排除两个字符串长度不相等的情况
wordpartten= [3, 3, 2, 2]
input = "hello hello hi hi"
def wordPattern(wordpartten, input):
word = input.split() # 单词由空格分开
if len(word) != len(wordpartten): # 长度不一样,肯定不对
return False
Hash = {} # 记录模式字符串与目标字符串匹配
Used = {} # 记录已经用过的字符串都哪些
for i in range(len(word)): # 遍历
if wordpartten[i] in Hash: # 如果匹配在HASH里了
if Hash[wordpartten[i]] != word[i]: # 是不是那个单词
return False
else: # HASH没这个匹配
if word[i] in Used: # 这个单词是不是新单词,若已经被用过了,不能两个匹配对应一个单词
return False
else: # 添加
Hash[wordpartten[i]] = word[i]
Used[word[i]] = True
return True
print(wordPattern(wordpartten, input))
问题三:猜词游戏(Bulls and Cows)
You are playing the following Bulls and Cows game with your friend: You write a 4-digit secret number and ask your friend to guess it, each time your friend guesses a number, you give a hint, the hint tells your friend how many digits are in the correct positions (called "bulls") and how many digits are in the wrong positions (called "cows"), your friend will use those hints to find out the secret number.
For example:
Secret number: 1807
Friend's guess: 7810
Hint: 1 bull and 3 cows. (The bull is 8, the cows are 0, 1 and 7.)
解法一:
把猜测数字的每一位拿出来,与秘密数字比较一圈,确定bulls和cows。
简单,但是大数据量时太慢(重复判断)
解法二:
在每一轮中使用哈希算法。查找是否bulls时,直接对应位置比较即可,如果不是bulls,我们建立字典
def getHint(secret, guess):
secret_dict = {} # 储存secret中不是bulls的个数
guess_dict = {} # 储存guess中不是bulls的个数
bull = 0
cow = 0
for i in range(len(secret)):
if secret[i] == guess[i]: # 对应位置两个元素相同
bull += 1
else:
if secret[i] in secret_dict: # secret对应字典中该项的数量加一
secret_dict[secret[i]] += 1
else:
secret_dict[secret[i]] = 1
if guess[i] in guess_dict: # guess对应字典中该项数量加一
guess_dict[guess[i]] += 1
else:
guess_dict[guess[i]] = 1
for k, v in secret_dict.items(): # 取两个字典中每一项的最小值,最小值,才能互相有匹配
if k in guess_dict:
cow += min(v, guess_dict[k])
return(bull, cow)
secret = [2, 0, 1, 8]
guess = [8, 0, 2, 1]
print(getHint(secret, guess))
问题四:神奇的词根
给定一个由许多词根组成的字典和一个句子。你需要将句子中的所有继承词用词根替换掉。如果继承词中有许多形成它的词根,则用最短的词根替换它。
看下哈希算法
import collections
def replaceWords(dict, sentence):
d = collections.defaultdict(set) # 记录以某字母开头的词根
s = collections.defaultdict(int) # 记录以某字母开头的最长的词根长度
sentence = sentence.split()
for w in dict: # 遍历词根,建立字典
d[w[0]].add(w)
s[w[0]] = max(len(w), s[w[0]])
# 遍历单词的首字母对应的词根,如果单词前j个字母在d字典中,则证明这j个字母是词根
for i, word in enumerate(sentence): # 遍历单词
for j in range(s[word[0]]):
if word[:j+1] in d[word[0]]:
sentence[i] = word[:j+1]
break
sentence = " ".join(sentence) # 连接,加空格
return sentence
sen = "the cattle was rattled by the battery"
dic = ["cat", "bat", "rat"]
print(replaceWords(dic, sen))
collections.defaultdict() 建立一个默认字典,本身就是一个字典,后面指定值的类型。如set(集合)
感悟:
哈希在搜索遍历时,运用字典的整合,可以减少遍历的长度和范围