Leetcode刷题笔记Day3:哈希表

101 阅读5分钟

理论基础

常见的三种哈希结构:

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

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

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

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

映射底层实现是否有序数值是否可以重复能否更改数值查询效率增删效率
std::map红黑树key有序key不可重复key不可修改O(log n)O(log n)
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。

数组作为哈希表的题目

有效的字母异位词

bool isAnagram(string s, string t) {
    if(s.size()!=t.size()) return false;
    int alphabet[26]={0}, n=s.size();
    for(int i=0; i<n; i++) alphabet[s[i]-'a']++;
    for(int i=0; i<n; i++){
        alphabet[t[i]-'a']--;
        if(alphabet[t[i]-'a']<0) return false;
    }
    return true;
}
  • 算法复杂度:时间复杂度为O(n),空间复杂度为O(1)

赎金信

bool canConstruct(string ransomNote, string magazine) {
    int alphabet[26]={0};
    for(auto ch:magazine) alphabet[ch-'a']++;
    for(auto ch:ransomNote){
        alphabet[ch-'a']--;
        if(alphabet[ch-'a']<0) return false;
    }
    return true;
}
  • 算法复杂度:时间复杂度为O(n),空间复杂度为O(1)

set作为哈希表的题目

两个数组的交集

vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
    int a[1001]={0};
    unordered_set<int> nums_set(nums1.begin(),nums1.end());
    unordered_set<int> result;
    for(auto num:nums2)
        if(nums_set.find(num)!=nums_set.end())//说明是交集元素
            result.insert(num);
    return vector<int>(result.begin(),result.end());
}
  • 算法复杂度:时间复杂度为O(m+n),空间复杂度为O(m)

快乐数

  • 无限循环变不到1退出的逻辑:出现重复的数字
  • 力扣题目链接
  • 解法:
int getSum(int n){
    int sum=0;
    while(n){
        sum+=(n%10)*(n%10);
        n/=10;
    }
    return sum;
}
bool isHappy(int n) {
    unordered_set<int> set;
    while(1){
        int sum=getSum(n);
        if(sum==1) return true;
        if(set.find(sum)!=set.end()) return false;
        set.insert(sum);
        n=sum;
    }
}
  • 算法复杂度:时间复杂度为O(log n),空间复杂度为O(log n)(复杂度分析

map作为哈希表的题目

两数之和

vector<int> twoSum(vector<int>& nums, int target) {
    unordered_map<int,int> map;
    for(int i=0; i<nums.size(); i++){
        auto iter=map.find(target-nums[i]);
        if(iter!=map.end()) return {iter->second, i};
        map.insert(pair<int,int>(nums[i],i));
    }
    return {};
}
  • 算法复杂度:时间复杂度为O(n),空间复杂度为O(n)

四数相加II

  • 本题只要求统计解的个数,将4个数两两分组,统计前两个数a和b的和的个数放入map中,然后遍历c和d在map中寻找-(c+d)的个数累加
  • 力扣题目链接
  • 解法:
int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
        unordered_map<int,int> map;
        for(int a:nums1) for(int b:nums2) map[a+b]++;
        int cnt=0;
        for(int c:nums3) for(int d:nums4)
            if(map.find(-(c+d))!=map.end())
                cnt+=map[-(c+d)];
        return cnt;
    }
  • 算法复杂度:时间复杂度为O(n2n^2),空间复杂度为O(n2n^2),最坏情况下A和B的值各不相同,相加产生的数字个数为 n2n^2

看似用哈希表,实则用双指针更好的题目

三数之和

  • 思路:对数组进行排序,辅以一定的去重和剪枝方法
  • 力扣题目链接
  • 解法:
vector<vector<int>> threeSum(vector<int>& nums) {
    vector<vector<int>> result;
    sort(nums.begin(), nums.end());
    for(int i=0; i<nums.size(); i++){
        if(nums[i]>0) break;
        //去重方法
        if(i>0 && nums[i]==nums[i-1]) continue;
        //双指针精髓——两边夹
        int left=i+1, right=nums.size()-1;
        while(left<right){
            int sum=nums[i]+nums[left]+nums[right];
            if(sum>0) right--;
            else if(sum<0) left++;
            else{
                result.push_back(vector<int>{nums[i],nums[left],nums[right]});
                //内循环去重
                while(left<right&&nums[right]==nums[right-1]) right--;
                while(left<right&&nums[left]==nums[left+1]) left++;
                //双指针收紧
                right--; left++;
            }
        }
    }
    return result;
}
  • 算法复杂度:时间复杂度为O(n2n^2),空间复杂度为O(1)

四数之和

vector<vector<int>> fourSum(vector<int>& nums, int target) {
    vector<vector<int>> result;
    sort(nums.begin(), nums.end());
    for(int k=0; k<nums.size(); k++){
        //1级剪枝
        if(nums[k]>target && nums[k]>=0) break;
        //1级去重
        if(k>0 && nums[k]==nums[k-1]) continue;
​
        for(int i=k+1; i<nums.size(); i++){
            //2级剪枝
            if(nums[k]+nums[i]>target && nums[i]>=0) break;
            //2级去重
            if(i>k+1 && nums[i]==nums[i-1]) continue;
            int left=i+1, right=nums.size()-1;
            while(left<right){
                long sum=(long)nums[k]+nums[i]+nums[left]+nums[right]; //有溢出风险
                if(sum>target) right--;
                else if(sum<target) left++;
                else{
                    result.push_back(vector<int>{nums[k],nums[i],nums[left],nums[right]});
                    //3级去重
                    while(left<right && nums[right]==nums[right-1]) right--;
                    while(left<right && nums[left]==nums[left+1]) left++;
                    right--; left++;
                }
            }
        }
    }
    return result;
}
  • 算法复杂度:时间复杂度为O(n3n^3),空间复杂度为O(1)

参考资料

[1] 代码随想录

[2] Leetcode题解