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;
}
这样就结束了。
另外,好像也有用动态规划的方法来解的,不过笔者确实不会(笑)

浙公网安备 33010602011771号