【LeetCode刷题】Day05 哈希表基础Ⅰ

哈希表当时数据结构好像没有学很多,看了基础知识部分,感觉是数据库的时候学的,数据库存储方式有哈希表,树等,不同组织方式查询效率不同。
基础知识基本上记得,但是随想录中的具体语言的set,map等方法的底层实现不了解,还是要对某一门语言精通,以后还要学习(买书学学C++)。

哈希表基础

哈希表

  • 定义:哈希表是根据关键码的值而直接进行访问的数据结构
  • 解决问题:一般哈希表都是用来快速判断一个元素是否出现集合里
  • 优点:查询操作只需要**O(1)**的时间复杂度

哈希函数

  • 定义:将关键码直接映射为哈希表索引的函数
  • hash函数映射原理

在这里插入图片描述通过hashCode把名字转化为数值,一般hashcode是通过特定编码方式,可以将其他数据格式转化为不同的数值,这样就把学生名字映射为哈希表上的索引数字了。
为了保证映射出来的索引数值都落在哈希表上,我们会在再次对数值做一个取模的操作,就要我们就保证了学生姓名一定可以映射到哈希表上了。(防止hashCode得到的数值大于哈希表的大小,即tableSize)

哈希碰撞

  • 解决现象:元素数量大于哈希表大小,不同元素同时映射到同一索引下标的位置
  • 解决方法:拉链法和线性探测法

1 拉链法
定义:发生冲突元素存在链表中
注意:选择适当的哈希表的大小,使得不会因为数组空值浪费大量内存,也不会因为链表太长而在查找上浪费太多时间

2 线性探测法
定义:依靠哈希表中的空位解决碰撞问题,如果冲突,则向下找一个空位置去放冲突元素。
注意:使用线性探测法,一定要保证tableSize大于dataSize

常见的三种哈希结构

哈希法解决问题常用数据结构:

  • 数组:数据量较小,数据分布均匀用,数组快,优先用
  • set(集合):无重复的一组key值,数据量较大,数据分散时用,key不能更改
  • map(映射):有key value时用,key不能更改

1 set(集合)

  • C++中set提供三种数据结构,底层实现以及优劣如下:

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

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

2 map(映射)

  • C++中set提供三种数据结构,底层实现以及优劣如下:

在这里插入图片描述 std::unordered_map 底层实现为哈希表
std::map 和std::multimap 的底层实现是红黑树。同理,std::map 和std::multimap 的key也是有序的(这个问题也经常作为面试题,考察对语言容器底层的理解)。

  • 在map 是一个key value 的数据结构,map中,对key是有限制,对value没有限制的,因为key的存储方式使用红黑树实现的。

总结

  • 当我们遇到了要快速判断一个元素是否出现集合里的时候,就要考虑哈希法
  • 哈希法也是牺牲了空间换取了时间,因为我们要使用额外的数组,set或者是map来存放数据,才能实现快速的查找

LeetCode242-有效字母的异位词

题目描述:给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。
注意:若 s 和 t 中每个字符出现的次数都相同,则称 s 和 t 互为字母异位词。
输入: s = “anagram”, t = “nagaram”
输出: true
题目链接https://leetcode.cn/problems/valid-anagram/

解题思路

  • map解决
  • 数组哈希

1 map解决

  • 用两个map分别存储s,t中的字符及相应个数
  • 比较两个map中的key,value是否完全一致

python写法1

class Solution(object):
    def isAnagram(self, s, t):
        """
        :type s: str
        :type t: str
        :rtype: bool
        """
        hashs = dict();
        hasht = dict();
        for ch in s:
            if ch in hashs:
                hashs[ch] += 1;
            else:
                hashs[ch] = 1;
        for ch in t:
            if ch in hasht:
                hasht[ch] += 1;
            else:
                hasht[ch] = 1;
        if len(hashs) < len(hasht):
            temp = hashs;
            hashs = hasht;
            hasht = temp;
        for key,value in hashs.items():
            if key in hasht and hasht[key] == value:
                continue;
            else:
                return False;
        return True;
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

python写法2

  • collections 中的 defaultdict 方法
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

注意:defaultdict 与 dict 的区别
① defaultdict 括号里必须标明类型
② 如果访问字典dict中不存在的键,会引发KeyError异常,所以我们要先处理没有元素的情况;collections 库中的 defaultdict 方法则可以直接写加的操作。

python写法3

  • collections 中的 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

2 数组哈希

  • 字符映射到数组,也就是哈希表的索引下标上(字符a到字符z的ASCII是26个连续的数值,所以字符a映射为下标0,相应的字符z映射为下标25)
  • 遍历字符串s时,将 s[i] - ‘a’ 所在的元素做+1操作
  • 检查字符串t中是否出现了这些字符,在遍历字符串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

3 心得体会

没有想到数组hash法,数据量小的话哈希法要优先考虑数组,数组速度快。
哈希法:重复元素记录次数。
暴力解法没写,我感觉写起来还比较麻烦,感觉内循环要做一些删除操作才行,二刷可以写一下。

LeetCode349-两个数组的交集

题目描述:给定两个数组 nums1 和 nums2 ,返回它们的交集 。输出结果中的每个元素一定是唯一的。我们可以不考虑输出结果的顺序 。
输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2]
题目链接https://leetcode.cn/problems/intersection-of-two-arrays/

解题思路

  • set解决
  • 数组解决
  • 暴力解法
  • map解决

1 set解决

  • 将两个列表均转化为set,再做交集即可
class Solution(object):
    def intersection(self, nums1, nums2):
        """
        :type nums1: List[int]
        :type nums2: List[int]
        :rtype: List[int]
        """
        l = [];
        nums1 = set(nums1);
        nums2 = set(nums2);
        for key in nums1:
            if key in nums2:
                l.append(key);
        return l;
class Solution:
    def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
        return list(set(nums1) & set(nums2)) # set交集操作

2 数组解决

  • 题目中有元素值<1000的限制,所以可以用数组方法,更快
class Solution {
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
        unordered_set<int> result_set; // 存放结果,之所以用set是为了给结果集去重
        int hash[1005] = {0}; // 默认数值为0
        for (int num : nums1) { // nums1中出现的字母在hash数组中做记录
            hash[num] = 1;
        }
        for (int num : nums2) { // nums2中出现话,result记录
            if (hash[num] == 1) {
                result_set.insert(num);
            }
        }
        return vector<int>(result_set.begin(), result_set.end());
    }
};

3 暴力解法

  • 时间复杂度:O(n^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

4 map解决

  • 原理与set相同,只不过map存储了value
  • 注意dict() 是按照出现元素的顺序进行存储
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

5 心得体会

这道题想到hash法并不难,因为要判断一个元素是否在另一个集合中,难点在于数据结构的选择。
哈希表数据结构选择:
数组:适用于数据量小且数据分布均匀的情况,此情况下数组速度更快,因为它不需要像set,map那样算哈希函数,也不用转成内部存储的结构。
set:适用于数据量大或者数据量小且分散的情况。python里set底层就是hash,可以不用像c++,java一样考虑其他set的实现方法。
map:需要 key value 去记录次数的情况。

LeetCode202-快乐数

题目描述:编写一个算法来判断一个数 n 是不是快乐数。
「快乐数」 定义为:
对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
如果这个过程 结果为 1,那么这个数就是快乐数。
如果 n 是 快乐数 就返回 true ;不是,则返回 false 。
输入:n = 19
输出:true
解释
1 2 + 9 2 = 82 1^2 + 9^2 = 82 12+92=82
8 2 + 2 2 = 68 8^2 + 2^2 = 68 82+22=68
6 2 + 8 2 = 100 6^2 + 8^2 = 100 62+82=100
1 2 + 0 2 + 0 2 = 1 1^2 + 0^2 + 0^2 = 1 12+02+02=1
题目链接https://leetcode.cn/problems/happy-number/

解题思路

  • 熟悉个位数求和
  • 无限循环:出现重复元素,即当出现重复元素时不是快乐数
class Solution(object):
    def isHappy(self, n):
        """
        :type n: int
        :rtype: bool
        """
        def getSum(n):
            sum = 0;
            while(n):
                x = n % 10;
                sum += x**2;
                n = n // 10;
            return sum;
        sum = n;
        s = set();
        while(1):
            sum = getSum(sum);
            if sum == 1:
                return True;
            if sum not in s:
                s.add(sum);
            else:
                return False; 

心得体会

主要是个位数求和操作要熟悉,加上理解循环终止条件,无限循环的意思。

LeetCode1-两数之和

题目描述:给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1]
题目链接https://leetcode.cn/problems/two-sum/

解题思路

  • 暴力求解
  • map解决

1 暴力求解

  • 两层循环
class Solution(object):
    def twoSum(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: List[int]
        """
        res = [];
        for i in range(len(nums)):
            for j in range(len(nums)):
                if i != j and nums[i] + nums[j] == target:
                    res.append(i);
                    res.append(j);
                    return res;

2 map解决

  • 因为要存元素和元素下标,所以要用map
  • 注意字典是没有重复元素的,如果相等后面的元素会覆盖前面的元素
# map存所有元素,需要判断下标是否相同
class Solution(object):
    def twoSum(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: List[int]
        """
        res = [];
        s = dict();
        for i in range(len(nums)):
            s[nums[i]] = i;
        for i in range(len(nums)):
            if target - nums[i] in s.keys() and s[target - nums[i]] != i:
                return i, s[target - nums[i]];
# map存遍历过的元素,不用判断下标是否相同了
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 []

3 心得体会

需要判断一个值是否在一个集合里,用哈希法;
当需要存对应的两个值时,选择map数据结构。

总结

要注意哈希法的使用条件:判断一个元素是否出现在另一个集合中/判断重复出现元素。注意数组、set、map的适用情况。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值