LeetCode 题解随笔:哈希表篇

目录

前言

一、哈希表基础

242.有效的字母异位词

49.字母异位词分组

438.找到字符串中所有字母异位词

二、set实现的哈希表

349. 两个数组的交集

三、map实现的哈希表

350.两个数组的交集 II

1. 两数之和

454.四数相加II

1487. 保证文件名唯一

总结


前言

std::unordered_set底层实现为哈希表,std::set 和std::multiset 的底层实现是红黑树;

std::unordered_map 底层实现为哈希表,std::map 和std::multimap 的底层实现是红黑树。

因此要使用集合来解决哈希问题的时候,优先使用unordered_set,它的查询和增删效率最优(O(1));如果需要集合是有序的,用set,如果要求不仅有序还要有重复数据的话,用multiset。红黑树查询和增删效率为(O(logn))。

一、哈希表基础

242.有效的字母异位词

class Solution {
public:
    bool isAnagram(string s, string t) {
        unordered_map<char, int> charNum;
        for (int i = 0; i < s.size(); i++){
            charNum[s[i]]++;
        }
        for (int i = 0; i < t.size(); i++) {
            charNum[t[i]]--;
        }
        for (unordered_map<char, int>::iterator it = charNum.begin(); it != charNum.end(); it++) {
            if (it->second != 0) return false;
        }
        return true;
    }
};

鉴于元素都是小写字母,也可以定义一个长度为26的数组,存放内容为字母的ASCII码。

类似题目:383.赎金信。较为简单,此处不再贴出。

49.字母异位词分组

vector<vector<string>> groupAnagrams(vector<string>& strs) {       
        unordered_map<string, vector<string>> resMap;
        for (int i = 0; i < strs.size(); i++) {
            int counts[26] = { 0 };
            for (int j = 0; j < strs[i].size(); j++) {
                counts[strs[i][j] - 'a']++;
            }
            string countsStr;
            for (int k = 0; k < 26; k++) {
                countsStr.append(to_string('a' + k));
                countsStr.append(to_string(counts[k]));
            }
            resMap[countsStr].push_back(strs[i]);
        }
        vector<vector<string>> res;
        for (unordered_map<string, vector<string>>::const_iterator it = resMap.begin(); it != resMap.end(); it++) {
            res.push_back(it->second);
        }
        return res;
    }

本题可以选择unordered_map的键值为字符串,该字符串内容为字母+出现的次数。采用该方式,时间复杂度为O(n+k),其中k为最长字符串的长度。

另一种方式是对每个str利用sort进行排序后,作为unordered_map的键值。由于sort的时间复杂度为O(logk),从而这一方法的时间复杂度为O(nlogk)。

438.找到字符串中所有字母异位词

vector<int> findAnagrams(string s, string p) {
        vector<int> res;
        unordered_map<char, int> charT;
        for (int i = 0; i < p.size(); i++) {
            charT[p[i]]++;
        }
        //储存目标字符串待比较信息
        unordered_map<char, int> charTemp = charT;
        int count = p.size();
        int slow = 0;
        int fast = 0;
        while (fast < s.size()) {          
            //快指针比慢指针提前移动字符p的长度
            if (fast < p.size()) {           
                if (charT[s[fast]] > 0) {
                    if (charTemp[s[fast]] > 0)   count--;
                    charTemp[s[fast]]--;
                }
            }
            //同时前移快指针和慢指针
            else {
                if (charT[s[slow]] > 0) {
                    if(charTemp[s[slow]] >= 0)  count++;
                    charTemp[s[slow]]++;
                }
                slow++;  
                if (charT[s[fast]] > 0) {
                    if(charTemp[s[fast]] > 0)   count--;
                    charTemp[s[fast]]--; 
                }
            }
            if (count == 0) {
                res.push_back(slow);
            }
            fast++;
        }
        return res;
    }

本题采用了滑动窗口法,类似于数组篇的查找最小子串。采用count标记是否满足要求,两个指针间隔长度为字符串p的长度。滑动窗口法的关键是判断什么时候移动快慢指针,且相应地要进行什么操作

上述比较小写字母的问题,其实都可以用int count[26]的数组来作为哈希表,进行条件的判断。


二、set实现的哈希表

349. 两个数组的交集

vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
        unordered_set<int> nums(nums1.begin(),nums1.end());
        unordered_set<int> resSet;
        for (int i : nums2) {
            if (nums.find(i) != nums.end()) {
                resSet.insert(i);
            }
        }
        return vector<int>(resSet.begin(),resSet.end());
    }

利用unordered_set,底层实现是哈希表而非红黑树,读写效率最高。本题不使用数组是原因是,待分类元素的数量不确定。

注意基于范围的遍历方式:for(auto i:nums);以及vector的初始拷贝赋值方式。


三、map实现的哈希表

350.两个数组的交集 II

vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
        vector<int> res;
        unordered_map<int, int> nums;
        for (auto i : nums1) {
            nums[i]++;
        }
        for (auto j : nums2) {
            if (nums[j] > 0) {
                res.push_back(j);
                nums[j]--;
            }
        }
        return res;
    }

与349类似,只是需要判断该元素是否已输出完毕(取两数组中数量最小者)。

1. 两数之和

vector<int> twoSum(vector<int>& nums, int target) {
        unordered_map<int, int> numsMap;
        vector<int> res;
        //只需要循环一次,没找到时在unorder_map中插入
        for (int i = 0; i < nums.size(); i++) {
            auto it = numsMap.find(target - nums[i]);
            //找到时一定是数值对的第二个数
            if (it != numsMap.end()) {
                res.push_back(it->second);
                res.push_back(i);
                return res;
            }
            numsMap.insert(make_pair(nums[i], i));
        }
        //没找到返回空
        return {};
    }

本题技巧性很强,利用unordered_map实现的哈希表,只需要一次循环就能完成。一遍查找一边添加数据,返回时利用哈希表查找到的一定是数值对的第一个数据,结合当前下标一起返回即可。

没找到时返回空的语句一定要写,否则会报错。

454.四数相加II

int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
        unordered_map<int, int> sumMap;
        int res = 0;
        //存放A、B数组元素之和及出现的次数
        for (auto i : nums1) {
            for (auto j : nums2) {
                sumMap[i + j]++;
            }
        }
        for (auto k : nums3) {
            for (auto l : nums4) {
                auto it = sumMap.find(-(k + l));
                if (it != sumMap.end()) {
                    res += it->second;
                }
            }
        }
        return res;
    }

不知道本题有没有比O(n^2)更好的算法,不过这个O(n^2)的算法也比较有技巧性。

其中分组的思想要多加熟悉。遍历A、B数组并将元素之和以及出现的次数记录下来,再遍历C、D数组,查找unordered_map中对应的负值元素出现次数。

1487. 保证文件名唯一

    vector<string> getFolderNames(vector<string>& names) {
        vector<string> res;
        unordered_map<string, int> record;
        for (string name : names) {
            // 不存在该前缀
            if (record.find(name) == record.end()) {
                record[name] = 1;
                res.push_back(name);
            }
            // 存在该前缀
            else {
                string new_name = name;
                while (record.find(new_name) != record.end()) {
                    new_name = name + "(" + to_string(record[new_name]) + ")";
                    record[name]++;
                }
                res.push_back(new_name);
                record[new_name]++;
            }
        }
        return res;
    }

​​​​​​​


总结

  • 哈希表大小固定时,采用数组作为哈希表。例如小写字母的映射;
  • 哈希值比较少、特别分散、跨度非常大,数组大小不确定时,采用set作为哈希表。例如求数组的交集。不需要对数据进行排序,且还不要让数据重复时采用unordered_set,底层实现为哈希而非红黑树,查找效率最高;
  • 需要记录键值和数值时,采用map作为哈希表。类型选用思路与上述set一致。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值