一、两数之和
题目讲解:代码随想录
代码随想录相关文章已经写过
二、字母异位词分组
1. 解题思路
这道题的核心思想是使用哈希表来聚合所有字母异位词。
字母异位词有一个关键特征:它们包含的字母及各字母的数量完全相同,只是排列顺序不同。例如,"eat","tea","ate" 都是字母异位词。
我们可以利用这个特征,为每一组字母异位词生成一个唯一的键(key)。一个简单有效的方法是将字符串按照字母顺序排序。排序后,所有的字母异位词都会得到完全相同的字符串。例如,"eat","tea","ate" 排序后都是 "aet"。
具体步骤如下:
- 创建一个哈希表
unordered_map<string, vector<string>>
,其中:- 键(Key):是排序后的字符串,作为唯一的标识符。
- 值(Value) :是一个字符串向量
vector<string>
,用来存储所有具有相同排序后键(即互为字母异位词)的原始字符串。
- 遍历输入的字符串数组。
- 对于每个字符串,先创建一个副本,然后对副本进行排序。
- 用排序后的字符串作为键,在哈希表中查找。
- 将原始 字符串添加到该键对应的值(
vector<string>
)中。 - 遍历完所有字符串后,哈希表中的所有值(
vector<string>
)就是我们需要的全部分组。我们只需将它们提取出来,放入最终的结果数组即可。
2. C++ 代码实现
cpp
#include <iostream>
#include <vector>
#include <string>
#include <unordered_map>
#include <algorithm>
using namespace std;
class Solution {
public:
/**
* @brief 对字母异位词进行分组
* @param strs 字符串向量,例如 {"eat", "tea", "tan", "ate", "nat", "bat"}
* @return 返回一个二维向量,其中每个子向量包含一组字母异位词
*/
vector<vector<string>> groupAnagrams(vector<string>& strs) {
// 创建一个哈希表(unordered_map),用于存储分组结果。
// 键(key)是排序后的字符串,值(value)是存储原始字符串的向量。
// 例如:{"aet": ["eat", "tea", "ate"], "ant": ["tan", "nat"], "abt": ["bat"]}
unordered_map<string, vector<string>> map;
// 遍历输入的每一个字符串
for (const string& str : strs) {
// 创建一个临时字符串 key,它是原始字符串 str 的一个副本
string key = str;
// 对这个副本 key 进行排序。
// 排序后,所有字母异位词都会变成同一个字符串。
// 例如:"eat", "tea", "ate" 排序后都得到 "aet"
sort(key.begin(), key.end());
// 使用排序后的字符串 key 作为哈希表的键,
// 将原始字符串 str 添加到对应的向量中。
// 如果哈希表中不存在这个 key,C++ 的 unordered_map 会自动创建它。
map[key].push_back(str);
}
// 创建一个二维向量,用于存放最终的结果
vector<vector<string>> result;
// 遍历哈希表中的所有键值对
// "it.second" 就是哈希表中每个键对应的值,也就是我们分组好的字母异位词向量
for (auto const& [key, val] : map) {
result.push_back(val);
}
// 返回最终的分组结果
return result;
}
};
// --- 主函数用于测试 ---
int main() {
Solution solution;
vector<string> strs = {"eat", "tea", "tan", "ate", "nat", "bat"};
vector<vector<string>> result = solution.groupAnagrams(strs);
cout << "字母异位词分组结果:" << endl;
cout << "[" << endl;
for (const auto& group : result) {
cout << " [";
for (size_t i = 0; i < group.size(); ++i) {
cout << "\"" << group[i] << "\"";
if (i < group.size() - 1) {
cout << ", ";
}
}
cout << "]" << endl;
}
cout << "]" << endl;
return 0;
}
3. 代码逻辑解释
-
#include
头文件:<vector>
: 使用std::vector
容器。<string>
: 使用std::string
类。<unordered_map>
: 使用std::unordered_map
哈希表。<algorithm>
: 使用std::sort
排序函数。<iostream>
: 用于在main
函数中打印测试结果。
-
groupAnagrams
函数:unordered_map<string, vector<string>> map;
: 这是解法的核心数据结构。map
会将排序后的字符串(如"aet"
)映射到包含所有对应原始字符串的列表(如{"eat", "tea", "ate"}
)。for (const string& str : strs)
: 这是一个范围for
循环,用于高效地遍历输入的所有字符串。使用const&
是一个好习惯,可以避免不必要的字符串复制,提高性能。string key = str;
: 创建原始字符串的一个副本,因为我们不想修改原始字符串本身。sort(key.begin(), key.end());
: 调用标准库中的sort
函数,对key
字符串进行原地排序。begin()
和end()
返回指向字符串首尾的迭代器。map[key].push_back(str);
: 这是最巧妙的一步。map[key]
会在哈希表中查找键为key
的元素。- 如果
key
不存在 ,unordered_map
会自动创建一个新的键值对,key
作为键,值则是一个空 的vector<string>
。 - 然后,
.push_back(str)
会将当前的原始字符串str
添加到这个(可能是新建的,也可能是已存在的)向量中。
vector<vector<string>> result;
: 创建一个二维向量来存储最终结果。for (auto const& [key, val] : map)
: 这是一个结构化绑定(C++17特性),用于方便地遍历map
。val
直接代表了哈希表中的每个vector<string>
。result.push_back(val);
: 将每个分组(即map
中的每个值)添加到结果向量result
中。return result;
: 返回包含所有分组的二维向量。
三、最长连续序列
1. 解题思路
- 空间换时间 : 我们先把所有数字都放进一个哈希集合
unordered_set
中。这样做有两个好处:一是自动去除了重复的数字,二是可以在 O(1) 的平均时间内判断一个数是否存在。 - 寻找序列的起点 : 接着,我们遍历数组中的每个数字
num
。对于每个num
,我们检查一下num - 1
是否存在于哈希集合中。 - 关键优化 : 如果
num - 1
不存在 ,那么这个num
就有可能是一个新序列的起点 。这时,我们才开始以它为起点,不断检查num + 1
,num + 2
, ... 是否存在,并累加序列的长度。 - 避免重复计算 : 如果
num - 1
存在 ,说明num
只是某个序列的中间部分,而不是起点。那么我们就可以直接跳过它,因为计算它的工作会在我们遍历到这个序列的真正起点时完成。这样就避免了大量的重复计算,保证了整体时间复杂度为 O(n)。 - 更新结果: 在每次找到一个序列的末尾时,我们都更新一下已知的最长序列长度。
2. C++ 代码实现
下面的代码严格遵循了上述思路,并添加了详细的注释,力求通俗易懂。
cpp
#include <vector>
#include <unordered_set>
#include <algorithm> // 用于 std::max
class Solution {
public:
int longestConsecutive(std::vector<int>& nums) {
// 1. 将所有数字存入哈希集合,用于O(1)复杂度的快速查找,并自动去重。
std::unordered_set<int> num_set(nums.begin(), nums.end());
int longest_streak = 0;
// 2. 遍历哈希集合中的每一个数字
for (int num : num_set) {
// 3. 关键一步:判断这个数是否是一个序列的起点
// 如果它的前一个数 (num - 1) 不在集合中,它就是一个潜在的起点
if (num_set.find(num - 1) == num_set.end()) {
int current_num = num;
int current_streak = 1;
// 4. 从起点开始,不断查找下一个连续的数是否存在
while (num_set.find(current_num + 1) != num_set.end()) {
current_num += 1;
current_streak += 1;
}
// 5. 更新我们找到的最长序列长度
longest_streak = std::max(longest_streak, current_streak);
}
}
return longest_streak;
}
};