396.小J的字母矩阵问题(哈希)|MarsCode AI刷题
哈希集合
哈希集合(Hash Set),也常被称为散列集合,是一种数据结构,它在很多编程语言中都有对应的实现,比如 C++ 中的 unordered_set、Java 中的 HashSet、Python 中的 set 等。以下是关于哈希集合的详细介绍:
基本概念
- 哈希集合用于存储一组不重复的元素。它基于哈希表(Hash Table)实现,哈希表通过一个哈希函数(Hash Function)将元素映射到一个特定的索引位置,以便快速地进行元素的插入、查找和删除操作。
工作原理
-
哈希函数:
- 哈希函数是哈希集合的核心。它接受一个元素作为输入,并返回一个整数值(通常是一个在特定范围内的索引值)。例如,对于一个存储整数的哈希集合,哈希函数可能会对输入的整数进行某种数学运算(如取模运算等),将其转换为一个适合存储在哈希表中的索引值。
- 理想的哈希函数应该能够将不同的元素均匀地分布到哈希表的各个索引位置,以减少哈希冲突(Hash Collision)的发生。哈希冲突是指不同的元素经过哈希函数计算后得到了相同的索引值。
-
哈希冲突处理:
- 当发生哈希冲突时,哈希集合需要采用一定的方法来处理。常见的处理方法有开放定址法(Open Addressing)和链地址法(Chaining)。
- 在开放定址法中,当一个元素通过哈希函数计算得到的索引位置已经被占用时,会按照一定的规则(如线性探测、二次探测等)去寻找下一个可用的索引位置来存储该元素。
- 在链地址法中,哈希表的每个索引位置实际上是一个链表(或其他数据结构,如向量等)的头部。当发生哈希冲突时,新的元素会被添加到对应索引位置的链表中。这样,同一个索引位置的链表中的元素虽然经过哈希函数计算得到了相同的索引值,但它们通过链表的形式被存储在一起,可以通过遍历链表来查找、插入和删除这些元素。
主要操作及时间复杂度
-
插入操作:
- 将要插入的元素通过哈希函数计算得到对应的索引位置。如果该位置为空,则直接将元素插入;如果发生哈希冲突,则根据所采用的冲突处理方法进行处理(如在链地址法中,将元素添加到对应链表的末尾)。
- 平均情况下,插入操作的时间复杂度通常为 ,这是因为在理想情况下,哈希函数能够将元素均匀分布,使得每次插入操作都能快速找到对应的位置。但在最坏情况下,例如所有元素都哈希到同一个位置(哈希函数设计不合理或数据本身具有特殊性质导致),时间复杂度可能会退化为 ,其中 为集合中元素的数量。
-
查找操作:
- 将待查找的元素通过哈希函数计算得到对应的索引位置,然后在该位置查找元素是否存在。如果采用链地址法,可能需要遍历对应索引位置的链表来确定元素是否存在。
- 平均情况下,查找操作的时间复杂度同样为 ,原因与插入操作类似。最坏情况下,时间复杂度也可能退化为 。
-
删除操作:
- 首先通过哈希函数找到元素所在的索引位置,然后在该位置删除元素。如果采用链地址法,需要在对应索引位置的链表中删除元素,并可能需要进行一些链表的维护操作(如更新表头指针等)。
- 平均情况下,删除操作的时间复杂度为 ,最坏情况下可能退化为 。
问题描述
小R拿到了一个 n 行 m 列的小写字母矩阵,她想知道有多少个子矩阵满足以下条件:每个字母在这个子矩阵中最多出现一次。
测试样例
样例1:
输入:
n = 2 ,m = 3 ,s = ["aad", "abc"]
输出:13
样例2:
输入:
n = 3 ,m = 3 ,s = ["abc", "def", "ghi"]
输出:36
样例3:
输入:
n = 4 ,m = 4 ,s = ["abcd", "efgh", "ijkl", "mnop"]
输出:100
解释
- 首先定义一个变量
count,用于记录满足条件的子矩阵的数量,初始值设为 0。 - 通过四层嵌套循环来遍历所有可能的子矩阵。外层两层循环
for (int i = 0; i < n; ++i)和for (int j = 0; j < m; ++j)用于确定子矩阵的左上角坐标(i, j)。内层两层循环for (int k = i; k < n; ++k)和for (int l = j; l < m; ++l)用于确定子矩阵的右下角坐标(k, l),这样就可以遍历到原矩阵中所有可能的子矩阵。 - 对于每一个确定的子矩阵,使用一个
unordered_set<char>类型的哈希集合unique_chars来检查子矩阵中的每个字母是否最多出现一次。通过两层嵌套循环for (int p = i; p <= k; ++p)和for (int q = j; q <= l; ++q)来遍历子矩阵中的每一个元素s[p][q]。如果发现某个字母已经在集合unique_chars中,说明该字母在这个子矩阵中出现了多次,不满足条件,此时使用goto语句直接跳到next_submatrix标签处,继续检查下一个子矩阵。如果能够完整遍历完子矩阵中的所有元素,说明该子矩阵满足每个字母最多出现一次的条件,将count的值增加 1。 - 最后,返回
count的值,就是满足条件的子矩阵的数量。
#include <iostream>
#include <vector>
#include <string>
#include <unordered_set>
int solution(int n, int m, std::vector<std::string> s) {
int count = 0;
// 遍历所有可能的子矩阵
for (int i = 0; i < n; ++i) {
for (int j = 0; j < m; ++j) {
for (int k = i; k < n; ++k) {
for (int l = j; l < m; ++l) {
// 使用哈希集合来检查子矩阵中每个字母是否最多出现一次
std::unordered_set<char> unique_chars;
for (int p = i; p <= k; ++p) {
for (int q = j; q <= l; ++q) {
if (unique_chars.find(s[p][q])!= unique_chars.end()) {
// 如果字母已经在集合中,说明出现多次,跳出循环
goto next_submatrix;
}
unique_chars.insert(s[p][q]);
}
}
// 如果能完整遍历完子矩阵,说明满足条件,计数加一
count++;
next_submatrix:;
}
}
}
}
return count;
}
int main() {
std::cout << (solution(2, 3, {"aad", "abc"}) == 13) << std::endl;
std::cout << (solution(3, 3, {"abc", "def", "ghi"}) == 36) << std::endl;
std::cout << (solution(4, 4, {"abcd", "efgh", "ijkl", "mnop"}) == 100) << std::endl;
}