算法记录 | Day5哈希表基础

128 阅读7分钟

算法记录 | Day5哈希表基础

LeetCode 242-有效字母的异位词

题目描述:给定两个字符串 st ,编写一个函数来判断 t 是否是 s 的字母异位词。

注意:若 s 和 t 中每个字符出现的次数都相同,则称 s 和 t 互为字母异位词。

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

题目链接:leetcode.cn/problems/va…

解题思路

哈希表:判断是否有重复元素出现;考虑三种数据结构:数组;set:set和dict类似,也是一组key的集合,但==不存储value==,且key不能重复;map: map 是一个key value 的数据结构。( map 和 set 的 key 值不可更改。)

  • 我的思考代码过程如下:① 将s中所有的字母加入hash表中,记录次数;② 判断 j 中的元素是否在hash表出现过,出现相减,未出现则加一。若最终结果都为0,说明次数一样为true,否则为false。
  • 这种方法总共经历了3次for循环。
class Solution:
    def isAnagram(self, s: str, t: str) -> bool:
        hashmap = dict()
        # 将s中所有的字母加入hash表
        for i in s:
            if i not in hashmap:
                hashmap[i] = 1
            else:
                hashmap[i] += 1
        # 判断 j 中的元素是否在hash表出现
        for j in t:
            if j not in hashmap:
                hashmap[j] = 1
            else:
                hashmap[j] -= 1 
        for k in hashmap:
            if hashmap[k] != 0:
                return False
        return True

方法二:也是利用哈希表记录次数,但记录方式不一样,通过把字符映射到数组也就是哈希表的索引下标上,因为字符a到字符z的ASCII是26个连续的数值,所以字符a映射为下标0,相应的字符z映射为下标25。

class Solution:
    def isAnagram(self, s: str, t: str) -> bool:
        record = [0] * 26
        for i in range(len(s)):
            #并不需要记住字符a的ASCII,只要求出一个相对数值就可以了
            record[ord(s[i]) - ord("a")] += 1
        print(record)
        for j in range(len(t)):
            record[ord(t[j]) - ord("a")] -= 1
        for k in range(26):
            if record[k] != 0:
                return False
        return True

注意:这里用的实际是数组来记录,用到了哈希表的思想。即数组可以是一种哈希表。

方法三:关于 collections 中的 defaultdict 方法

from collections import defaultdict
class Solution:
    def isAnagram(self, s: str, t: str) -> bool:
        s_dict = defaultdict(int)
        t_dict = defaultdict(int)

        for i in s:
            s_dict[i] += 1  
        for j in t:
            t_dict[j] += 1
        return s_dict == t_dict

注意

  • defaultdict 与 dict 的区别
    • ① defaultdict 括号里必须标明类型
    • ② 如果访问字典dict中不存在的键,会引发KeyError异常,所以我们要先处理没有元素的情况;collections 库中的 defaultdict 方法则可以直接写加的操作。
难点
  • 注意三种hash表结构的选择,目前没有做过用set的题目,还不太清除具体操作。
总结

哈希表的解题方式:看题目中是否需要判断重复元素


LeetCode 349-两个数组的交集

给定两个数组 nums1nums2 ,返回 它们的交集 。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序

题目链接:leetcode.cn/problems/in…

输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2]
解题思路
  • 暴力法
  • 利用哈希表

方法一:暴力

class Solution:
    def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
        # 暴力
        res = []
        for i in range(len(nums1)):
            for j in range(len(nums2)):
                if nums1[i] == nums2[j] and nums2[j] not in res:
                    res.append(nums2[j])
        return res

方法二:哈希表

class Solution:
    def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
        # 哈希法
        record = dict()
        res = []
        # 若 nums1 = [3, 6, 2, 2, 1]
        for i in range(len(nums1)):
            if nums1[i] not in record:
                record[nums1[i]] = 1
        print(record) # {3: 1, 6: 1, 2: 1, 1: 1}
        for j in range(len(nums2)):
            if nums2[j] in record and nums2[j] not in res:
                res.append(nums2[j])
        return res

注意:

  • 方法二与方法一相比,时间复杂度降低了。
  • dict() 是按照出现元素的顺序进行存储的。

方法三: 哈希表set

class Solution:
    def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
        return list(set(nums1) & set(nums2))    # 两个数组先变成集合,求交集后还原为数组

注意

  • dict和set唯一区别在于没有存储value,但是原理一样,key的值是唯一的,不可改变。

  • set的主要用法可以判断一个数组里有哪些元素,比如set([3, 6, 2, 2, 1]) ,则输出{1,2,3,6} 。注意这里只是告诉你有1,2,3,6这四个元素,并不是说set是有序的。

难点
  • 哈希表的选择
    • 数组,可以直接通过下标访问元素。
    • map,是一个 key value 的数据结构,适合记录次数。
    • set,有 key 无value值。c++里set有不同的实现方式,包括哈希和红黑树两种,一般优先使用unordered_set,因为它的查询和增删效率是最优的(不太清楚)。python里set底层就是hash,可以不用考虑其他set的实现方法。
总结

这道题想法很简单,用多种解法解出来可以思考各个哈希表的应用场景,最难的应该是灵活使用到各个适合的场景之中。


LeetCode 202-快乐数

题目描述:编写一个算法来判断一个数 n 是不是快乐数。

「快乐数」 定义为:

  • 对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
  • 然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
  • 如果这个过程 结果为 1,那么这个数就是快乐数。

如果 n快乐数 就返回 true ;不是,则返回 false

题目链接:leetcode.cn/problems/ha…

输入:n = 19
输出:true
解释:
$1^2 + 9^2 = 82$
$8^2 + 2^2 = 68$
$6^2 + 8^2 = 100$
$1^2 + 0^2 + 0^2 = 1$
解题思路
  • 先定义一个函数,计算各个数位的平方和
class Solution:
    def isHappy(self, n: int) -> bool:
        # 计算各个数位的和
        def sumHappy(n):
            sum = 0
            while n >= 10:
                mod = n % 10
                sum = sum + mod**2
                n = n // 10
            sum = sum + n**2
            return sum
        # 这样可能会进入很无限次循环
        # 注意思考到什么情况下快乐数会出现无数次循环
        ans = sumHappy(n)
        record = set()
        while True:
            if ans == 1:
                return True
            else:
                ans = sumHappy(ans)   
                if ans not in record:
                    record.add(ans)
                else:
                    return False
   
难点
  • ① 计算一个数各个数位的平方和
  • ② 陷入无限次循环如何输出 False
    • ==注意==思考到 当计算过程中出现重复数的时候会陷入到循环中
总结

这道题于我而言最难的点在于不知如何退出循环


LeetCode 1-两数之和

题目描述:给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。

题目链接:leetcode.cn/problems/tw…

输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1]
解题思路
  • 暴力解法
class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        for i in range(len(nums)):
            for j in range(i+1,len(nums)):
                if nums[i] + nums[j] == target:
                    return [i,j]
  • 哈希表解法
class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        record = dict()
        # 存储下标和值
        for key, value in enumerate(nums):
            # record[key] = value
            record[value] = key
        print(record)
        # 查找target - nums[i]的值
        for i in range(len(nums)):
            res = target - nums[i]
            if res in record and i != record[res]:
                return [i, record[res]]
            else:
                continue

注意

  • key 和 value的值不要写反了!!
  • 字典是没有重复元素的,如果相等后面的元素会覆盖前面的元素!!所以不需要判断下标。

更简单的写法:

class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
		records = dict()
        for index, val in enumerate(nums):
            if target - nums[index] not in records:
                records[val] = index
                print(records)
            else:
                return [records[target - val], index]
难点
  • key 和 value的值
  • value值相同的时候如何找另一个key
总结

这次全部自己写一遍的时候,发现思路都还是挺简单的,就是书写过程中的一些小细节。

再次提醒自己:

  • 哈希表注意三种结构的使用
    • 数组,可以直接通过下标访问元素。
    • map,是一个 key value 的数据结构,适合记录次数,无重复元素。
    • set,有 key 无value值;适合查看有哪些无重复元素。

告诉大家,查找错误的时候放到本地进行debug,就能很快明白自己错在哪了!!