当前位置: 首页 > backend >正文

字母异位词分组,leetCode热题100,C++实现

题目来源:leetCode

49. 字母异位词分组 - 力扣(LeetCode)

题目描述:给你一个字符串数组,请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。

解法1:

class Solution {
public:vector<vector<string>> groupAnagrams(vector<string>& strs) {unordered_map<string, vector<string>> mp;for (string& str: strs) {string key = str;sort(key.begin(), key.end());mp[key].emplace_back(str);}vector<vector<string>> ans;for (auto it = mp.begin(); it != mp.end(); ++it) {ans.emplace_back(it->second);}return ans;}
};

排序,意思是将每一个单词都进行排序,比如eat排序后变成aet,tea排序后变成aet,然后把原来的单词放到aet这个key的value里

然后放入容器里返回即可

解法2

class Solution {
public:vector<vector<string>> groupAnagrams(vector<string>& strs) {// 自定义对 array<int, 26> 类型的哈希函数auto arrayHash = [fn = hash<int>{}] (const array<int, 26>& arr) -> size_t {return accumulate(arr.begin(), arr.end(), 0u, [&](size_t acc, int num) {return (acc << 1) ^ fn(num);});};unordered_map<array<int, 26>, vector<string>, decltype(arrayHash)> mp(0, arrayHash);for (string& str: strs) {array<int, 26> counts{};int length = str.length();for (int i = 0; i < length; ++i) {counts[str[i] - 'a'] ++;}mp[counts].emplace_back(str);}vector<vector<string>> ans;for (auto it = mp.begin(); it != mp.end(); ++it) {ans.emplace_back(it->second);}return ans;}
};

该方法使用的是计数法,比如eat里包含1个a,一个e和一个t,tea和ate也是一样的,然后我们把每一个拥有相同元素的单词放到一块

知道了思想后我们来解释一下代码

// 自定义对 array<int, 26> 类型的哈希函数auto arrayHash = [fn = hash<int>{}] (const array<int, 26>& arr) -> size_t {return accumulate(arr.begin(), arr.end(), 0u, [&](size_t acc, int num) {return (acc << 1) ^ fn(num);});};

首先是自定义的hash函数,用于计算hash值,这是一个lambda表达式,首先是捕获列表[fn = hash<int>{}],其中hash<int>是C++标准库中定义的函数对象类,专门用于计算整数的哈希值,在 <functional> 头文件中定义,然后是参数列表(const array<int, 26>& arr),是一个一维数组,可以存放26个元素(26个字母),然后是返回类型-> size_t

lambda和unordered_map都是C++11里新增的,以及这里的array也是,不清楚的可以看看我之前的博客

C++11-CSDN博客

C++-哈希Hash_c++ hash<int>-CSDN博客

{return accumulate(arr.begin(), arr.end(), 0u, [&](size_t acc, int num) {return (acc << 1) ^ fn(num);});
}

然后是上面的函数体,使用 accumulate 算法遍历数组并计算组合哈希值,std::accumulate 是标准库算法,用于计算范围内元素的累积值。

accumulate(开始迭代器, 结束迭代器, 初始值, 累积函数)

里面的0u是初始值(无符号整数0),然后里面嵌套了一个lambda表达式,以引用的方式捕获外部变量(包括fn),使lambda内部可以使用外部的hash<int>对象,acc是积累值(上一次的计算结果),num是当前数组元素值(字母计数),接下来看函数体,acc<<1是左移1位,相当于乘以2,比如5<<1就是10,fn(num)是计算当前数字的哈希值,接着对他两进行异或混合

假设数组是arr=[1,2,3],那么第一次迭代结果如下

第二次迭代结果如下

第三次迭代结果如下

为什么这么复杂?

因为我们需要位整个数组生成一个唯一的哈希值

为什么这里需要自定义哈希函数?

因为我们要使用array<int,26>作为哈希表的key,但是C++标准库里没有为这种自定义类型提供默认的哈希函数

// 这样写会编译错误!
unordered_map<array<int, 26>, vector<string>> mp; // ❌ 错误

自定义哈希函数的作用是告诉编译器如何计算哈希值,确保相同的计数产生相同的哈希值

array<int, 26> arr1 = {1,0,0,0,1,0,...,1,...}; // eat
array<int, 26> arr2 = {1,0,0,0,1,0,...,1,...}; // teaarrayHash(arr1) == arrayHash(arr2);  // true,相同的哈希值
array<int, 26> arr1 = {1,0,0,0,1,0,...,1,...}; // eat
array<int, 26> arr2 = {1,0,1,0,0,0,...,0,...}; // tanarrayHash(arr1) != arrayHash(arr2);  // true,不同的哈希值

以及不同的计数产生不同的哈希值

这里可以把哈希表想象成一个有很多抽屉的柜子,每个key都要被分配到一个特定的抽屉

哈希表柜子:
[抽屉0] → 存放key哈希值 % N = 0的元素
[抽屉1] → 存放key哈希值 % N = 1的元素  
...
[抽屉N-1] → 存放key哈希值 % N = N-1的元素

为什么需要哈希值?

用于快速定位key应该放到哪个抽屉里

比如我们用eat这个单词举例

字符串 "eat" 
    → 统计计数: array<int, 26> = {a:1, e:1, t:1, ...}
    → 计算哈希: arrayHash({1,0,0,0,1,0,...,1,...}) = 12345
    → 定位抽屉: 12345 % N = 抽屉5
    → 存储在抽屉5中

查找也是同理,这里是取模运算,N是哈希表中桶的数量,也就是抽屉总数

为什么需要取模?因为哈希值可能非常大,但是我们的抽屉数量是有限的,比如上面的哈希值为12345,非常大

然后我们看后续代码

unordered_map<array<int, 26>, vector<string>, decltype(arrayHash)> mp(0, arrayHash);

我们创建一个map,key是array,26个整数的一维数组,存储字母频率,value是vector<string>,用于存储所有产生相同字母频率的原始字符串,decltype是哈希函数类型

array<int, 26> counts{};  // 所有26个元素初始化为0
for (int i = 0; i < length; ++i) {counts[str[i] - 'a']++;  // 统计每个字母出现次数
}

这段代码用于统计每个单词的字母频率,然后通过emplace_back将原本单词放入到key为counts的位置,我们用eat举例

counts = [a:1, e:1, t:1, 其他:0]  // 字母频率数组
mp[counts] // 查找或创建对应这个频率的分组
→ 将 "eat" 加入分组 ["eat"]

我们可以发现有相同字母频率的单词会被放到一个key对应的value里,而value是一个vector

最后我们再通过和解法1一样的办法,把单词放入容器里返回即可

http://www.xdnf.cn/news/18848.html

相关文章:

  • 嵌入式学习day38
  • 搭建域服务器
  • spring-ai-alibaba使用
  • 第18章|变量:把数据装进“盒子”的正确方式
  • 机器学习 TF-IDF方法
  • 【docker apoc 部署的neo4j安装apoc插件】
  • MySQL 面试题系列(五)
  • 【Kafka】重点概念和架构总结
  • Python 入门操作指南
  • 如何在 Docker 和AKS上使用 IIS
  • iOS技术之通过Charles抓包http、https数据
  • 【Linux】基本指令学习3
  • opencv+yolov8n图像模型训练和推断完整代码
  • Clerk 用户认证系统集成文档
  • ollama离线部署+大语言模型
  • AI-调查研究-62-机器人 机械臂五大应用场景详解:从焊接到手术,从农田到太空
  • 4步用代码拆解数学建模中的TOPSIS评价决策! ! !
  • Apache Commons Lang 3
  • 野火STM32Modbus主机读取寄存器/线圈失败(二)-解决CRC校验错误
  • uC/OS-III 队列相关接口
  • 数据分析与数据挖掘
  • 企业如何构建全面的高防IP防护体系?
  • Teams Workflows 业务流程搭建与Linux自动化运维拓展应用全解析
  • 状态设计模式
  • 构建面向人工智能决策的世界模型引擎所需的基本知识体系
  • 如何在GitHub找到10k+个stars的仓库
  • podman启动mongdb的container因为权限问题导致changing ownership和读取storage.bson失败的解决方法
  • CMake构建学习笔记20-iconv库的构建
  • 算法概述篇
  • 游戏空间划分技术