力扣Hoot100 第一天 | 哈希3题

一、两数之和

题目链接:1. 两数之和 - 力扣(LeetCode)

题目讲解:代码随想录

代码随想录相关文章已经写过

二、字母异位词分组

题目链接:49. 字母异位词分组 - 力扣(LeetCode)

1. 解题思路

这道题的核心思想是使用哈希表来聚合所有字母异位词

字母异位词有一个关键特征:它们包含的字母及各字母的数量完全相同,只是排列顺序不同。例如,"eat","tea","ate" 都是字母异位词。

我们可以利用这个特征,为每一组字母异位词生成一个唯一的键(key)。一个简单有效的方法是将字符串按照字母顺序排序。排序后,所有的字母异位词都会得到完全相同的字符串。例如,"eat","tea","ate" 排序后都是 "aet"。

具体步骤如下:

  1. 创建一个哈希表 unordered_map<string, vector<string>>,其中:
    • 键(Key):是排序后的字符串,作为唯一的标识符。
    • 值(Value) :是一个字符串向量 vector<string>,用来存储所有具有相同排序后键(即互为字母异位词)的原始字符串。
  2. 遍历输入的字符串数组。
  3. 对于每个字符串,先创建一个副本,然后对副本进行排序。
  4. 用排序后的字符串作为键,在哈希表中查找。
  5. 原始 字符串添加到该键对应的值(vector<string>)中。
  6. 遍历完所有字符串后,哈希表中的所有值(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. 代码逻辑解释

  1. #include 头文件:

    • <vector>: 使用 std::vector 容器。
    • <string>: 使用 std::string 类。
    • <unordered_map>: 使用 std::unordered_map 哈希表。
    • <algorithm>: 使用 std::sort 排序函数。
    • <iostream>: 用于在 main 函数中打印测试结果。
  2. 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特性),用于方便地遍历 mapval直接代表了哈希表中的每个 vector<string>
    • result.push_back(val); : 将每个分组(即 map 中的每个值)添加到结果向量 result 中。
    • return result;: 返回包含所有分组的二维向量。

三、最长连续序列

题目链接:128. 最长连续序列 - 力扣(LeetCode)

1. 解题思路

  1. 空间换时间 : 我们先把所有数字都放进一个哈希集合 unordered_set 中。这样做有两个好处:一是自动去除了重复的数字,二是可以在 O(1) 的平均时间内判断一个数是否存在。
  2. 寻找序列的起点 : 接着,我们遍历数组中的每个数字 num。对于每个 num,我们检查一下 num - 1 是否存在于哈希集合中。
  3. 关键优化 : 如果 num - 1 不存在 ,那么这个 num 就有可能是一个新序列的起点 。这时,我们才开始以它为起点,不断检查 num + 1, num + 2, ... 是否存在,并累加序列的长度。
  4. 避免重复计算 : 如果 num - 1 存在 ,说明 num 只是某个序列的中间部分,而不是起点。那么我们就可以直接跳过它,因为计算它的工作会在我们遍历到这个序列的真正起点时完成。这样就避免了大量的重复计算,保证了整体时间复杂度为 O(n)。
  5. 更新结果: 在每次找到一个序列的末尾时,我们都更新一下已知的最长序列长度。

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;
    }
};
相关推荐
愚润求学6 小时前
【递归、搜索与回溯】FloodFill算法(一)
c++·算法·leetcode
愚润求学9 小时前
【递归、搜索与回溯】FloodFill算法(二)
c++·算法·leetcode
南枝异客9 小时前
四数之和-力扣
java·算法·leetcode
凌肖战10 小时前
C语言中提供的第三方库之哈希表实现
算法·哈希算法
hn小菜鸡13 小时前
LeetCode 2529.正整数和负整数的最大计数
java·算法·leetcode
hn小菜鸡13 小时前
LeetCode 2917.找出数组中的K-or值
数据结构·算法·leetcode
Once_day14 小时前
代码训练LeetCode(34)文本左右对齐
算法·leetcode·c
zhuiQiuMX14 小时前
SQL力扣
数据库·sql·leetcode
好易学·数据结构14 小时前
可视化图解算法51:寻找第K大(数组中的第K个最大的元素)
数据结构·python·算法·leetcode·力扣·牛客网·堆栈