leetcode 49.字母异位词分组

76 阅读3分钟

1. 题目链接

leetcode.cn/problems/gr…

2. 思路

这个题目的一个技巧就是怎么去让满足条件的一组单词能映射到同一个值上。并且这种映射需要具有唯一性(类似于hash方式,但是这个映射不应该发生碰撞)我在尝试的过程中思考过几种方式:

2.1 字符串直接每一个字母映射成数值相乘。

但是出现问题也很明显,字符串太长时会出现越界。
另外其实可能出现不同的数相乘,也可能出现相同的结果,比如2x100和4x50,出现hash碰撞。这个方法是有问题的。

size_t myHash(const string &str) const {
    size_t res = 1;
    for(auto &ch : str) {
        res = res * ch; // +107避免a为0
    } 
    return res;
}

2.2 使用质数相乘

改进1的方式,换成质数相乘,因为根据算术的基本定理,任何一个数都可以分解为一组质数的乘积,这个是唯一的。所以,我们可以先打表出26个质数,在此基础上用乘积结果来计算hash。

vector<int> pureNums = {2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101,103};


auto myHash = [=](const string &s) {
    long long res = 1;
    for (auto& ch : s) {
        res = (res * pureNums[ch - 'a']) % static_cast<long long>(1e10 + 7);     // mod一个大质数防止越界
    }

    return res;
};

完整的代码:

class Solution {
public:
    vector<int> pureNums = {2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101,103};

    vector<vector<string>> groupAnagrams(vector<string>& strs) {
        vector<vector<string>> res;

        auto mHashString = [=](const string &s) {
            long long res = 1;
            for (auto& ch : s) {
                res = (res * pureNums[ch - 'a']) % static_cast<long long>(1e10 + 7);
            }

            return res;
        };

        unordered_multimap<long long, string> mMap;

        for (auto& s : strs) {
            mMap.insert({ mHashString(s), s });
        }

        for (auto it = mMap.begin(); it != mMap.end(); ) {
            auto range = mMap.equal_range(it->first);

            vector<string> tvec;
            while (range.first != range.second) {
                tvec.push_back(std::move(range.first->second));
                ++range.first;
            }

            res.push_back(std::move(tvec));
            it = range.second;
        }

        return res;
    }
};

2.3 构建一种“模式字符串”。

官方题解中2的方式。leetcode.cn/problems/gr…
对于2中的方法,确实是个比较好的方式,但是依然需要知道26个质数,不是很方便。
既然只需要统计字符串中字母对应数量是否一致。而字母范围又是有限的26个,我们干脆把字符串映射成“a*b*c*”这样的结构,其中*表示前面字母的出现数量,并且是按照字母顺序。比如daaabcc映射成"a3b1c2d1",再把同一映射结果的字符串分为一类则可。
完整代码:

class Solution {
public:
    string getHashStr(const string &str) {
        size_t res = 1;
        int num[26] = {0};
        for(auto &ch : str) {
            ++num[ch - 'a'];
        }
        string toHashStr;
        for(int i = 0; i < 26; ++i) {
            char ch = 'a' + i;
            toHashStr += ch;
            toHashStr += num[i];
        }
        return toHashStr;
    }

    unordered_map<string, vector<string>> mMap;
    vector<vector<string>> groupAnagrams(vector<string>& strs) {
        for(auto &str : strs) {
            auto hashStr = getHashStr(str);
            if(mMap.find(hashStr) == mMap.end()) {
                mMap[hashStr] = vector<string> {str};
            } else {
                mMap[hashStr].push_back(str);
            }
        }

        vector<vector<string>> res;

        for(auto &key : mMap) {
            res.push_back(key.second);
        }

        return res;
    }
};