哈希表基础:代码随想录

总结:哈希表是拿空间换时间,当需要快速知道一个元素是否在某一集合中时,可以使用。


常见的三种哈希结构:(摘自代码随想录

  • 数组

  • 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值会导致整棵树的错乱,所以只能删除和增加。

映射

底层实现

是否有序

数值是否可以重复

能否更改数值

查询效率

增删效率

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)

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

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

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


题目:242.有效的字母异位词

242. 有效的字母异位词 - 力扣(LeetCode)

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

思路:最简单的哈希表使用方式,只需要将s 中的所有字母放入哈希表中,之后便利 t 中的字母并每次检查字母是否在哈希表中,并哈希表中对应字母的值减一。若t 中的字母都能在哈希表中找到,并且在遍历完成后哈希表的值为0,表明为 字母异位词。

class Solution {
public:
    bool isAnagram(string s, string t) {
        int record[26] = {0};
        for(int i = 0;i < s.size();i++)
        {
            int index = s[i] -'a';
            record[index] ++ ;
        }
​
        for(int i = 0; i< t.size();i++)
        {
            int index = t[i] -'a';
            record[index] -- ;
        }
​
        for(int i = 0; i<26;i++)
        {
            if(record[i] != 0){
                return false;
            }
        }
        return true;
    }
};

小技巧:可以利用ascii来实现,例如t[i] -'a'就是t[i]'a'的距离为一个正整数,因此可以直接用一个数组来作为哈希表。

题目:349. 两个数组的交集

349. 两个数组的交集 - 力扣(LeetCode)

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

思路:开始的思路和上一题的思路相同,想用一个数组将所有的数值纳入其中,但是该题写出0 <= nums1[i], nums2[i] <= 1000,这说明里面的数字很大,这样数组就需要很大,相对来说set会更加的方便,且set的唯一性(Uniqueness)也便于这道题目结果的输出。(应为cpp的特性还在摸索,自己手巧了一遍代码随想录中的代码)

注意:数组的速度是比set快的,若是能用数组还是使用数组

class Solution {
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
        unordered_set<int> result;
        unordered_set<int> nums_set(nums1.begin(),nums1.end());
        
        for(int num : nums2){
            if(nums_set.find(num) != nums_set.end()){
                result.insert(num);
            }
        }
        return vector<int>(result.begin(),result.end());
    }
};

题目:202. 快乐数

202. 快乐数 - 力扣(LeetCode)

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

「快乐数」 定义为:

  • 对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。

  • 然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。

  • 如果这个过程 结果为 1,那么这个数就是快乐数。

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

思路:主要需要处理的是怎么判断是否陷入无限循环 ,此时可以使用set,将每次的计算结果放入set中,在整个计算过程中若出现计算的结果存在于set中则说明循环了。

class Solution {
public:
    int getsum(int num){
        int sum = 0;
        while(num != 0){
            sum += (num%10) * (num%10);
            num /= 10;
        }
        return sum;
    }
    bool isHappy(int n) {
        unordered_set<int> record;
        
        while(1){
            n = getsum(n);
            if(n == 1){
                return true;
            }
​
            if(record.find(n) != record.end()){
                return false;
            }
​
            record.insert(n);
        }
    }
};

题目:1.两数之和

1. 两数之和 - 力扣(LeetCode)

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

你可以假设每种输入只会对应一个答案,并且你不能使用两次相同的元素。

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

思路:第一次做的时候用的暴力破解,两层for循环😂。实际上这题可以使用map来做,只需要使用一次for循环即可。将target-num[i]即为我们需要寻找的数字,因此只需要将这个值在哈希表中寻找即可。

为什么不用set?因为需要保存数组中元素的值和索引值,因此需要使用map

注意:需要将所有操作在一次循环中完成,不要先将所有值放入map后再查找,会导致重复的问题。

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        unordered_map<int,int> nums_map;
​
        for(int i = 0; i< nums.size();i++){
            auto iter = nums_map.find(target - nums[i]);
            if(iter != nums_map.end()){
                return {iter->second,i};   //因为存放在nums_map中的值的索引一定是先于i的因此这么返回
            }
            nums_map.insert(pair<int,int>(nums[i],i));
        }
        return {};
    }
};