哈希集合与哈希表的刷题总结
概念
使用哈希函数将键映射到存储桶
支持快速插入和搜索
1、设置哈希函数:y = x % 5
2、插入:我们通过哈希函数解析键,将它们映射到相应的桶中。
例如,1987 分配给桶 2,而 24 分配给桶 4。
3、搜索:我们通过相同的哈希函数解析键,并仅在特定存储桶中搜索。
如果我们搜索 1987,我们将使用相同的哈希函数将1987 映射到 2。因此我们在桶 2 中搜索,我们在那个桶中成功找到了 1987。
如果我们搜索 23,将映射 23 到 3,并在桶 3 中搜索。我们发现 23 不在桶 3 中,这意味着 23 不在哈希表中。
哈希集合:#include < unordered_set >
#include <iostream>
#include <unordered_set>
using namespace std;
int main(){
unordered_set<int> hashset;
hashset.insert(3);
hashset.insert(3);
hashset.insert(3);
hashset.insert(3);
hashset.insert(2);
hashset.insert(1);
hashset.erase(2);
int isHave = hashset.count(3);
int Nums = hashset.size();
//for(int i=0; i<hashset.size(); i++){
// cout << hashset[i] << '\t';
//}
for(auto it=hashset.begin(); it!=hashset.end(); ++it){
cout << (*it) << '\t';
}
cout << endl;
hashset.clear();
cout << hashset.empty() << endl;
return 0;
}
}
哈希映射:#include < unordered_map >
#include <iostream>
#include <unordered_map>
using namespace std;
int main(){
unordered_map<int, int> hashmap;
hashmap.insert(make_pair(0,-1));
hashmap.insert(make_pair(100,3));
//cout << hashmap[0] << endl;
//cout << hashmap[1] << endl;
//cout << hashmap[2] << endl;
for (auto it = hashmap.begin(); it != hashmap.end(); ++it) {
cout << "(" << it->first << "," << it->second << ") ";
}
cout << endl;
hashmap.erase(0);
hashmap[2] = 109;
cout << hashmap.count(0) << endl;
cout << hashmap.count(1) << endl;
cout << hashmap.count(2) << endl;
cout << hashmap.count(3) << endl;
cout << hashmap.count(4) << endl;
cout << hashmap.count(5) << endl;
cout << hashmap.size() << endl;
for (auto it = hashmap.begin(); it != hashmap.end(); ++it) {
cout << "(" << it->first << "," << it->second << ") ";
}
cout << endl;
cout << hashmap.empty() << endl;
hashmap.clear();
cout << hashmap.empty() << endl;
return 0;
}
哈希集合查重模板
bool findDuplicates(vector<Type>& keys) {
// Replace Type with actual type of your key
unordered_set<Type> hashset;
for (Type key : keys) {
if (hashset.count(key) > 0) {
return true;
}
hashset.insert(key);
}
return false;
}
哈希映射表查重模板
/*
* Template for using hash map to find duplicates.
* Replace ReturnType with the actual type of your return value.
*/
ReturnType aggregateByKey_hashmap(vector<Type>& keys) {
// Replace Type and InfoType with actual type of your key and value
unordered_map<Type, InfoType> hashtable;
for (Type key : keys) {
if (hashmap.count(key) > 0) {
if (hashmap[key] satisfies the requirement) {
return needed_information;
}
}
// Value can be any information you needed (e.g. index)
hashmap[key] = value;
}
return needed_information;
}
哈希集合取交集[349]
给定两个数组,编写一个函数来计算它们的交集。
示例 1:
输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2]
示例 2:
输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出:[9,4]
说明:
输出结果中的每个元素一定是唯一的。
我们可以不考虑输出结果的顺序。
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
if(nums1.empty() || nums2.empty()) return {};
unordered_set<int> h1;
unordered_set<int> h2;
vector<int> h3;
for(int key : nums1){
h1.insert(key);
}
// h2.erase()更优
for(int key : nums2){
h2.insert(key);
}
for(int key : h1){
if(h2.count(key)==1){
h3.push_back(key);
}
}
return h3;
}
};
快乐数[202]
输入:19
输出:true
解释:
1^ 2 + 9^ 2 = 82
8^ 2 + 2^ 2 = 68
6^ 2 + 8^ 2 = 100
1^ 2 + 0^ 2 + 0^ 2 = 1
class Solution {
public:
bool isHappy(int n) {
unordered_set<int> dup;
int m = 0;
dup.insert(n);
while(1){
m += pow(n%10, 2);
// 取十位数的各位权值
if(n/10 != 0){
n = n/10;
}else{
if(m==1) return true;
if(dup.count(m)==1){
return false;
}else{
dup.insert(m);
n = m;
m = 0;
}
}
}
return false;
}
};
两数之和(映射:为记录更多信息)
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
int len = nums.size();
// 分别存取(值,索引)
unordered_map<int , int> table;
for(int i=0; i<len; i++){
// 余项对比
int m = target - nums[i];
if(table.count(m)==1){
return {table[m], i};
}
table.insert(make_pair(nums[i], i));
}
return {};
}
};
四数之和[]
给定四个包含整数的数组列表 A , B , C , D ,计算有多少个元组 (i, j, k, l) ,使得 A[i] + B[j] + C[k] + D[l] = 0。
为了使问题简单化,所有的 A, B, C, D 具有相同的长度 N,且 0 ≤ N ≤ 500 。所有整数的范围在 -228 到 228 - 1 之间,最终结果不会超过 231 - 1 。
输入:
A = [ 1, 2]
B = [-2,-1]
C = [-1, 2]
D = [ 0, 2]
输出:
2
解释:
两个元组如下:
- (0, 0, 0, 1) -> A[0] + B[0] + C[0] + D[1] = 1 + (-2) + (-1) + 2 = 0
- (1, 1, 0, 0) -> A[1] + B[1] + C[0] + D[0] = 2 + (-1) + (-1) + 0 = 0
class Solution {
public:
int fourSumCount(vector<int>& A, vector<int>& B, vector<int>& C, vector<int>& D) {
unordered_map<int, int> abtotal_cnt;
for(int a : A){
for(int b: B){
abtotal_cnt[a+b]++;
}
}
// 原本想再开一个列表记录c+d的情况
//unordered_map<int, int> cdtotal_cnt;
int cnt = 0;
for(int c : C){
for(int d : D){
//cdtotal_cnt[c+d]++;
if(abtotal_cnt.find(0-c-d) != abtotal_cnt.end()){
cnt += abtotal_cnt[0-c-d];
}
}
}
/*int cnt = 0;
for(auto it=abtotal_cnt.begin(); it!=abtotal_cnt.end(); ++it){
for(auto ti=cdtotal_cnt.begin(); ti!=cdtotal_cnt.end(); ++ti){
if(it->first + ti->first==0){
cnt += it->second*ti->second;
}
}
}*/
return cnt;
}
};
同构字符串[205]
输入: s = “foo”, t = “bar”
输出: false
输入: s = “paper”, t = “title”
输出: true
输入: s = “ab”, t = “dd”
输出: false
class Solution {
public:
bool isIsomorphic(string s, string t) {
int len = s.size();
// 双向查找
unordered_map<char, char> table12;
unordered_map<char, char> table21;
for(int i=0; i<len; i++){
if(table12.count(s[i])==1){
if(table12[s[i]] != t[i]){
return false;
}
}
if(table21.count(t[i])==1){
if(table21[t[i]] != s[i]){
return false;
}
}
table12.insert(make_pair(s[i], t[i]));
table21.insert(make_pair(t[i], s[i]));
}
return true;
}
};
两个列表的最小索引总和[599]
输入:
[“KFC”, “Shogun”, “Burger King”, “Tapioca Express”]
[“Burger King”, “Shogun”, “PizzaHub”,“KFC”]
输出: [“Burger King”, “Shogun”]
解释: 他们共同喜爱且具有最小索引和的餐厅是“Shogun”,它有最小的索引和1(0+1)。
提示:
两个列表的长度范围都在 [1, 1000]内。
两个列表中的字符串的长度将在[1,30]的范围内。
下标从0开始,到列表的长度减1。
两个列表都没有重复的元素。
class Solution {
public:
vector<string> findRestaurant(vector<string>& list1, vector<string>& list2) {
unordered_map<string, int> table;
for(int i=0; i<list1.size(); i++){
table.insert(make_pair(list1[i], i));
}
vector<string> list;
int top = list1.size() + list2.size();
for(int i=0; i<list2.size(); i++){
if(table.count(list2[i])==1){
if(table[list2[i]] + i == top){
list.push_back(list2[i]);
}else if(table[list2[i]] + i < top){
list.clear();
list.push_back(list2[i]);
top = table[list2[i]] + i;
}
}
}
return list;
}
};
字符串中的第一个唯一字符
给定一个字符串,找到它的第一个不重复的字符,并返回它的索引。如果不存在,则返回 -1。
s = “leetcode”
返回 0
s = “loveleetcode”
返回 2
class Solution {
public:
int firstUniqChar(string s) {
int len = s.size();
if(!len) return -1;
unordered_map<char, int> table;
for(int i=0; i<len; i++){
if(table.count(s[i])==1){
table[s[i]]++;
}else{
table.insert(make_pair(s[i], 1));
}
}
for(int i=0; i<len; i++){
if(table[s[i]]==1){
return i;
}
}
return -1;
}
};
哈希表取交集[350]
输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2,2]
输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出:[4,9]
说明:
输出结果中每个元素出现的次数,应与元素在两个数组中出现次数的最小值一致。
可以不考虑输出结果的顺序。
class Solution {
public:
vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
int len1 = nums1.size();
int len2 = nums2.size();
if(!len1 || !len2) return {};
unordered_map<int, int> table1;
for(int i=0; i<len1; i++){
if(table1.count(nums1[i])){
table1[nums1[i]]++;
}else{
table1.insert(make_pair(nums1[i], 1));
}
}
vector<int> inter;
for(int i=0; i<len2; i++){
if(table1.count(nums2[i])){
if(table1[nums2[i]]>0){
table1[nums2[i]]--;
inter.push_back(nums2[i]);
}
}
}
return inter;
}
};
设计键
字母异位词分组[49]
给定一个字符串数组,将字母异位词组合在一起。字母异位词指字母相同,但排列不同的字符串。
示例:
输入: [“eat”, “tea”, “tan”, “ate”, “nat”, “bat”]
输出:
[
[“ate”,“eat”,“tea”],
[“nat”,“tan”],
[“bat”]
]
思路:对字符进行排序后作为键使用
如何巧妙地在不明确key的情况下取value
for(auto m : table)
value = m.second;
key = m.first;
class Solution {
public:
vector<vector<string>> groupAnagrams(vector<string>& strs) {
int len = strs.size();
if(!len) return {{}};
vector<vector<string>> res;
unordered_map<string, vector<string>> buff;
// 排序设计key
for(string str: strs){
string t = str;
sort(t.begin(), t.end());
// 根据key存value
buff[t].push_back(str);
}
// 不调用key的情况下取value
for(auto n: buff){
res.push_back(n.second);
}
return res;
}
};
寻找重复的子树
给定一棵二叉树,返回所有重复的子树。对于同一类的重复子树,你只需要返回其中任意一棵的根结点即可。
两棵树重复是指它们具有相同的结构以及相同的结点值。
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
vector<TreeNode*> findDuplicateSubtrees(TreeNode* root) {
vector<TreeNode*> res;
unordered_map<string, int> mp;
dfs(root, res, mp);
return res;
}
string dfs(TreeNode* root, vector<TreeNode*>& res, unordered_map<string, int>& mp){
if(root==0) return "";
//二叉树先序序列化(构成键)
string str = to_string(root->val) + "," + dfs(root->left, res, mp) + "," + dfs(root->right, res, mp);
// 防止同一子树重复压入(并未使用count)
if(mp[str]==1){
res.push_back(root);
}
// 防止同一子树重复压入
mp[str]++;
return str;
}
};
有关设计键的总结
一、当字符串 / 数组中每个元素的顺序不重要时,可以使用排序后的字符串 / 数组作为键。
二、如果只关心每个值的偏移量,通常是第一个值的偏移量,则可以使用偏移量作为键。
三、在树中,有时可能直接使用 TreeNode 作为键。 但在大多数情况下,采用子树的序列化表述可能是一个更好的主意。
四、在矩阵中,使用行索引或列索引作为键。
五、在数独中,将行索引和列索引组合来标识此元素属于哪个块
六、在矩阵中,将值聚合在同一对角线中。

电话号码字母组合
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
输入:“23”
输出:[“ad”, “ae”, “af”, “bd”, “be”, “bf”, “cd”, “ce”, “cf”].
class Solution {
public:
vector<string> letterCombinations(string digits) {
// 哈希表
unordered_map<char, string> table{
{'0', " "}, {'1',"*"}, {'2', "abc"},
{'3',"def"}, {'4',"ghi"}, {'5',"jkl"},
{'6',"mno"}, {'7',"pqrs"},{'8',"tuv"},
{'9',"wxyz"}};
// 结果保存
vector<string> res;
if(digits == "") return res;
// 现阶段输出为"", 从第零项开始
func(res, "", digits, table, 0);
return res;
}
void func(vector<string> &res, string str, string &digits, unordered_map<char, string> &m, int k){
// 如果输出以达到要求位数,压入结果,返回
if(str.size() == digits.size()){
res.push_back(str);
return;
}
// 准备好第k个数字以及其所对应字符
string tmp = m[digits[k]];
// BFS的方式
for(char w : tmp){
str += w;
func(res, str, digits, m, k+1);
// 类似回溯
str.pop_back();
}
return ;
}
};
有效的数独[36]
class Solution {
public:
bool isValidSudoku(vector<vector<char>>& board) {
int row[9][10] = {0};// 哈希表存储每一行的每个数是否出现过,默认初始情况下,每一行每一个数都没有出现过
// 整个board有9行,第二维的维数10是为了让下标有9,和数独中的数字9对应。
int col[9][10] = {0};// 存储每一列的每个数是否出现过,默认初始情况下,每一列的每一个数都没有出现过
int box[9][10] = {0};// 存储每一个box的每个数是否出现过,默认初始情况下,在每个box中,每个数都没有出现过。整个board有9个box。
for(int i=0; i<9; i++){
for(int j = 0; j<9; j++){
// 遍历到第i行第j列的那个数,我们要判断这个数在其所在的行有没有出现过,
// 同时判断这个数在其所在的列有没有出现过
// 同时判断这个数在其所在的box中有没有出现过
if(board[i][j] == '.') continue;
int curNumber = board[i][j]-'0';
if(row[i][curNumber]) return false;
if(col[j][curNumber]) return false;
if(box[j/3 + (i/3)*3][curNumber]) return false;
row[i][curNumber] = 1;// 之前都没出现过,现在出现了,就给它置为1,下次再遇见就能够直接返回false了。
col[j][curNumber] = 1;
box[j/3 + (i/3)*3][curNumber] = 1;
}
}
return true;
}
};
滑动窗+哈希表
给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。
曾经报错项:
‘‘abba’’
‘‘qvqe’’
‘’ ‘’
‘’’’
class Solution {
public:
int lengthOfLongestSubstring(string s) {
int len = s.size();
if(!len) return 0;
unordered_map<char, int> undup;
// 纠正项
int p = 0;
int ptr = 0;
int cnt = 1;
for(int i=0; i<len; i++){
if(undup.count(s[i])){
// 更新滑动窗,防止回指
p = ptr;
// 剔除后方字符(但有时却会回溯到前方)
ptr = undup[s[i]]+1;
// 防止回溯现象发生
if(ptr<p) ptr = p;
}
cnt = max(cnt, i-ptr+1);
undup[s[i]] = i;
}
return cnt;
}
};
哈希表+优先队列
前K个高频元素
给定一个非空的整数数组,返回其中出现频率前 k 高的元素。
输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]
class Solution {
public:
vector<int> topKFrequent(vector<int>& nums, int k) {
// 哈希表统计各元素频次
unordered_map<int, int> table;
for(int i=0; i<nums.size(); i++){
table[nums[i]]++;
}
// 采用优先队列的方法
// 队列元素,队列存储,频数小的排在对头(小顶堆,升序队列)
priority_queue< pair<int,int>, vector< pair<int,int> >, greater< pair<int,int> > > q;
vector<int> result;
// 哈希表遍历
for(auto it : table){
// 队列长度达到k
if(q.size()==k){
// 与队头频次比较,保留大值
//(由于需要排序,将频次放在前面)
if(it.second > q.top().first){
q.pop();
q.push(make_pair(it.second, it.first));
}
}else{
q.push(make_pair(it.second, it.first));
}
}
while(q.size()){
result.push_back(q.top().second);
q.pop();
}
// 保持频次从高到低的顺序
return vector<int>(result.rbegin(), result.rend());
}
};
根据字符出现频率排序
给定一个字符串,请将字符串里的字符按照出现的频率降序排列。
输入: “tree”
输出: “eert”
解释:
'e’出现两次,'r’和’t’都只出现一次。
因此’e’必须出现在’r’和’t’之前。此外,"eetr"也是一个有效的答案。
class Solution {
public:
string frequencySort(string s) {
unordered_map<char, int> table;
for(int i=0; i<s.size(); i++)
table[s[i]]++;
priority_queue<pair<int, char>, vector<pair<int, char>>, less<pair<int, char>>> q;
for(auto i : table){
q.push(make_pair(i.second, i.first));
}
string t = "";
while(q.size()){
for(int i=0; i<q.top().first; i++){
t.push_back(q.top().second);
}
q.pop();
}
return t;
}
};
本文详细介绍了哈希表和哈希集合的概念及其在编程中的应用,包括查重模板、取交集、快乐数计算、两数之和等经典问题的解决方案。并探讨了如何设计键和在不同场景下的哈希策略,如字母异位词分组、滑动窗口和优先队列等。
559

被折叠的 条评论
为什么被折叠?



