页面标题
GitHub Gitee

LeetCode笔记:1. 哈希表(题目49、128)

了解笔者的朋友们都知道笔者的算法基础实在是一塌糊涂,具体详见笔者四月份的那篇随笔(笑)。
碰巧笔者最近一次比赛也结束了,笔者希望趁着这个机会能巩固一下个人的算法能力,以备后面的秋招/考研复试之需。
这次笔者开的力扣笔记合集主要是以个人学习为主,希望读者朋友们不要嘲笑就好......

1. 字母异位词分组(力扣题号49)

题目:
给你一个字符串数组,请你将字母异位词(通过重新排列不同单词的字母而形成的新单词,并使用所有原字母一次)组合在一起。可以按任意顺序返回结果列表。
strs[i] 仅包含小写字母。
输入1: strs = ["eat", "tea", "tan", "ate", "nat", "bat"]
输出1: [["bat"],["nat","tan"],["ate","eat","tea"]]
输入2: strs = [""]
输出2: [[""]]
输入3: strs = ["a"]
输出3: [["a"]]

其余信息详见原题目链接
原题目链接:https://leetcode.cn/problems/group-anagrams/description/?envType=study-plan-v2&envId=top-100-liked

找字母异位词并不难,既然限制只出现小写字母,那么只要统计不同字母出现的频数即可。
比如判断两个单词之间是否互为字母异位词,只要确保它们出现的各个字母频数相同就行:

static bool isZimuyiweici(string A, string B)
{
    int AlphaBet[26] = {0};  // 26个小写字母
    for (char c : A)
        AlphaBet[c - 'a']++;  
    for (char c : B)
        AlphaBet[c - 'a']--;
    for (int i = 0; i < 26; i++)
        if (AlphaBet[i] != 0)
            return false;
    return true;
}

那么,我们需要每两个单词判断一次,就会有这样的代码:

vector<vector<string>> groupAnagrams(vector<string>& strs) 
{
    unordered_map<string, int> HashList;
    for (string Tmp : strs)
        HashList[Tmp]++;    // 记录每个单词出现次数(以备重复单词)
    vector<vector<string>> Results;
    for (int i = 0; i < strs.size(); i++)
    {
        vector<string> TmpRes;  // 一组单词易位词
        if (HashList.find(strs[i])->second != 0)  // 若出现
        {
            HashList[strs[i]]--;
            TmpRes.push_back(strs[i]);
            for (int j = i + 1; j < strs.size(); j++)
            {
                if (isZimuyiweici(strs[i], strs[j]) && HashList.find(strs[j])->second != 0)  // 判断
                {
                    HashList[strs[j]]--;
                    TmpRes.push_back(strs[j]);
                }
            }
        }
        if (TmpRes.size() != 0)
            Results.push_back(TmpRes);
    }
    return Results;
}

这种二层循环的暴力做法显然会超时,尽管也通过了 112 / 127 个测试用例。
那么,正确的解法应该是什么样的呢?首先,我们要知道:所有的字母易位词,字符经过排序后的单词应该是一样的。
就像下面的示例:

输入1: strs = ["eat", "tea", "tan", "ate", "nat", "bat"]
输出1: [["bat"],["nat","tan"],["ate","eat","tea"]]

这三组单词,第一组排序(以升序为例)后为“abt”,第二组为“ant”,第三组为“aet”。
那么,我们遍历最开始的输入的那个 vector 的时候,我只需要先取得遍历到的那个单词的“排序后的词”,然后将这个“排序后的词”作为哈希表的 key。
这样,所有排序后和之前那个“排序后的词”相等的词,全部插入到那个 key 所对应的 value 值,不就可以了吗?
这样,我们就写成:

vector<vector<string>> groupAnagrams(vector<string>& strs) {
    vector<vector<string>> Results;
    unordered_map<string, vector<string>> HashList;  // 哈希表
    for(string Tmpstr : strs)  // 遍历
    {
        string L = Tmpstr;
        sort(L.begin(), L.end());  // 排序
        HashList[L].push_back(Tmpstr);  // 排序后的词作为 key 值
    }
    for(auto it : HashList)
    {
        Results.push_back(it.second);  // 插入返回的 Results
    }
    return Results;
}

这样就可以了。

2. 最长连续序列(力扣题号128)

题目:
给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。请你设计并实现时间复杂度为 O(n) 的算法解决此问题。
输入1:nums = [100,4,200,1,3,2]
输出1:4
输入2:nums = [0,3,7,2,5,8,4,6,0,1]
输出2:9
输入3:nums = [1,0,1,2]
输出3:3

其余信息详见原题目链接
原题目链接:https://leetcode.cn/problems/longest-consecutive-sequence/description/?envType=study-plan-v2&envId=top-100-liked

这道题的问题在于限制了时间复杂度,排序后使用双指针的办法原则上是肯定不行了(排序的时间复杂度为O(nlogn))
而且,这样做排序后还需要去重。
不过,我们这里需要寻找类似“2,3,4,5,6”这样的连续序列,既然涉及到这种“有序的元素连接”,我们不妨尝试一下并查集。

不知道什么是并查集的读者可以先看看这个:https://programmercarl.com/kamacoder/图论并查集理论基础.html#背景

我们这里首先让并查集初始化,然后,让所有能连接到前一个结点(TmpNum - 1)的结点都去连接,然后再进行路径压缩。
看下面的代码:

unordered_map<int, int> HashList;  // 代码随想录中并查集示例用数组储存,我们这里用哈希表
// 每个 key 值对应的 value 自然就是它们的父节点了
int find (int u)  // 寻找父节点
{
    return (u == HashList[u]) ? u : (HashList[u] = find(HashList[u]));  // 路径压缩
    // 保证找到的父节点是“最老的父节点”(没有自己的父节点)
}
int longestConsecutive (vector<int>& nums) 
{
    if (nums.size() == 0)  // 如果一个元素都没有
        return 0;
    for (int TmpNum : nums)
        HashList.insert(pair<int, int>(TmpNum, TmpNum));  // 初始化
    for (int TmpNum : nums)
    {
        if (HashList.find(TmpNum - 1) != HashList.end())  // 前一个结点存在
            HashList[TmpNum] = TmpNum - 1;  // 连接前一个结点
    }
    int maxLen = 1;
    for (int TmpNum : nums)
    {
        maxLen = ((TmpNum - find(TmpNum) + 1) > maxLen) ? (TmpNum - find(TmpNum) + 1) : maxLen;  // 计算最大长度
    }
    return maxLen;
}

这样就结束了。

另外,好像也有用动态规划的方法来解的,不过笔者确实不会(笑)

posted @ 2025-08-06 18:59  Wintoki  阅读(13)  评论(0)    收藏  举报