LeetCode 算法笔记 哈希表(简单题)

先写个哈希表的定义:散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。也是刚刚百度得到的。

第771题:宝石与石头(简单)哈希表

 给定字符串J 代表石头中宝石的类型,和字符串 S代表你拥有的石头。 S 中每个字符代表了一种你拥有的石头的类型,你想知道你拥有的石头中有多少是宝石。

J 中的字母不重复,J 和 S中的所有字符都是字母。字母区分大小写,因此"a""A"是不同类型的石头。

示例 1:

输入: J = "aA", S = "aAAbbbb"
输出: 3

示例 2:

输入: J = "z", S = "ZZ"
输出: 0

注意:

  • S 和 J 最多含有50个字母。
  •  J 中的字符不重复。

J就是我们要查询的表,而S就是一个要处理的字符串,所以我们就是要将S中的每一个字符遍历一遍,看是否在J中出现,具体代码如下:

class Solution:
    def numJewelsInStones(self, J, S):
        """
        :type J: str
        :type S: str
        :rtype: int
        """
        count = 0
        for i in S:
            if i in J:
                count =count + 1
        return count

811:子域名访问计数(简单)哈希表

一个网站域名,如”discuss.leetcode.com”,包含了多个子域名。作为顶级域名,常用的有”com”,下一级则有”leetcode.com”,最低的一级为”discuss.leetcode.com”。当我们访问域名”discuss.leetcode.com”时,也同时访问了其父域名”leetcode.com”以及顶级域名 “com”。

给定一个带访问次数和域名的组合,要求分别计算每个域名被访问的次数。其格式为访问次数+空格+地址,例如:”9001 discuss.leetcode.com”。

接下来会给出一组访问次数和域名组合的列表cpdomains 。要求解析出所有域名的访问次数,输出格式和输入格式相同,不限定先后顺序。

示例 1:
输入: 
["9001 discuss.leetcode.com"]
输出: 
["9001 discuss.leetcode.com", "9001 leetcode.com", "9001 com"]
说明: 
例子中仅包含一个网站域名:"discuss.leetcode.com"。按照前文假设,子域名"leetcode.com"和"com"都会被访问,所以它们都被访问了9001次。
示例 2
输入: 
["900 google.mail.com", "50 yahoo.com", "1 intel.mail.com", "5 wiki.org"]
输出: 
["901 mail.com","50 yahoo.com","900 google.mail.com","5 wiki.org","5 org","1 intel.mail.com","951 com"]
说明: 
按照假设,会访问"google.mail.com" 900次,"yahoo.com" 50次,"intel.mail.com" 1次,"wiki.org" 5次。
而对于父域名,会访问"mail.com" 900+1 = 901次,"com" 900 + 50 + 1 = 951次,和 "org" 5 次。

注意事项:

  •  cpdomains 的长度小于 100
  • 每个域名的长度小于100
  • 每个域名地址包含一个或两个”.”符号。
  • 输入中任意一个域名的访问次数都小于10000

这道题目逻辑上是非常简单的,就是建立一个字典,字典的key就是这些域名,值就是每一个域名被访问的次数,对输入的list循环,每进来一个string就进行相应的处理,把对应的域名和子域名传入到字典中,全部整合完毕后,就将字典再转化为列表,具体代码如下:

class Solution:
    def subdomainVisits(self, cpdomains):
        """
        :type cpdomains: List[str]
        :rtype: List[str]
        """
        dict = {}
        result = []
        for s in cpdomains:
            L = s.split(' ')
            a = int(L[0])
            l = L[1].split('.')
            if dict. __contains__(l[-1]):
                dict[l[-1]] = dict[l[-1]] + a
            else:
                dict[l[-1]] = a    
            if dict. __contains__(l[-2]+'.'+l[-1]):
                dict[l[-2]+'.'+l[-1]] = dict[l[-2]+'.'+l[-1]] + a
            else:
                dict[l[-2]+'.'+l[-1]] = a 
            if(len(l) == 3):
                if dict. __contains__(l[-3]+'.'+l[-2]+'.'+l[-1]):
                    dict[l[-3]+'.'+l[-2]+'.'+l[-1]] = dict[l[-3]+'.'+l[-2]+'.'+l[-1]] + a
                else:
                    dict[l[-3]+'.'+l[-2]+'.'+l[-1]] = a 
        for i in dict.items():
            result.append(str(i[1])+' '+i[0])
        return result

虽然思想很简单,但是我这个代码确实写的有点挫。

这是别人的代码:

class Solution:
        def subdomainVisits(self, cpdomains):
            c = collections.Counter()
            for cd in cpdomains:
                n, d = cd.split()
                c[d] += int(n)
                for i in range(len(d)):
                    if d[i] == '.': c[d[i + 1:]] += int(n)
            return ["%d %s" % (c[k], k) for k in c]

这里它用了python里面的collections.Counter()这个类方法,具体可以参照python标准库,这个标准库的类方法用来做累加计数确实特别方便,确实在算法设计上有很多值得借鉴的地方。

500:键盘行(简单)哈希表

给定一个单词列表,只返回可以使用在键盘同一行的字母打印出来的单词。键盘如下图所示。

《LeetCode 算法笔记 哈希表(简单题)》

示例:

输入: ["Hello", "Alaska", "Dad", "Peace"]
输出: ["Alaska", "Dad"]

注意:

  1. 你可以重复使用键盘上同一字符。
  2. 你可以假设输入的字符串将只包含字母。

对于这一题,我们可以首先建立三个列表,分别表示键盘的123行;,然后将键盘的信息存储到这个里面,然后对于每一个输入进来的字符串,判断是否是属于同一个str,这里通过转化为列表,然后用集合的方式去处理,具体代码如下:

class Solution:
    def findWords(self, words):
        """
        :type words: List[str]
        :rtype: List[str]
        """
        l1 = list("QWERTYUIOPqwertyuiop")
        l2 = list("ASDFGHJKLasdfghjkl")
        l3 = list("ZXCVBNMzxcvbnm")
        result = []
        
        for s in words:
            l0 = list(s)
            if(set(l0).issubset(set(l1)) or set(l0).issubset(set(l2)) or set(l0).issubset(set(l3))):
                result.append(s)
                
        return result

575:分糖果(简单)哈希表

给定一个偶数长度的数组,其中不同的数字代表着不同种类的糖果,每一个数字代表一个糖果。你需要把这些糖果平均分给一个弟弟和一个妹妹。返回妹妹可以获得的最大糖果的种类数。

示例 1:

输入: candies = [1,1,2,2,3,3]
输出: 3
解析: 一共有三种种类的糖果,每一种都有两个。
     最优分配方案:妹妹获得[1,2,3],弟弟也获得[1,2,3]。这样使妹妹获得糖果的种类数最多。

示例 2 :

输入: candies = [1,1,2,3]
输出: 2
解析: 妹妹获得糖果[2,3],弟弟获得糖果[1,1],妹妹有两种不同的糖果,弟弟只有一种。这样使得妹妹可以获得的糖果种类数最多。

这道题目比较简单,具体代码如下,看代码应该就能理解,主要就是把列表转化为集合这个想法:

class Solution:
    def distributeCandies(self, candies):
        """
        :type candies: List[int]
        :rtype: int
        """
        s = set(candies)
        if(len(s)<len(candies)/2):
            return len(s)
        else:
            return len(candies)//2

811:有效的字母异位词(简单)哈希表 排序

给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的一个字母异位词。

示例 1:

输入: s = "anagram", t = "nagaram"
输出: true

示例 2:

输入: s = "rat", t = "car"
输出: false

说明:
你可以假设字符串只包含小写字母。

进阶:
如果输入字符串包含 unicode 字符怎么办?你能否调整你的解法来应对这种情况?

对于这一题,要求是t是否是s的字母异位词,很明显的是,如果s是t的字母异位词,那么我们就有s和t的排序应该是一模一样的,所以我们对s和t进行排序,然后去判断这两个是否一样就可以了,具体的代码如下:

class Solution:
    def isAnagram(self, s, t):
        """
        :type s: str
        :type t: str
        :rtype: bool
        """
        if(len(s)!=len(t)):
            return False
        l0 = list(s)
        l0.sort()
        l1 = list(t)
        l1.sort()

        for i in range(len(l0)):
            if(l0[i]!=l1[i]):
                return False
        return True

这个是通过排序的方法去做,排序的话时间复杂度是O(nlogn),接下来我们要用一个时间复杂度更低的算法,用hash table来做。

思想其实很简单就是去计数,总共我们知道有26个字母,那么建立一个字典,对应于26个字母,然后遍历字符串,去算每一个字母出现的次数,最后两个字典如果相等就返回true。具体代码如下:

def isAnagram1(self, s, t):
    dic1, dic2 = {}, {}
    for item in s:
        dic1[item] = dic1.get(item, 0) + 1
    for item in t:
        dic2[item] = dic2.get(item, 0) + 1
    return dic1 == dic2
    
def isAnagram2(self, s, t):
    dic1, dic2 = [0]*26, [0]*26
    for item in s:
        dic1[ord(item)-ord('a')] += 1
    for item in t:
        dic2[ord(item)-ord('a')] += 1
    return dic1 == dic2

这是别人写的两个方法,第一个里面有个dict.get(item,0)这个方法就是有就返回具体值,没有就是0,第二个方法里面就是ord便是字符串的位置信息,都是python里面的一些语言技巧。第二个方法里面生成的是一个包含26个0元素的列表。

811:字符串中的第一个唯一字符(简单)哈希表 字符串

给定一个字符串,找到它的第一个不重复的字符,并返回它的索引。如果不存在,则返回 -1。

案例:

s = "leetcode"
返回 0.

s = "loveleetcode",
返回 2.

注意事项:您可以假定该字符串只包含小写字母。

这道题其实我自己都没有想到什么好的方法,一开始,所以看到别人的一个代码如下:

class Solution:
    def firstUniqChar(self, s):
        """
        :type s: str
        :rtype: int
        """
        letters='abcdefghijklmnopqrstuvwxyz'
        index=[s.index(l) for l in letters if s.count(l) == 1]
        print(index)
        return min(index) if len(index) > 0 else -1

这个代码中,利用了字符串的两个方法,一个是count,还有一个是index,分别是去数字符出现的个数以及出现的位置,同时也利用了正则表达式去简化代码,这两个类方法运用到这个题目当中是非常好的,也使得思想更加的明朗,就是对26个字母看每个在字符串中出现了几次,出现一次的话就返回位置,最后把位置最小的给输出就好了。当然论坛上还有一些其它的方法,但是整体上都是利用了python语言的相关特性,从算法本身角度看,都是用count去做,并没有什么太大新意,或者用字典去做。

599:两个列表的最小索引总和(简单)哈希表

假设Andy和Doris想在晚餐时选择一家餐厅,并且他们都有一个表示最喜爱餐厅的列表,每个餐厅的名字用字符串表示。

你需要帮助他们用最少的索引和找出他们共同喜爱的餐厅。 如果答案不止一个,则输出所有答案并且不考虑顺序。 你可以假设总是存在一个答案。

示例 1:

输入:
["Shogun", "Tapioca Express", "Burger King", "KFC"]
["Piatti", "The Grill at Torrey Pines", "Hungry Hunter Steakhouse", "Shogun"]
输出: ["Shogun"]
解释: 他们唯一共同喜爱的餐厅是“Shogun”。

示例 2:

输入:
["Shogun", "Tapioca Express", "Burger King", "KFC"]
["KFC", "Shogun", "Burger King"]
输出: ["Shogun"]
解释: 他们共同喜爱且具有最小索引和的餐厅是“Shogun”,它有最小的索引和1(0+1)。

提示:

  1. 两个列表的长度范围都在 [1, 1000]内。
  2. 两个列表中的字符串的长度将在[1,30]的范围内。
  3. 下标从0开始,到列表的长度减1。
  4. 两个列表都没有重复的元素。

这一道题目,首先我的想法就是如果答案只有一个的话,那么我们就可以在寻找最小的索引的同时并且寻找共同喜爱的餐厅,就是对list1进行迭代,那么我们从list2中找一样的,然后看是否这一次的索引和更小,更小的话就替换掉喜爱的餐厅,这是很多题目的的套路,但是这里由于可能有多个答案,所以我的想法就是分开做,先去找最小的索引,把索引找出来以后,再去判断在这样的索引和的情况下,到底有多少组答案,具体的代码如下:

class Solution:
    def findRestaurant(self, list1, list2):
        """
        :type list1: List[str]
        :type list2: List[str]
        :rtype: List[str]
        """
        minIndex = 2000
        result = []
        for i in range(len(list1)):
            try:
                a = list2.index(list1[i])
            except BaseException:
                continue
            else:
                if(i + a<minIndex):
                    minIndex = i + a
        print(minIndex)
        for j in range(minIndex+1):
            if(j<len(list1) and minIndex-j<len(list2) and list1[j] == list2[minIndex-j]):
                 result.append(list1[j])
        return result

以上是我自己在解题的思路,下面我们去看看答案以及别人的想法。答案上的第一个方法和我们的思路一样,都是通过表的查找,用hash map的思想去做,它的第二个方法就特别的有意思,它是从索引和的角度出发,分别取计算索引和为0,1….一旦出现不为空的情况就返回结果,代码如下:

class Solution:
    def findRestaurant(self, list1, list2):
        """
        :type list1: List[str]
        :type list2: List[str]
        :rtype: List[str]
        """
        for i in range(len(list1)+len(list2)-1):
            result = []
            for j in range(i+1):
                if(j<len(list1) and i-j<len(list2) and list1[j] == list2[i-j]):
                    result.append(list1[j])
            if(len(result)>0):
                return result

这个代码是比较慢的,因为没有用hash map,两个for循环的情况下,时间复杂度是更加的高的。

594: 最长和谐子序列 (简单) 哈希表

和谐数组是指一个数组里元素的最大值和最小值之间的差别正好是1。

现在,给定一个整数数组,你需要在所有可能的子序列中找到最长的和谐子序列的长度。

示例 1:

输入: [1,3,2,2,5,2,3,7]
输出: 5
原因: 最长的和谐数组是:[3,2,2,2,3].

说明: 输入的数组长度最大不超过20,000。

这道题目的话最暴力的解法,复杂度是O(2^n),就是把所有的子序列遍历一遍,然后找出其中最长的和谐数组,很显然这个方法一定会超时。还有一种优化后的暴力算法,也是我一开始想到的,就是对于数组里面的每一个数,将其个数以及比它大1的个数给数出来,然后选出最大的那种,具体代码如下,这个很简单:

class Solution:
    def findLHS(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        result = 0
        s = set(nums)
        for i in s:
            a = nums.count(i)
            b = nums.count(i+1)
            if(a+b>result and b!=0):
                result = a+b
        return result

尽管这个算法的复杂度已经降了很多,是O(n^2),不过还是会超时。接下来我们用hash map来做,首先建立一个hash table,具体代码如下:

class Solution:
    def findLHS(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        dict0 = {}
        result = 0
        for i in range(len(nums)):
            dict0[nums[i]] = dict0.get(nums[i], 0) + 1
        for key in dict0:
            a = dict0.get(key, 0)
            b = dict0.get(key+1, 0)
            if(a+b>result and dict0.get(key+1, 0)!= 0 ):
                result = a+b
        return result

其实思想上是跟上面是一样的,但是由于利用了hash map明显降低了时间复杂度,所以这个是可以通过的。上面我们用的是双循环,其实单循环也是可以做到的,单循环就是每来一个数,我们就更新一下最后的结果,具体的代码如下:

class Solution:
    def findLHS(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        dict0 = {}
        result = 0
        for i in range(len(nums)):
            dict0[nums[i]] = dict0.get(nums[i], 0) + 1
            a = dict0.get(nums[i], 0)
            b = dict0.get(nums[i]+1, 0)
            if(a+b>result and dict0.get(nums[i]+1, 0)!= 0 ):
                result = a+b
            a = dict0.get(nums[i], 0)
            b = dict0.get(nums[i]-1, 0)
            if(a+b>result and dict0.get(nums[i]-1, 0)!= 0 ):
                result = a+b
    
        return result

代码写的不是很好,但是具体的思想大概就是这样。

最后还有一个用sort的方法去做的,就是先把数组排玩顺序之后再去做分析。其实在面对很多需要去比较数的大小的时候,我们先进行排序是一个很好的策略。

290.:单词模式 (简单)哈希表

给定一种 pattern(模式) 和一个字符串 str ,判断 str 是否遵循相同的模式。

这里的遵循指完全匹配,例如, pattern 里的每个字母和字符串 str 中的每个非空单词之间存在着双向连接的对应模式。

示例1:

输入: pattern = "abba", str = "dog cat cat dog"

输出: true

示例 2:

输入:pattern = "abba", str = "dog cat cat fish"

输出: false

示例 3:

输入: pattern = "aaaa", str = "dog cat cat dog"

输出: false

示例 4:

输入: pattern = "abba", str = "dog dog dog dog"

输出: false

说明:
你可以假设 pattern 只包含小写字母, str 包含了由单个空格分隔的小写字母。

关于这一题,我个人的思想就是把两个字符串映射到一个用数字表示的字典中去,abba就对应于1221,而我们建立的词典就是{‘a’:1,’b’:2},所以说我就先贴上代码:

class Solution:
    def wordPattern(self, pattern, str):
        """
        :type pattern: str
        :type str: str
        :rtype: bool
        """
        l1 = list(pattern)
        l2 = str.split(" ")
        dict1 = {}
        dict2 = {}
        count1 = 1
        count2 = 1
        if(len(l1)!=len(l2)):
            return False
        for i in range(len(l1)):
            if(dict1. __contains__(l1[i])):
                l1[i] = dict1[l1[i]]
            else:
                dict1[l1[i]] = count1
                l1[i] = count1
                count1 = count1 + 1
                
            if(dict2. __contains__(l2[i])):
                l2[i] = dict2[l2[i]]
            else:
                dict2[l2[i]] = count2
                l2[i] = count2
                count2 = count2 + 1
            if(l1[i]!=l2[i]):
                return False
        return True
            

代码写的比较冗余,因为其实是对两个字符串进行的相同的操作,只不过在中间进行比较。

204:计数质数(简单)哈希表 数学

统计所有小于非负整数 的质数的数量。

示例:

输入: 10
输出: 4
解释: 小于 10 的质数一共有 4 个, 它们是 2, 3, 5, 7

这道题目有明显的暴力解法,那就是对于每一个数,我们判断它是不是质数,如果是的话,那就记一次,那么这个的时间复杂度是O(n^2),除了暴力解法之外,论坛上有一个如下的解法:

class Solution:
    def countPrimes(self, n):
        """
        :type n: int
        :rtype: int
        """
        if n < 3:
            return 0
        primes = [True] * n
        primes[0] = primes[1] = False
        for i in range(2, int(n ** 0.5) + 1):
            if primes[i]:
                primes[i * i: n: i] = [False] * len(primes[i * i: n: i])
        return sum(primes)
            

他是首先认为所有的数都是质数,然后依次将2*[2,3,4…],3*[3,4,5…]…等这些全部置成合数,然后最终得到结果,这里利用了列表的赋值,整体的复杂度就是O(n),要比暴力解决快很多。

    原文作者:哈希算法
    原文地址: https://blog.csdn.net/marmove/article/details/82989496
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞