python哈希算法——两个数的和、三个数的和、单词模式匹配、猜词游戏、神奇的词根实现实例

哈希算法

    哈希算法,又称为散列函数算法,是一种查找算法。简单来说,就是把一些复杂的数据,通过某种函数映射关系,

映射成更加易于查找的方式。但是这种映射关系有可能会发生多个关键字映射到同一个地址的现象,称之为冲突。

在这种特殊情况下,需要对关键字进行第二次或更多次的处理。

   1)什么是哈希:简单介绍什么是哈希及哈希的原理

   2)两个数的和:快速寻找两个数的和

   3)单词模式匹配:简单的模式匹配问题

常见的数据查找算法:

  1. 顺序查找:最简单的查找方式,需要对数据集中的数据逐个进行匹配,所以效率相对较低,不太适合大数据量的查找问题
  2. 二分查找:查找效率很高,但要求数据必须有序,而对数据排序通常需要更多的时间开销
  3. 深度优先遍历、广度优先遍历:对大数据量的查找问题效率并不高
  4. 哈希查找:由于其查找速度快,查询、插入、删除操作简单等原因而获得了广泛的应用。很多问题本质上都是查找问题。解决查找问题,哈希算法是较好的选择。

哈希算法进行查找的基本原理是根据数据量预先设置一个长度为M的数组,使用一个哈希函数F并以数据的关键字作为自变量,得到唯一的返回值,返回值的范围为0~M-1。

解决哈希冲突的方法有很多,如链地址法、二次再散列法、线性探测再散列、建立一个公共溢出区等方法

链地址法处理冲突的方法本质上是一种数组加链表的处理方法,当发生多个数据通过哈希映射后得到相同的哈希值时,通常把具有相同哈希地址的关键字放在同一个链表中,称该链表为同义词链表,也称为桶。(链地址法本质上是数组+链表的数据结构)

 

'''

一、两个数的和的解法

   要求在给定的一些数字中找出两个数,使得它们的和为N,前提是这些数据中保证有答案,并且只有一个答案。

   返回值为原始数组中查找的两个元素的编号

'''

# 求解方法1  使用双指针的方法解决

def twoSum(nums,target):
    res = []                       #存放结果编号数据
    newnums = nums[:]              #深拷贝,把原数据复制到newnums里
    newnums.sort()                 #对新数组排序
    left = 0
    right = len(newnums)-1        #定义left和right指针分别指向新数组的开头和结尾
    while left<right:
        if newnums[left]+newnums[right] == target:
            for i in range(0,len(nums)):
                if nums[i] == newnums[left]:
                    res.append(i)  #将下标加入结果集
                    break
            for i in range(len(nums)-1,-1,-1): #在原始数组中寻找第二个元素的原始下标
                if nums[i] == newnums[right]:
                    res.append(i) #将下标加入结果集
                    break
            res.sort()
            break
        elif  newnums[left]+newnums[right] < target:
            left +=1
        elif  newnums[left]+newnums[right] > target:
            right -=1
    return (res[0]+1,res[1]+1)  #返回结果集

                   

# 求解方法2  使用哈希算法解决

def twoSum(nums,target):
    dict={}                                # 建立一个字典,存储数据和下标的对应关系
    for i in range(len(nums)):
        m = nums[i]                        # 定义m为当前待查询的数字
        if target-m in dict:               #判断target-m是否已经在字典中
            return (dict[target-m]+1,i+1)  # 如果已经存在,则返回两个数的下标
        dict[m] = i                        # 如果不存在,则记录键值对

 

'''

二、三个数的和的解法

  给定一个包含n个整数的数组nums,判断nums中是否存在三个元素a,b,c,使得a+b+c=target(目标值)?找出所有满足条件且不重复的三元组。

  注意:答案中不可以包含重复的三元组。

 

例如, 给定数组 nums = [-1, 0, 1, 2, -1, -4],target=0

满足要求的三元组集合为:

[

  [-1, 0, 1],

  [-1, -1, 2]

]

解题设计:

    求解三数之和,可以用三层循环分别找到符合题意的三个数,是一种解决办法,但是很耗时,算法复杂度为O(n3)。

    另一种解决办法,可以将三数之和考虑为2+1的形式,先求解两个数的和,然后用目标值target减去两数之和即为所要找的数。

    而找这个数在python中非常简单,假设这个数为c,则只需要写c in nums即可判断是否存在这个数,但只做这个判断有可能

    会使同一个数在解答的一个三元组中出现两次,所以此处采用字典的形式,将每个元素与其所对应的下标组成键值对。

    这样即可轻易地避免元素重复地出现。

'''

def threeSum(nums,target):
    
    """
    type nums: List[int]
    returntype: List[List[int]]
    """
    m={}                      #存储将列表元素和其下标 构建的键值对
    for i in range(len(nums)):
        m[nums[i]]=i
    
    l = []
    for a in range(len(nums)): 
        for b in range(len(nums)):
            c = target-(nums[a]+nums[b])
            if (a != b) and c in nums and (a!=m[c]) and (b!=m[c]):
                l.append(sorted([nums[a],nums[b],c]))
#    return(list(list(t) for t in(set([tuple(t) for t in l]))))
    return([list(t) for t in (set([tuple(t) for t in l]))])

nums = [-1, 0, 1, 2, -1, -4]
target = 0
print(threeSum(nums,target))

 

'''

三、单词模式匹配

    模式匹配问题是一个经典的问题,即求目标字符串中单词出现的规律是否符合单词模式字符串中的规律

    解题设计:

        建立哈希表存储数据,需要排除三个问题:1)排除一个模式对应多个字符串的情况;

        2)排除多个模式对应一个字符串的情况。因此需要建立两个哈希表:hash和used.

           (hash用来存储模式字符串和目标字符串的对应关系;used记录目前已经使用的字符串)

        3) 排除两个字符串长度不相等的情况

'''

def wordPattern(wordPattern,inputstring):
    word = inputstring.split(' ')     # 目标字符串中的单词以空格隔开
    if len(word) != len(wordPattern): # 如果两个字符串的长度不一样,则肯定不匹配
        return False
    hash = {}                         # 记录模式字符串和目标字符串的对应关系
    used = {}                         # 记录目前已经使用过的字符串都有哪些
    for i in range(len(wordPattern)):
        if wordPattern[i] in hash:    # 检查模式字符串中的字符是否已经被记录过映射关系
            if hash[wordPattern[i]] != word[i]:  # 不是第一次出现,则检查映射关系是否一致
                return False
        else:
            if word[i] in used:       # 检查这个单词是否已经使用过,使用过返回不成立
                return False
        hash[wordPattern[i]] = word[i] #第一次出现,则加入哈希表
        used[word[i]] = True           # 在used中保存哪些单词已经使用过
    return True

  

'''

四、猜词游戏

   需要写一个程序,它能够根据秘密数字和朋友的猜测数返回提示,其中A表示公牛(几位数字和确切位置都猜对了),

   B表示奶牛(多少位数字猜对了但位置不对)。注意:秘密数字和朋友猜测的数字都可能含有重复数字

  

   解题设计:

       1)先解决第一个问题,查找公牛(A)的数量。首先定义一个变量A表示公牛的数量,之后循环把每个位置上的

          数字都取出来,按位置对比即可,数字一样则让A加1

       2)然后解决奶牛(B)的数量问题。使用哈希表来加快速度,建立两个字典,因为需要记录配对的过程中哪些

          位置的数字配对了,哪些还没有配对。最终B的数量是由该数在秘密数字中和在猜测数字中更小的那方决定。

          为此,只需要记录秘密数字和猜测数字中未匹配的数字和它的个数,之后看相同数字的最小数即可。

         

          那么在分析A的数量的同时,记录其他未匹配数字的数量

'''

def getHint(secret,guess):
    """
    type secret: str
    type guess:  str
    return type: str
    """
    secret_dict = {}
    guess_dict = {}
    
    A = 0
    B = 0
    for i in range(len(secret)):
        if secret[i] == guess[i]: # 查找A的数量
            A += 1
        else:
            if secret[i] in secret_dict:
                secret_dict[secret[i]] += 1
            else:
                secret_dict[secret[i]] = 1
            if guess[i] in guess_dict:
                guess_dict[guess[i]] += 1
            else:
                guess_dict[guess[i]] =1
    # 记录好了数量,接下来根据两个数组中的数的最小值来判断B的数量
    for digit in secret_dict:
        if digit in guess_dict:
            B += min(secret_dict[digit],guess_dict[digit])
    return str(A)+'A'+str(B)+'B'

   

'''

五、神奇的词根

    问题:给定一个由许多词根组成的字典和一个句子。你需要将句子中的所有继承词用词根替换掉。

         如果继承词中有许多形成它的词根,则用最短的词根替换它。

'''

# 暴力破解法

def  replaceWords(dict,sentence):
    '''
    type dict : list[str]
    type sentence:str
    return type: str
    '''
    s = sentence.split(' ') # 先把句子转换为列表
    
    # 判断单词是否以词根开头,如果是,则替换(dict存储词根)
    for item in dict:
        for i in range(len(s)):
            n = len(item)
            if item == s[i][:n]:
                s[i] = item
    return " ".join(s)

 

# 哈希来优化解决

def replaceWords(dict,sentence):
    '''
    type dict : list[str]
    type sentence:str
    return type: str
    '''
   #建立两个字典,并做好初始化工作(建立字典使用collections.defaultdict()建立默认字典,python会自动为它的键赋一个初始值。
    d = collections.defaultdict(set)
    s = collections.defaultdict(int)
    sentence = sentence.split(" ")
    
    # 以每一个词根的首字母为键,把每一个词根放到该键所对应的值中去,值是一个集合,同时记录下根根的最大长度
    for w in dict:
        d[w[0]].add(w)
        s[w[0]] = max(s[w[0]],len(w))
        
    # 把每个单词拿出来,查找以该单词开头的词根是否能够和这个单词匹配
    # 使用enumerate()函数来遍历句子,它会把索引放到第一个变量,把元素放到第二个变量。
    for i,w in enumerate(sentence):
        for j in range(s[w[0]]):
            if w[:j+1] in d[w[0]]:
                sentence[i] = w[:j+1]
                break
    return ' '.join(sentence)

  参考书籍:《你也能看得懂的Python算法书》/王硕等编著.—北京:电子工业出版社,2018.11

猜你喜欢

转载自blog.csdn.net/weixin_42521211/article/details/87709305