当前位置: 代码网 > it编程>编程语言>C/C++ > 代码随想录训练营 Day6打卡 哈希表part01 242.有效的字母异位词 349. 两个数组的交集 202. 快乐数 1. 两数之和

代码随想录训练营 Day6打卡 哈希表part01 242.有效的字母异位词 349. 两个数组的交集 202. 快乐数 1. 两数之和

2024年07月31日 C/C++ 我要评论
代码随想录训练营 Day6打卡 哈希表part01 242.有效的字母异位词 349. 两个数组的交集 202. 快乐数 1. 两数之和

代码随想录训练营 day6打卡 哈希表part01

一、 哈希表理论基础

哈希表(hash table)是一种基于哈希函数的高效数据结构,用于快速存储和查找数据。哈希表通过将键(key)映射到一个索引位置来实现,这个索引位置在哈希表的底层数组中存储值(value)。

哈希函数

哈希函数(hash function)是哈希表的核心,它接受输入(键)并返回一个整数(哈希值),这个整数通常是数组的索引。理想的哈希函数应当:

  • 均匀分布:不同的输入应均匀分布到不同的索引位置,避免碰撞。
  • 确定性:相同的输入必须始终返回相同的哈希值。
  • 高效计算:哈希函数应快速计算,以保持插入和查找的高效性。

碰撞解决

由于哈希函数的输出范围有限,不同的输入可能映射到同一个索引位置,称为碰撞(collision)。解决碰撞的方法有多种:

  • 链地址法(chaining):在每个索引位置存储一个链表(或其他结构)来处理多个键值对。
  • 开放地址法(open addressing):在碰撞发生时,查找表内的下一个空位置来存储值。

python中的哈希表

在python中,哈希表由内置的dict类型和set类型实现。

python中的dict类型

字典(dictionary)是python内置的哈希表类型,它以键值对(key-value pair)的形式存储数据。字典的键必须是可哈希的(即具有__hash__方法和可比较性)。

# 创建字典
my_dict = {'apple': 1, 'banana': 2, 'cherry': 3}

# 添加元素
my_dict['date'] = 4

# 访问元素
print(my_dict['apple'])  # 输出: 1

# 删除元素
del my_dict['banana']

# 检查键是否存在
if 'cherry' in my_dict:
    print('cherry is in the dictionary')

# 遍历字典
for key, value in my_dict.items():
    print(f'{key}: {value}')

python中的set类型

集合(set)也是一种基于哈希表的数据结构,它仅存储键而不存储值,用于高效的成员检测和去重操作。

# 创建集合
my_set = {'apple', 'banana', 'cherry'}

# 添加元素
my_set.add('date')

# 删除元素
my_set.remove('banana')

# 检查元素是否存在
if 'cherry' in my_set:
    print('cherry is in the set')

# 遍历集合
for item in my_set:
    print(item)

哈希表的性能

  • 时间复杂度:
    平均情况下,哈希表的插入、删除和查找操作的时间复杂度为o(1)。
    在最坏情况下(哈希冲突严重),时间复杂度可能退化为o(n)。

  • 空间复杂度:
    哈希表需要额外的存储空间来存储键值对和处理冲突的结构(如链表)。

使用哈希表的注意事项

  • 选择合适的哈希函数:确保哈希函数能均匀分布输入,减少冲突。
  • 避免过度装填:装载因子(load factor)应适中,过高的装载因子会导致冲突增多,影响性能。
  • 处理不可哈希类型:字典和集合的键必须是不可变的可哈希类型,如数字、字符串、元组等。

二、力扣242.有效的字母异位词

算法思路
为了判断两个字符串s和t是否为字母异位词,我们采用基于计数数组的算法。首先,定义一个大小为26的数组record,用于统计s中字符’a’到’z’的出现次数。通过s[i] - 'a’计算每个字符的数组下标,遍历s时在对应位置增加计数。

接着,同样遍历t,但在record数组的对应位置减少计数。最后,检查record数组所有元素是否为0,如果是,说明s和t字符出现次数一致,是异位词,返回true;否则,返回false。

时间复杂度为o(n),空间上因为定义是的一个常量大小的辅助数组,所以空间复杂度为o(1)。

版本一 使用数组作为哈希表

代码思路

  1. 创建一个长度为26的数组record,用于记录每个字母出现的次数。
  2. 遍历字符串s,增加对应字母的计数。
  3. 遍历字符串t,减少对应字母的计数。
  4. 最后检查数组record,如果所有元素都为0,说明s和t是字母异位词,否则不是。
class solution:
    def isanagram(self, s: str, t: str) -> bool:
        record = [0] * 26  # 创建一个长度为26的数组记录字母出现的次数
        for i in s:
            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:
                return false  # 如果有元素不为0,说明字符串s和t的字母出现次数不一致
        return true  # 所有元素都为0,说明s和t是字母异位词

版本二 使用defaultdict

代码思路

  1. 使用defaultdict创建两个字典,分别记录字符串s和t中每个字母出现的次数。
  2. 遍历字符串s,增加对应字母的计数。
  3. 遍历字符串t,增加对应字母的计数。
  4. 最后比较两个字典,如果相等,说明s和t是字母异位词,否则不是。
class solution:
    def isanagram(self, s: str, t: str) -> bool:
        from collections import defaultdict
        
        s_dict = defaultdict(int)  # 创建用于记录字符串s中每个字母出现次数的字典
        t_dict = defaultdict(int)  # 创建用于记录字符串t中每个字母出现次数的字典
        
        for x in s:
            s_dict[x] += 1  # 增加s_dict中对应字母的计数
        
        for x in t:
            t_dict[x] += 1  # 增加t_dict中对应字母的计数
        
        return s_dict == t_dict  # 比较两个字典是否相等
        
版本三 使用counter

代码思路

  1. 使用counter类分别统计字符串s和t中每个字母出现的次数。
  2. 比较两个counter对象,如果相等,说明s和t是字母异位词,否则不是。
class solution(object):
    def isanagram(self, s: str, t: str) -> bool:
        from collections import counter
        
        a_count = counter(s)  # 统计字符串s中每个字母出现的次数
        b_count = counter(t)  # 统计字符串t中每个字母出现的次数
        
        return a_count == b_count  # 比较两个counter对象是否相等
        

方法一:使用数组作为哈希表,时间复杂度为o(n),空间复杂度为o(1),适用于字符集较小(如仅包含小写字母)的情况。
方法二:使用defaultdict,时间复杂度为o(n),空间复杂度为o(n),代码简洁,适用于字符集较大的情况。
方法三:使用counter,时间复杂度为o(n),空间复杂度为o(n),代码更简洁,适用于所有情况。

力扣题目链接
题目文章讲解
题目视频讲解

三、力扣349. 两个数组的交集

当题目明确说明输出结果中的每个元素一定是唯一的,并且可以不考虑输出结果的顺序时,暴力解法的时间复杂度为o(n^2)。使用哈希法可以进一步优化。

版本一 使用字典和集合

代码思路

  1. 使用字典存储nums1中的所有元素。
  2. 使用集合存储结果,遍历nums2,如果元素在字典中,则加入集合,并从字典中删除。
  3. 最终返回集合的列表形式。
class solution:
    def intersection(self, nums1: list[int], nums2: list[int]) -> list[int]:
        # 使用哈希表存储nums1中的所有元素
        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)  # 将集合转换为列表返回

版本二 使用数组

代码思路

  1. 创建两个长度为1001的数组count1和count2,分别记录nums1和nums2中元素的出现次数。
  2. 遍历nums1和nums2,更新对应数组的计数。
  3. 遍历0到1000,如果对应位置的两个数组计数都大于0,则说明该元素在两个数组中都出现过,加入结果列表。
class solution:
    def intersection(self, nums1: list[int], nums2: list[int]) -> list[int]:
        count1 = [0]*1001  # 初始化计数数组
        count2 = [0]*1001
        result = []
        
        for num in nums1:
            count1[num] += 1  # 记录nums1中的元素出现次数
        for num in nums2:
            count2[num] += 1  # 记录nums2中的元素出现次数
        
        for i in range(1001):
            if count1[i] > 0 and count2[i] > 0:  # 如果元素在两个数组中都出现过
                result.append(i)
        
        return result  # 返回结果列表

版本三 使用集合

代码思路

  1. 使用集合记录nums1和nums2中的元素。
  2. 计算两个集合的交集并返回其列表形式。
class solution:
    def intersection(self, nums1: list[int], nums2: list[int]) -> list[int]:
        return list(set(nums1) & set(nums2))  # 计算两个集合的交集并转换为列表返回

力扣题目链接
题目文章讲解
题目视频讲解

四、力扣202. 快乐数

版本一 使用集合检测循环

代码思路

  1. 使用一个集合 seen 来记录出现过的数字。如果一个数字再次出现,说明陷入了循环。
  2. 每次将数字替换为其各位数字平方和。
  3. 如果数字变为1,则返回 true;如果数字在集合中再次出现,则返回 false。
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,返回true

版本二 使用列表检测循环

代码思路

  1. 使用一个列表 seen 来记录出现过的数字。如果一个数字再次出现,说明陷入了循环。
  2. 每次将数字替换为其各位数字平方和。
  3. 如果数字变为1,则返回 true;如果数字在列表中再次出现,则返回 false。
class solution:
   def ishappy(self, n: int) -> bool:
       seen = []  # 创建一个列表记录出现过的数字
       while n != 1:
           n = sum(int(i) ** 2 for i in str(n))  # 计算数字每位平方和
           if n in seen:  # 如果数字在列表中再次出现,说明陷入循环
               return false
           seen.append(n)  # 将当前数字加入列表
       return true  # 数字变为1,返回true

版本三 快慢指针法检测循环

代码思路

  1. 使用快慢指针法检测数字序列是否存在循环(类似于链表中的环检测)。
  2. 快指针每次移动两步,慢指针每次移动一步。
  3. 如果两个指针相遇,说明存在循环,返回 false。
  4. 如果快指针变为1,返回 true。
class solution:
   def ishappy(self, n: int) -> bool:        
       slow = n  # 慢指针初始化为n
       fast = n  # 快指针初始化为n
       
       # 使用快慢指针法检测循环
       while self.get_sum(fast) != 1 and self.get_sum(self.get_sum(fast)) != 1:
           slow = self.get_sum(slow)  # 慢指针每次移动一步
           fast = self.get_sum(self.get_sum(fast))  # 快指针每次移动两步
           
           # 如果快慢指针相遇,说明存在循环,不是快乐数
           if slow == fast:
               return false
       
       # 如果快指针遇到1,说明是快乐数
       return true
   
   def get_sum(self, n: int) -> int: 
       new_num = 0  # 初始化新数值为0,用于存储各位数字平方和
       while n:
           n, r = divmod(n, 10)  # 使用divmod函数同时计算n除以10的商和余数
           new_num += r ** 2  # 将当前位数字的平方累加到new_num中
       return new_num  # 返回所有位数字平方和的结果

版本一:使用集合检测循环。通过记录已经出现过的数字,检测是否陷入循环。空间复杂度为o(n),时间复杂度为o(log n)。
版本二:使用列表检测循环。原理与方法一相同,只是使用列表记录出现过的数字。空间复杂度为o(n),时间复杂度为o(log n),但查找时间复杂度较高。
版本三:使用快慢指针法。通过快慢指针检测循环,空间复杂度为o(1),时间复杂度为o(log n)。适合大数据量的情况。

力扣题目链接
题目文章讲解

五、力扣1. 两数之和

版本一 使用字典

代码思路

  1. 使用一个字典 records 来存放访问过的元素及其对应的下标。
  2. 遍历数组 nums,对于每个元素 value,计算其对应的补数 target - value。
  3. 如果补数存在于 records 中,说明找到了两个数的和为 target,返回它们的下标。
  4. 如果补数不存在,则将当前元素 value 及其下标 index 存入字典 records 中。
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:  # 在字典中寻找是否有匹配的key
                return [records[target - value], index]  # 找到匹配对,返回下标
            records[value] = index  # 将访问过的元素和下标加入到字典中
        return []  # 如果没有找到匹配对,返回空列表
        
版本二 使用集合

代码思路

  1. 使用一个集合 seen 来存储已经访问过的数字。
  2. 遍历数组 nums,对于每个元素 num,计算其对应的补数 target - num。
  3. 如果补数存在于集合 seen 中,说明找到了两个数的和为 target,返回它们的下标。
  4. 如果补数不存在,则将当前元素 num 加入集合 seen 中。
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)  # 将当前元素加入集合
        return []  # 如果没有找到匹配对,返回空列表

版本三 使用双指针

代码思路

  1. 对输入列表 nums 进行排序,生成一个新的有序列表 nums_sorted。
  2. 使用双指针 left 和 right,分别指向排序后列表的头和尾。
  3. 计算双指针指向元素的和 current_sum。
  4. 如果 current_sum 等于 target,则找到两个数,返回它们在原列表中的下标。
  5. 如果 current_sum 小于 target,将左指针右移。
  6. 如果 current_sum 大于 target,将右指针左移。
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  # 总和大于目标,右指针左移
        return []  # 如果没有找到匹配对,返回空列表

版本一:使用字典:时间复杂度为o(n),空间复杂度为o(n),适用于数值范围较大的情况。
版本二:使用集合:时间复杂度为o(n),空间复杂度为o(n),代码简洁,但在返回下标时需要额外处理。
版本三:使用双指针:时间复杂度为o(nlogn),因为需要先排序,空间复杂度为o(n),适用于需要排序并找到匹配对的情况。

力扣题目链接
题目文章讲解
题目视频讲解

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com