首页 > 编程语言 >【算法】哈希表

【算法】哈希表

时间:2023-09-23 12:49:05浏览次数:45  
标签:right return nums int 算法 哈希 left

1 哈希表理论基础

1.1 哈希表

哈希表是根据关键码的值而直接进行访问的数据结构。一般哈希表都是用来快速判断一个元素是否出现集合里。

1.2 哈希函数

哈希函数如下图所示,通过hashCode把名字转化为数值,一般hashcode是通过特定编码方式,可以将其他数据格式转化为不同的数值。如果hashCode得到的数值大于tableSize,此时为了保证映射出来的索引数值都落在哈希表上,会再次对数值做一个取模的操作。

好几个名字同时映射到哈希表同一个索引下标的位置,叫哈希碰撞

一般有两种解决方法, 拉链法和线性探测法。

拉链法

发生冲突的元素都被存储在链表中。拉链法就是要选择适当的哈希表的大小,这样既不会因为数组空值而浪费大量内存,也不会因为链表太长而在查找上浪费太多时间。

线性探测法

使用线性探测法,一定要保证tableSize大于dataSize,依靠哈希表中的空位来解决碰撞问题。

例如冲突的位置放了小李,那么就向下找一个空位放置小王的信息,如图所示:

1.3 常见的三种哈希结构

  • 数组
  • set (集合)
  • map(映射)

在C++中,set 和 map 分别提供以下三种数据结构,其底层实现以及优劣如下表所示:

集合底层实现是否有序数值是否可以重复能否更改数值查询效率增删效率
std::set 红黑树 有序 O(log n) O(log n)
std::multiset 红黑树 有序 O(logn) O(logn)
std::unordered_set 哈希表 无序 O(1) O(1)

std::unordered_set底层实现为哈希表,std::set 和std::multiset 的底层实现是红黑树,红黑树是一种平衡二叉搜索树,所以key值是有序的,但key不可以修改,改动key值会导致整棵树的错乱,所以只能删除和增加。

当我们要使用集合来解决哈希问题的时候,优先使用unordered_set,因为它的查询和增删效率是最优的,如果需要集合是有序的,那么就用set,如果要求不仅有序还要有重复数据的话,那么就用multiset。

映射底层实现是否有序数值是否可以重复能否更改数值查询效率增删效率
std::map 红黑树 key有序 key不可重复 key不可修改 O(logn) O(logn)
std::multimap 红黑树 key有序 key可重复 key不可修改 O(log n) O(log n)
std::unordered_map 哈希表 key无序 key不可重复 key不可修改 O(1) O(1)

map 是一个key-value的数据结构,map中对key是有限制,对value没有限制的,因为key的存储方式使用红黑树实现的。std::unordered_map 底层实现为哈希表,std::map 和std::multimap 的底层实现是红黑树。同理,std::map 和std::multimap 的key也是有序的。

1.4 总结

当我们遇到了要快速判断一个元素是否出现集合里的时候,就要考虑哈希法

但是哈希法也是牺牲了空间换取了时间,因为我们要使用额外的数组,set或者是map来存放数据,才能实现快速的查找。

2 有效的字母异位词*

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

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

说明: 字符串只包含小写字母。

示例 1:

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

示例 2:

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

思路

  1. 数组
  2. 数组其实就是一个简单哈希表,而且这道题目中字符串只有小写字符,那么就可以定义一个大小为26的数组record,来记录字符串s里字符出现的次数。需要把字符映射到数组索引下标上,因为字符a到字符z的ASCII是26个连续的数值,所以字符a映射为下标0,相应的字符z映射为下标25。

    在遍历字符串s的时候,**只需要将 s[i] - ‘a’ 所在的元素做+1 操作即可,**这样就将字符串s中字符出现的次数统计出来了。同样在遍历字符串t的时候,对t中出现的字符映射哈希表索引上的数值再做-1的操作。

    最后检查一下,**record数组如果有的元素不为零0,说明字符串s和t一定是谁多了字符或者谁少了字符,return false。**最后如果record数组所有元素都为零0,说明字符串s和t是字母异位词,return true。

    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
    

    时间复杂度: O(n),空间复杂度: O(1)

  3. defaultdict
  4. defaultdict使得在处理字典时更加方便,无需在访问不存在的键之前检查键是否存在,而是可以直接使用它们。

    class Solution:
        def isAnagram(self, s: str, t: str) -> bool:
            from collections import defaultdict
            # 创建一个 defaultdict,指定默认值为 int 类型,即默认值为 0
            s_dict = defaultdict(int)
            t_dict = defaultdict(int)
            for x in s:
                s_dict[x] += 1   # 访问不存在的键不会引发 KeyError,而是使用默认值 0 创建该键,可以在不提前设置默认值的情况下递增不存在的键的值
            for x in t:
                t_dict[x] += 1
            return s_dict == t_dict
    
  5. Counter
  6. Counter 是 Python 中 collections 模块中的一个类,它用于计数可哈希对象(hashable objects)的出现次数。Counter 返回一个字典,其中键是可哈希对象,而值是该对象在输入序列中出现的次数Counter 是一种非常方便的工具,特别适用于统计元素的频率。

    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
    

3 两个数组的交集

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

示例 1:

输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2]

思路

要求不重复,可以用set

  1. 使用字典和集合
  2. class Solution:
        def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
            from collections import defaultdict
            dict = defaultdict(int) # 使用哈希表存储一个数组中的所有元素
            for i in nums1: 
                dict[i] += 1
    
        union = set() # 使用集合存储结果
        for i in nums2:
            if i in dict:
                union.add(i)
    
        return list(union) # 记得转回list
    

    时间复杂度:O(n),空间复杂度:O(n)

  3. 使用集合*
  4. class Solution:
        def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
            return list(set(nums1) & set(nums2))
    

    tips

    1. 交集操作:&,用于获取两个集合的共同元素,返回一个新集合,包含两个集合中都存在的元素。
    2. 并集操作: |,表示两个集合的合并,返回包含两个集合中所有不重复元素的新集合。
    3. 差集操作:-,用于从一个集合中移除另一个集合中包含的元素,返回一个新集合,包含在第一个集合中但不在第二个集合中的元素。
    4. 对称差集操作:^,用于获取两个集合中不重复的元素,返回一个新集合,包含只存在于一个集合中的元素。

思考:为什么不直接所有题目都用set?

直接使用set 不仅占用空间比数组大,而且速度要比数组慢,set把数值映射到key上都要做hash计算。不要小瞧这个耗时,在数据量大的情况,差距是很明显的。

4 快乐数*

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

「快乐数」 定义为:

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

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

示例 1:

输入:n = 19
输出:true
解释:
12 + 92 = 82
82 + 22 = 68
62 + 82 = 100
12 + 02 + 02 = 1

思路**

题目中说了会无限循环,也就是说求和的过程中,sum会重复出现,这对解题很重要。

当我们遇到了要快速判断一个元素是否出现集合里的时候,就要考虑哈希法了。

所以这道题目使用哈希法,来判断这个sum是否重复出现,如果重复了就是return false, 否则一直找到sum为1为止

  1. 集合解法一(divmod
  2. 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
    

  3. 集合解法二(字符串↔int
  4. 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
    
  5. 集合解法三(精简**
  6. 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
    

5 两数之和

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

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。

你可以按任意顺序返回答案。

示例 1:

输入:nums = [3,2,4], target = 6
输出:[1,2]

示例 2:

输入:nums = [3,3], target = 6
输出:[0,1]

思路

本题需要一个集合来存放我们遍历过的元素,然后在遍历数组的时候去询问这个集合,某元素是否遍历过,也就是是否出现在这个集合。因为本题不仅要知道元素有没有遍历过,还要知道这个元素对应的下标,需要使用 key value结构来存放,key来存元素,value来存下标,那么使用map正合适

  1. 字典法
  2. 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中
            return []
    
  3. 集合法
  4. class Solution:
        def twoSum(self, nums: List[int], target: int) -> List[int]:
            seen = set() #创建一个集合来存储我们目前看到的数字
            for idx, val in enumerate(nums):
                if target - val in seen:
                    return [nums.index(target - val), idx]
                seen.add(val)
    

    时间复杂度:O(n),空间复杂度:O(n)

6 四数相加

题目:给你四个整数数组 nums1nums2nums3 和 nums4 ,数组长度都是 n ,请你计算有多少个元组 (i, j, k, l) 能满足:nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0

示例 1:

输入:nums1 = [1,2], nums2 = [-2,-1], nums3 = [-1,2], nums4 = [0,2]
输出:2
解释:
两个元组如下:
1. (0, 0, 0, 1) -> nums1[0] + nums2[0] + nums3[0] + nums4[1] = 1 + (-2) + (-1) + 2 = 0
2. (1, 1, 0, 0) -> nums1[1] + nums2[1] + nums3[0] + nums4[0] = 2 + (-1) + (-1) + 0 = 0

思路*

本题解题步骤:

  1. 首先定义一个unordered_map,key放a和b两数之和,value 放a和b两数之和出现的次数。
  2. 遍历大A和大B数组,统计两个数组元素之和,和出现的次数,放到map中。
  3. 定义int变量count,用来统计 a+b+c+d = 0 出现的次数。
  4. 在遍历大C和大D数组,找到如果 0-(c+d) 在map中出现过的话,就用count把map中key对应的value也就是出现次数统计出来。
  5. 最后返回统计值 count 就可以了
class Solution:
    def fourSumCount(self, nums1: List[int], nums2: List[int], nums3: List[int], nums4: List[int]) -> int:
        dict, cnt = defaultdict(int), 0
        for i in nums1:
            for j in nums2:
                dict[i + j] += 1 # 初始默认是0
        for i in nums3:
            for j in nums4:
                cnt += dict[-(i + j)] # 没有的话就是0
        return cnt

时间复杂度:O(n^2),空间复杂度:O(n^2)

7 赎金信

题目:给你两个字符串:ransomNote 和 magazine ,判断 ransomNote 能不能由 magazine 里面的字符构成。

如果可以,返回 true ;否则返回 false 。

magazine 中的每个字符只能在 ransomNote 中使用一次。

示例 1:

输入:ransomNote = "a", magazine = "b"
输出:false

示例 2:

输入:ransomNote = "aa", magazine = "aab"
输出:true

思路

  1. defaultdict法
  2. class Solution:
        def canConstruct(self, ransomNote: str, magazine: str) -> bool:
            dict = defaultdict(int)
            for s in magazine:
                dict[s] += 1
            for s in ransomNote:
                dict[s] -= 1
            # for val in dict.values():
            #     if val < 0:
            #         return False
            # return True
            return all(val >= 0 for val in dict.values())
    
  3. Counter法*
  4. from collections import Counter
    class Solution:
        def canConstruct(self, ransomNote: str, magazine: str) -> bool:
            return not Counter(ransomNote) - Counter(magazine)
    
  5. count法*
  6. class Solution:
        def canConstruct(self, ransomNote: str, magazine: str) -> bool:
            return all(ransomNote.count(c) <= magazine.count(c) for c in set(ransomNote))
    

8 三数之和*

题目:给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != ji != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请你返回所有和为 0 且不重复的三元组。

示例 1:

输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]

思路

  1. 哈希法
  2. 去重很容易超时!需要剪枝(有点恶心就跳过吧)

  3. 双指针法
  4. 这题双指针法比哈希法高效一点。首先将数组排序,有一层for循环,i从下标0的地方开始,同时定一个下标left 定义在i+1的位置上,定义下标right 在数组结尾的位置上。

    依然还是在数组中找到 abc 使得a + b +c =0,我们这里相当于 a = nums[i],b = nums[left],c = nums[right]。

    接下来如何移动left 和right呢, 如果nums[i] + nums[left] + nums[right] > 0 就说明此时三数之和大了,因为数组是排序后了,所以right下标就应该向左移动,这样才能让三数之和小一些。

    如果 nums[i] + nums[left] + nums[right] < 0 说明此时 三数之和小了,left 就向右移动,才能让三数之和大一些,直到left与right相遇为止。

    class Solution:
        def threeSum(self, nums: List[int]) -> List[List[int]]:
            nums.sort()
            res = []
            # res = set()
            for i in range(len(nums) - 2):
                left, right = i + 1, len(nums) - 1
                # 剪枝
                if nums[i] > 0:
                    break
                elif nums[i] + nums[left] > 0:
                    break
                if i > 0 and nums[i] == nums[i - 1]: # a去重
                    continue
                while left < right:
                    if nums[i] + nums[left] + nums[right] < 0:
                        left += 1
                    elif nums[i] + nums[left] + nums[right] > 0:
                        right -= 1
                    else:
                        res.append([nums[i], nums[left], nums[right]])
                        # 去重逻辑应该放在找到一个三元组之后,对b 和 c去重
                        while left < right and nums[left] == nums[left + 1]:
                            left += 1
                        while left < right and nums[right] == nums[right - 1]:
                            right -= 1
    
                    # res.add((nums[i], nums[left], nums[right]))
    
                    left += 1
                    right -= 1
        
        return res
        # return list(res)
    

9 四数之和**(经典

题目:给定一个包含 n 个整数的数组 nums 和一个目标值 target,判断 nums 中是否存在四个元素 a,b,c 和 d ,使得 a + b + c + d 的值与 target 相等?找出所有满足条件且不重复的四元组。

示例 1:

输入:nums = [1,0,-1,0,-2,2], target = 0
输出:[[-2,-1,1,2],[-2,0,0,2],[-1,0,0,1]]

思路**

注意细节:不要判断nums[k] > target 就返回了,三数之和可以通过 nums[i] > 0 就返回了,因为 0 已经是确定的数了,四数之和这道题目 target是任意值。比如:数组是[-4, -3, -2, -1]target-10,不能因为-4 > -10而跳过。

  1. 双指针法
  2. class Solution:
        def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
            nums.sort()
            res = []
            for i in range(len(nums)):
                if i > 0 and nums[i] == nums[i - 1]: # a去重
                    continue
                if nums[i] > target and nums[i] >= 0: # 剪枝(optional
                    break
                for j in range(i+1, len(nums)):
                    left, right = j + 1, len(nums) - 1
                    sum = nums[i] + nums[j]
                    if sum > target and sum >= 0: # 剪枝(optional
                        break
                    if j > i + 1 and nums[j] == nums[j - 1]: # b去重
                        continue
                    while left < right:
                        if sum + nums[left] + nums[right] < target:
                            left += 1
                        elif sum + nums[left] + nums[right] > target:
                            right -= 1
                        else:
                            res.append([nums[i], nums[j], nums[left], nums[right]])
                            while left < right and nums[left] == nums[left + 1]:
                                left += 1
                            while left < right and nums[right] == nums[right - 1]:
                                right -= 1
                            left += 1
                            right -= 1
    
        return res
    

  3. 字典法(适用于n数之和题目/要求输出为值*
  4. class Solution(object):
        def fourSum(self, nums, target):
            # 创建一个字典来存储输入列表中每个数字的频率
            freq = defaultdict(int)
            for i in nums:
                freq[i] += 1
    
        # 创建一个集合来存储最终答案,并遍历4个数字的所有唯一组合
        ans = set()
        for i in range(len(nums)):
            for j in range(i + 1, len(nums)):
                for k in range(j + 1, len(nums)):
                    val = target - (nums[i] + nums[j] + nums[k])
                    if val in freq:
                        # 确保没有重复
                        count = (nums[i] == val) + (nums[j] == val) + (nums[k] == val)
                        if freq[val] &gt; count:
                            ans.add(tuple(sorted([nums[i], nums[j], nums[k], val])))
        
        return [list(x) for x in ans]
    

标签:right,return,nums,int,算法,哈希,left
From: https://www.cnblogs.com/Aikoin/p/17724212.html

相关文章

  • 【算法】字符串
    1反转字符串题目:编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。不要给另外的数组分配额外的空间,你必须**原地修改输入数组**、使用O(1)的额外空间解决这一问题。1.双指针classSolution:defreverseString(self,s:List[str])......
  • 网络拥塞控制算法总结-PolyCC
    字节跳动在SIGCOMM'23以Poster形式提交了一篇论文《PolyCC:Poly-AlgorithmicCongestionControl》,试图将各种拥塞控制算法整合到一个统一的框架里。其理由是近40年来各种渠道发布的各种拥塞控制算法,没有一种算法能解决所有网络场景(不同的应用,不同的流量模型等)。 如上图,PolyCC......
  • 【算法】链表
    1链表理论基础链表是一种通过指针串联在一起的线性结构,每一个节点由两部分组成,一个是数据域一个是指针域(存放指向下一个节点的指针),最后一个节点的指针域指向null(空指针的意思)。链表的入口节点称为链表的头结点也就是head。链表中的节点在内存中不是连续分布的,而是散乱分布在内......
  • 代码随想录算法训练营-动态规划-1|509. 斐波那契数、70. 爬楼梯
    509. 斐波那契数 1classSolution:2deffib(self,n:int)->int:3ifn<=2:4returnn56prev1,prev2=0,17for_inrange(2,n+1):8sum_value=prev1+prev29prev1,......
  • 【算法】数组
    1数组理论基础数组是存放在连续内存空间上的相同类型数据的集合。数组下标都是从0开始的数组内存空间的地址是连续的在删除或者增添元素时,需要移动其他元素的地址:C++要注意vector和array的区别,vector的底层实现是array,严格来讲vector是容器,不是数组。数组的元素是不能......
  • 代码随想录算法训练营day17 | ● 110.平衡二叉树 ● 257. 二叉树的所有路径 ● 404.
    110.平衡二叉树classSolution{public:intgetHeight(TreeNode*node){if(node==NULL)return0;intleftHeight=getHeight(node->left);if(leftHeight==-1)return-1;intrightHeigh......
  • LZW字典压缩算法及例程
    字典压缩算法是一种数据压缩方法,其基本原理是将重复出现的字符串或者片段用一个短的代表符号来表示,从而减小数据的存储空间。字典压缩算法通常用于无损压缩数据。一种常见的字典压缩算法是Lempel-Ziv-Welch(LZW)算法。该算法通过构建和更新一个字典来实现压缩。初始时,字典中包含......
  • 【算法】算法性能分析
    1时间复杂度1.1知识点时间复杂度是一个函数,它定性描述该算法的运行时间。通常会估算算法的操作单元数量来代表程序消耗的时间。假设算法的问题规模为n,那么操作单元数量便用函数f(n)来表示,随着数据规模n的增大,算法执行时间的增长率和f(n)的增长率相同,这称作为算法的渐近时间复......
  • 【POJ 1521】Entropy 题解(贪心算法+优先队列+哈夫曼树)
    熵编码器是一种数据编码方法,通过对删除了“浪费”或“额外”信息的消息进行编码来实现无损数据压缩。换句话说,熵编码去除了最初不需要的信息,以准确编码消息。高度的熵意味着一条消息包含大量浪费的信息;以ASCII编码的英文文本是具有极高熵的消息类型的示例。已经压缩的消息,如JPEG图......
  • Kafka消息压缩算法性能调优与选择
    前言Kafka作为一款高性能的分布式消息队列,其消息压缩算法的选择和调优对于系统性能的提升至关重要。本文将深入探讨Kafka消息压缩算法的性能调优和选择。压缩算法的选择Kafka支持多种压缩算法,包括gzip、snappy和lz4。这些算法各有优缺点,需要根据实际情况进行选择。gzipgzip是......