2023/7/17--代码随想录算法训练营day6|哈希表 part01、哈希表理论基础、242.有效的字母异位词、349.两个数组的交集、202、快乐数、1.两数之和

哈希表Hash Table理论基础:

当遇到要快速判断一个元素是否出现在集合时,就要考虑哈希法。(如果枚举法,时间复杂度O(n),但哈希表O(1)。哈希表,牺牲了空间,换取时间)
哈希表】:能够直接通过索引下标,直接访问数据。
【哈希表内部实现原理】:把所有内容都存进哈希表,查询的时候,通过索引直接就可以知道在不在表里面。
哈希函数】:把 内容 映射成—> 哈希表上的索引。在这里插入图片描述

哈希碰撞】:
出现的原因:哈希表装不下所有的内容。可能出现几个内容,映射到一个索引。
在这里插入图片描述
解决方法:

  1. 拉链法:其实拉链法就是要选择适当的哈希表的大小,这样既不会因为数组空值而浪费大量内存,也不会因为链表太长而在查找上浪费太多时间。在这里插入图片描述

  2. 线性探测法:使用线性探测法,一定要保证tableSize大于dataSize。 我们需要依靠哈希表中的空位来解决碰撞问题。
    在这里插入图片描述
    常见哈希表的区别】:
    红黑树是一种平衡二叉搜索树,所以key值是有序的,但key不可以修改,改动key值会导致整棵树的错乱,所以只能删除和增加。
    底层实现使用的哈希表,key值是无序的。
    在这里插入图片描述
    在这里插入图片描述

数组、set和map】:

  1. 当我们要使用集合来解决哈希问题的时候,优先使用unordered_set,因为它的查询和增删效率是最优的,如果需要集合是有序的,那么就用set,如果要求不仅有序还要有重复数据的话,那么就用multiset。
  2. 在map 是一个key value 的数据结构,map中,对key是有限制,对value没有限制的,因为key的存储方式使用红黑树实现的。

242.有效的字母异位词

【状态】:前一天晚上把代码都过了一遍,所以写出来了。但是犯了个小错误,

  • while里面变量要手动去调整+1,但是for不用,for自动迭代,自动抛出抛出下一个值。
  • 掌握相对位置ord() 给出字符的ascll编码

【题目】:在这里插入图片描述

【思路】:
【解法1】:时间复杂度O(n),空间复杂度O(1):数组是一个常量的辅助数组

  1. 定义一个大小26的数组record,初始化为0(因为字符a到字符z的ASCII也是26个连续的数值,为什么能用数组,因为这里显然使用的是个有限数组,否则不能使用)
  2. 先去遍历字符串s(用record记录字符出现的次数,字符对应索引的数值+1),然后在遍历字符串t(字符对应索引的数值-1)
    3.最后检查record数组,是否所有元素为0,是s和t是字母异位词,则true;否则,则两个字母长得不一样,return fase

注意:并不需要记住字符a的ASCII,只要求出一个相对数值就可以了。
在这里插入图片描述

class Solution:
    def isAnagram(self, s: str, t: str) -> bool:
        record = [0] * 26
        for i in s:
            #并不需要记住字符a的ASCII,只要求出一个相对数值就可以了
            record[ord(i) - ord("a")] += 1
        for i in t:
            record[ord(i) - ord("a")] -= 1
        for i in range(26):
            if record[i] != 0:
                #record数组如果有的元素不为零0,说明字符串s和t 一定是谁多了字符或者谁少了字符。
                return False
        return True

【解法2】:使用特殊的函数
collections.defaultdict 是 python 标准库中的一个字典子类。与普通字典不同的是,当访问一个不存在的键时,它会自动创建一个默认值。并对key自动排序。
这里的 defaultdict(int) 表示当访问不存在的键时,会自动创建一个值为 0 的键。

class Solution:
    def isAnagram(self, s: str, t: str) -> bool:
        from collections import defaultdict
        
        s_dict = defaultdict(int)
        t_dict = defaultdict(int)
        for x in s:
            s_dict[x] += 1
        
        for x in t:
            t_dict[x] += 1
        return s_dict == t_dict

collections.Counter是一个计数器,用于跟踪元素出现的次数。count里面自动对key排序

class Solution(object):
    def isAnagram(self, s: str, t: str) -> bool:
        from collections import Counter
        a_count = Counter(s)
        b_count = Counter(t)
        return a_count == b_count

349. 两个数组的交集:使用set()

【状态】:刚开始只想到用上面那题数组的方法解。但是说尽可能用set去解题,没想出来,看了代码以后才会写。但是自己写的时候,犯了个小错误。

  1. 我想要查找键在不在字典里面,不能使用 if table[j] != 0(应该有些字符没有出现在字典里面,所以找不到value),只能使用if j in table查找键在不在链表里面
  2. table[i] = table.get(i,0) + 1,如果键在,就给value+1;如果不在,先赋值为0,在+1。

【题目】:在这里插入图片描述

【思路】:根据题目描述,结果要用到无序set()就行;
【解法一】字典+数组

  1. 先用遍历nums1,用字典存储所有元素, key是元素,value是字符出现的次数
  2. 再遍历nums2,看看字符能不能在字典找到,找到把字符加到res集合里面,然后在字典里面删除这个字符
  3. 返回list(res)
class Solution:
    def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
    # 使用哈希表存储一个数组中的所有元素
        table = {}
        for num in nums1:
            table[num] = table.get(num, 0) + 1
        
        # 使用集合存储结果
        res = set()
        for num in nums2:
            if num in table:
                res.add(num)
                del table[num]
        
        return list(res)

【解法二】使用集合set()的特殊方法

class Solution:
    def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
        return list(set(nums1) & set(nums2))
       #&操作符是取交集;and关键字是两边为正,才是真

【解法三】使用数组
跟上一题一样,建一个数组,遍历num1和nums2,对数组进行判断。

class Solution:
    def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
        count1 = [0]*1001
        count2 = [0]*1001
        result = []
        for i in range(len(nums1)):
            count1[nums1[i]]+=1
        for j in range(len(nums2)):
            count2[nums2[j]]+=1
        for k in range(1001):
            if count1[k]*count2[k]>0:
                result.append(k)
        return result

202. 快乐数: 使用set() [这道题转成数组也行]

【状态】: ==n, r = divmod(n, 10) == #就是n除以10,n是商,t是余数
数组.append(元素)集合.add(元素)
总而言之,这道题,我自己写的时候,尽管知道了解题思路,在逻辑上,写的时候屡不清楚!!
在这个循环里面,快指针去追赶慢指针,只要fast(slow)指针一旦值为1,里面返回true,一旦追赶上,说明是一个死循环,并且在这个循环里面不可能有1,返回false。
我一直写错的原因就是,一旦判断可能循环次数不够,导致还没出现1,就被否定成不是快乐数了。
【题目】:在这里插入图片描述

【思路】:
1.快速判断一个元是否在集合中,要用哈希法----->考虑使用set,dict,nums数组
2.注意 == 求和的过程==
【解法1】使用集合set()

class Solution:
    def isHappy(self, n: int) -> bool:        
        record = set()

        while True:
            n = self.get_sum(n)
            if n == 1:
                return True
            
            # 如果中间结果重复出现,说明陷入死循环了,该数不是快乐数
            if n in record:
                return False
            else:
                record.add(n)

    def get_sum(self,n: int) -> int: 
        new_num = 0
        while n:
            n, r = divmod(n, 10)
            new_num += r ** 2
        return new_num

【解法二】逻辑不同,取数平方和,使用了转换字符串遍历字符,和字符转int算平方。【这个更重要,把握循环条件!!

class Solution:
   def isHappy(self, n: int) -> bool:
       record = set()
       while n not in record:
           record.add(n)
           new_num = 0
           n_str = str(n)
           for i in n_str:
               new_num+=int(i)**2
           if new_num==1: return True
           else: n = new_num
       return False

【解法三】竟然还能用快慢指针因为这道题就是个无限循环的过程,这个过程如果拦截到n为1,返回true;是死循环就是false
在这个循环里面,快指针去追赶慢指针,只要fast(slow)指针一旦值为1,里面返回true,一旦追赶上,说明是一个死循环,并且在这个循环里面不可能有1,返回false。否则写出来的逻辑就是,一旦判断可能循环次数不够,导致还没出现1,就被否定成不是快乐数了。

class Solution:
   def isHappy(self, n: int) -> bool:        
       slow = n
       fast = n
       while self.get_sum(fast) != 1 and self.get_sum(self.get_sum(fast)):
           slow = self.get_sum(slow)
           fast = self.get_sum(self.get_sum(fast))
           if slow == fast:
               return False
       return True
   def get_sum(self,n: int) -> int: 
       new_num = 0
       while n:
           n, r = divmod(n, 10)
           new_num += r ** 2
       return new_num

【解法四】集合+精简

class Solution:
   def isHappy(self, n: int) -> bool:
       seen = set()
       while n != 1:
           n = sum(int(i) ** 2 for i in str(n))
           if n in seen:
               return False
           seen.add(n)
       return True

1.两数之和

【状态】:就是比如说,我现在有了一个数,根据目标,我判断出我还需要一个数,我需要知道这个数在不在list里面,用哈希可以,但是索引怎么获取??
我们不仅要知道元素有没有遍历过,还要知道这个元素对应的下标,需要使用 key value结构来存放,key来存元素,value来存下标,那么使用map正合适。所以 map中的存储结构为 {key:数据元素,value:数组元素对应的下标}
【题目】在这里插入图片描述

【思路】
再来看一下使用数组和set来做哈希法的局限。

  • 数组的大小是受限制的,而且如果元素很少,而哈希值太大会造成内存空间的浪费。
  • set是一个集合,里面放的元素只能是一个key,而两数之和这道题目,不仅要判断y是否存在而且还要记录y的下标位置,因为要返回x 和 y的下标。所以set 也不能用。
  • map是一种key value的存储结构,可以用key保存数值,用value在保存数值所在的下标。
    在这里插入图片描述

在这里插入图片描述
时间复杂度: O(n)、空间复杂度: O(n)
【解法一】使用字典

class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        records = dict()

        for index, value in enumerate(nums):  
            if target - value in records:   # 遍历当前元素,并在map中寻找是否有匹配的key
                return [records[target- value], index]
            records[value] = index    # 遍历当前元素,并在map中寻找是否有匹配的key
        return []

【解法二】使用集合:使用 == 数组.index(元素) == 返回元素在数组的索引

class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        #创建一个集合来存储我们目前看到的数字
        seen = set()             
        for i, num in enumerate(nums):
            complement = target - num
            if complement in seen:
                return [nums.index(complement), i]
            seen.add(num)

【解法三】双指针:先排序,left指针指向首,right指针指向尾巴;如果两个指针指向数组的和达到target,返回下标(如果两个指针再原list索引相同,比如两个一样的数,给出的index一样,就要对所有做一些特殊处理);否则,如果小,就left+1,如果大了,就right-1。

class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        # 对输入列表进行排序
        nums_sorted = sorted(nums)
        
        # 使用双指针
        left = 0
        right = len(nums_sorted) - 1
        while left < right:
            current_sum = nums_sorted[left] + nums_sorted[right]
            if current_sum == target:
                # 如果和等于目标数,则返回两个数的下标
                left_index = nums.index(nums_sorted[left])
                right_index = nums.index(nums_sorted[right])
                if left_index == right_index:
                    right_index = nums[left_index+1:].index(nums_sorted[right]) + left_index + 1
                return [left_index, right_index]
            elif current_sum < target:
                # 如果总和小于目标,则将左侧指针向右移动
                left += 1
            else:
                # 如果总和大于目标值,则将右指针向左移动
                right -= 1

【解法四】暴力法时间复杂度O(n2)

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]

【总结】

情况哈希表
空间有限优先考虑用构建一个 数组
空间无限,存一个信息考虑set()
空间无限、需要分别存两个值dict

目前做题遇到的两个指针的解法:

双指针
快慢指针(一倍数和二倍速),特别在循环题目
首尾指针
首首指针,出发点不同
首首指针,一个先跑n步,在一起跑
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值