0%

热题100 – 哈希

题源一:49. 字母异位词分组 – 力扣(LeetCode) [把字母数量相同的字符串分到一组]

方法一 基于排序

class Solution {
public: 
    vector<vector<string>> groupAnagrams(vector<string>& strs) {
        unordered_map<string, vector<string>> mp;
        for(const string& s: strs){
            string key = s;
            sort(key.begin(), key.end());
            mp[key].emplace_back(s);
        }

        vector<vector<string>> ans;
        for(auto it = mp.begin(); it != mp.end(); it++){
            ans.emplace_back(it->second);
        }
        return ans;
    }
};

/*

一、知识点:
1、map 实现是通过红黑树实现的; 在map中,红黑树的每个节点就代表一个元素,因此实现对map的增删改查 O(logn)
unordered_map 是通过哈希表 散列函数实现的  O(1)

2、遍历stl容器方法(vector)
for(auto s: strs){
    string key = s;
    ...
}

3、emplace_back && push_back [vector容器]
操作方式	push_back	                                    emplace_back
构造过程	先构造临时对象,再移动/复制	                直接在容器内存位置构造
参数传递	接受对象实例	                                接受构造函数参数
对象创建	至少1次构造 + 1次移动/复制	                        仅1次构造

推荐使用emplace_back

4、sort 原地排序

二、思想 (排序)
将每个字符串按字符进行排序,再进行哈希匹配,若匹配失败,则需要将键值存入;否则,在值数组中加入字符串
*/

方法二 基于计数

class Solution {
public: 
    vector<vector<string>> groupAnagrams(vector<string>& strs) {
        //  自定义array<int, 26>哈希函数 (佬写的太离谱了)
        auto arrayHash = [fn = hash<int>{}](const array<int, 26> &arr) -> size_t{
            return accumulate(arr.begin(), arr.end(), 0u, [&](size_t acc, int num){
                return (acc << 1) ^ fn(num);
                });
            };
        
        cout << hash<int>{}(3) << endl;
        /*
            解析上述代码:
            arrayHash - 自定义的哈希函数类型
            [fn = hash<int>{}] - 匿名函数 - 其中fn为成员对象
            (const array<int, 26> & arr) - array<int, 26>表示包含26个整数的数组 (字母频率统计)
            -> size_t  明确返回类型(无符号整型)

            accumulate(起始位置,终止位置,初始累加值,自定义累加规则)
            [&] - lambda捕获外部变量,此处捕获 fn,用于下面的 fn(num)
            (size_t acc, int num) 当前累加值,当前元素值

            每个累加值操作,例如:
                acc初始值 = 0
                acc << 1 = 0 << 1 = 0
                fn(3) = 3
                结果 = 0 ^ 3 = 3
        */

        unordered_map<array<int, 26>, vector<string>, decltype(arrayHash)> mp(0, arrayHash);
        for(string& str: strs){
            array<int, 26> counts{};
            int length = str.length();
            // counts 数组会完整记录该字符串的字母组成
            for(int i = 0; i < length; ++i){
                counts[str[i] - 'a'] ++;
            }
            mp[counts].emplace_back(str);
        }

        /*
            counts先记录下字符串各字母的数量,然后通过哈希函数存入哈希表中
        */

        vector<vector<string>> ans;
        for(auto it = mp.begin(); it != mp.end(); it++){
            ans.emplace_back(it->second);
        }

        return ans;
    }

};

/*
算法思想其实不是很难(但也绝对不容易),即 字符串的每个字母出现次数进行计数,再经过哈希函数映射
此处的哈希函数为 (acc << 1) ^ fn(num)  --  为什么不直接相加? 规避非异位体的累加和相同
*/

 

题源二:128. 最长连续序列 – 力扣(LeetCode)

方法一 排序

class Solution {
public:
    int longestConsecutive(vector<int>& nums) {
        vector<int> copynum;
        copynum.resize(nums.size());
        copy(nums.begin(), nums.end(), copynum.begin());
        sort(copynum.begin(), copynum.end(), less<int>());
        // 查找各个连续序列
        vector<vector<int>> vec;
        vector<int> tmp;
        int i = 0;
        // 注意:请勿将最后一组tmp漏插入到vec中
        while(i < copynum.size()){
            /*
                有如下情况:
                    1、tmp为空
                    2、tmp不为空,copynum[i] - tmp.back() == 0
                    3、tmp不为空, copynum[i] - tmp.back == 1
                    4、tmp不为空, copynum[i] - tmp.back != 1
            */
            if(tmp.empty()){
                tmp.emplace_back(copynum[i++]);
            }


            else if(copynum[i] - tmp.back() == 0){
                i++;
                continue;
            }
            else if(copynum[i] - tmp.back() == 1){
                tmp.emplace_back(copynum[i++]);
            }


            else{
                vec.emplace_back(tmp);
                tmp.clear();
            }
        }


        if(!tmp.empty()){
            vec.emplace_back(tmp);
            tmp.clear();
        }
        int maxseqlen = 0;
        for(i = 0; i < vec.size(); ++i)
            maxseqlen = (maxseqlen < vec[i].size())? vec[i].size():maxseqlen;
        return maxseqlen;
    }
};
/*
    遇到的问题:
        1、循环结束时未将最后一组排序数组tmp加入到 vec中
        2、没有考虑到重复情况 ([1, 0, 1, 2])
    方法优化:可以先用nuordered_set去重
    思想优化:只找开头
*/

 

方法二 找开头

class Solution {
public:
    int longestConsecutive(vector<int>& nums) {
        unordered_set<int> num_set;
        for(const int& num: nums)
            num_set.insert(num);
        
        int Maxseqlen = 0;
        
        for(const int& num: num_set){
            // 找开头,非开头直接跳过
            // 否则,就作为开头循环查找num_set内是否有下一个值
            if(!num_set.contains(num - 1)){
                int currentNum =  num;
                int currentLen = 1;

                while(num_set.contains(currentNum + 1)){
                    currentNum++;
                    currentLen++;
                }

            Maxseqlen = max(Maxseqlen, currentLen);
            }
        }

        return Maxseqlen;
    }
};

/*
1、思想方法: 找开头

unordered_set 去重 -> 判断num_set可以做开头的值 -> 循环查询作为开头的连续序列数量 -> 返回最大序列值

2、知识点:
    1)、 set & unordered_set
        <set> 它存储了一组唯一的元素,并按照一定的顺序进行排序 (基于红黑树实现)
        <unordered_set> 提供了一种基于哈希表的容器, 不保证元素的排序
    2)、 std::max
        max(3,5)
        max({1,2,3,4,5})
*/

 

题源三 1. 两数之和 – 力扣(LeetCode)

思想: 正难则反

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        unordered_map<int,int> mp;

        for(int i = 0; i < nums.size(); ++i){
            // mp 键存 target - nums[k],值存下标
            if(mp.find(nums[i]) != mp.end())
                return {mp[nums[i]], i};
            else
                mp[target - nums[i]] = i;
        }

        return {};
    }
};

/*
思想:正难则反 --  相加等于某值转化为相减,查找数组中有无值等于差值
*/