[TOC]
本文主要记录哈希表的相关LeetCode题目的实现代码,直接给出本文各个题目的答案,供有需求的读者学习或复制。
一、设计哈希结构的LeetCode题目
705. 设计哈希集合(简单难度)
class MyHashSet {
private:
vector<list<int>> data;// 链表数组
static const int tableSize = 769;//哈希表的容量,设为static是为了直接初始化
static int hashFunction(int key) {
return key % tableSize;
}
public:
MyHashSet(): data(tableSize) {}
void add(int key) {
int index = hashFunction(key);// 获取key对应的索引
// 遍历data[h],查看是否已经存在key
for (auto it = data[index].begin(); it != data[index].end(); it++) {
if ((*it) == key) {
return;
}
}
// 如果该key不存在则加入
data[index].push_back(key);
}
void remove(int key){
int index=hashFunction(key);
// 遍历data[h],查看是否已经存在key
for(auto it = data[index].begin(); it != data[index].end(); it++){
if((*it)==key){
data[index].erase(it);
return;
}
}
}
bool contains(int key) {
int index = hashFunction(key);
// 遍历data[h],查看是否已经存在key
for (auto it = data[index].begin(); it != data[index].end(); it++) {
if ((*it) == key) {
return true;
}
}
return false;
}
};
706. 设计哈希映射(简单难度)
class MyHashMap {
private:
vector<list<pair<int, int>>> data;// 链表数组,每个链表元素是pair对象
static const int tableSize = 769;//哈希表的容量,设为static是为了直接初始化
static int hashFunction(int key) {
return key % tableSize;
}
public:
MyHashMap(): data(tableSize) {}
void put(int key, int value) {
int h = hashFunction(key);// 获取key对应的索引
// 遍历data[h],查看是否已经存在key
for (auto it = data[h].begin(); it != data[h].end(); it++) {
if ((*it).first == key) {
(*it).second = value;
return;
}
}
// 如果该key不存在则加入
data[h].push_back(make_pair(key, value));
}
int get(int key) {
int h = hashFunction(key);
// 遍历data[h],查看是否已经存在key
for (auto it = data[h].begin(); it != data[h].end(); it++) {
if ((*it).first == key) {
return (*it).second;
}
}
return -1;
}
void remove(int key) {
int h = hashFunction(key);
for (auto it = data[h].begin(); it != data[h].end(); it++) {
if ((*it).first == key) {
data[h].erase(it);
return;
}
}
}
};
146. LRU 缓存(中等难度)
剑指 Offer II 031. 最近最少使用缓存(中等难度)
面试题 16.25. LRU 缓存(中等难度)
// 最近最少使用缓存器
class LRUCache {
private:
int cap;// 容量
list<pair<int,int>> l;// 链表(队列),存放元素<key,value>的数据
unordered_map<int, list<pair<int, int>>::iterator> m;//哈希表,用来实现O(1)的查找效率,<key,链表元素指针>
public:
LRUCache(int capacity):cap(capacity) {}
// 通过key获取value
int get(int key) {
auto it = m.find(key);//在缓存数据中查找
// 如果没有找到,返回-1
if (it == m.end()) return -1;
// 如果成功找到,将其升到链表顶部
l.splice(l.begin(), l, it->second);
return it->second->second;
}
void put(int key, int value) {
auto it = m.find(key);//在缓存数据中查找
// 如果成功找到,删除原有数据
if (it != m.end()) l.erase(it->second);
// 在链表顶部插入新元素
l.push_front(make_pair(key, value));
m[key] = l.begin();// 建立哈希映射
// 如果插入元素后数量大于容量
if (m.size() > cap) {
int k = l.rbegin()->first;//找到倒数第一个元素<key,value>的key
l.pop_back();// 链表删除最后一个元素
m.erase(k);// 哈希表删除映射
}
}
};
二、哈希表的简单难度的LeetCode题目
1、找出一个序列中某个特殊的数
(1)找出数组中出现次数超过一半的数字
169. 多数元素(简单难度)
剑指 Offer 39. 数组中出现次数超过一半的数字(简单难度)
class Solution {
public:
int majorityElement(vector<int>& nums) {
// 统计nums中每个数组元素出现的频率
unordered_map<int,int> dict;// nums中每个数,出现频率
for(int num:nums) dict[num]++;
//建立哈希数组找其中出现次数大于n/2的数
int n=nums.size();
for(auto it=dict.begin();it!=dict.end();++it){
if(it->second>n/2)
return it->first;
}
return 0;
}
}
(2)找出数组中只出现一次的数字
136. 只出现一次的数字(简单难度)
137. 只出现一次的数字 II(中等难度)
剑指 Offer II 004. 只出现一次的数字 (中等难度)
剑指 Offer II 070. 排序数组中只出现一次的数字(中等难度)
class Solution {
public:
int singleNumber(vector<int>& nums) {
unordered_map<int,int> map;// 数字和出现次数
// 遍历数组一遍统计频率
for(int num:nums){
map[num]++;
}
// 遍历哈希表
for(auto it=map.begin();it!=map.end();++it){
if(it->second==1)
return it->first;
}
return -1;
}
};
387. 字符串中的第一个唯一字符(简单难度)
class Solution {
public:
int firstUniqChar(string s) {
unordered_map<char,int> map;// 字符和出现次数
// 遍历字符串一遍统计频率
for(char c:s){
map[c]++;
}
// 再次遍历遍历字符串
for(int i=0;i<s.size();++i ){
if(map[s[i]]==1)
return i;
}
return -1;
}
};
剑指 Offer 50. 第一个只出现一次的字符(简单难度)
class Solution {
public:
char firstUniqChar(string s) {
unordered_map<char,int> map;// 字符和出现次数
// 遍历字符串一遍统计频率
for(char c:s){
map[c]++;
}
// 再次遍历遍历字符串
for(char c:s){
if(map[c]==1)
return c;
}
return ' ';
}
};
(3)找出数组中重复的数
217. 存在重复元素(简单难度)
class Solution {
public:
bool containsDuplicate(vector<int>& nums) {
unordered_map<int,int> dict;
// 遍历数组
for(int num:nums){
if(dict.count(num)<=0) dict[num]++;
else return true;
}
return false;
}
};
剑指 Offer 03. 数组中重复的数字(简单难度)
class Solution {
public:
int findRepeatNumber(vector<int>& nums) {
unordered_map<int,int> dict;
for(int num : nums){
// 如果已经存在就返回
if(dict.count(num) > 0) return num;
// 如果不存在就添加
else dict[num]++;
}
return -1;
}
};
219. 存在重复元素 II(简单难度)
class Solution {
public:
bool containsNearbyDuplicate(vector<int>& nums, int k) {
unordered_map<int,int> map;// 数字和所在索引
// 遍历一遍数组元素
for(int i=0;i<nums.size();++i){
// 如果当前元素是重复元素
if(map.count(nums[i])>0){
//如果满足一次直接返回true
if(abs(map[nums[i]]-i)<=k){
return true;
}
//否则更新索引(可能存在2个以上的重复元素)
else{
map[nums[i]]=i;
}
}
map[nums[i]]=i;
}
return false;
}
};
2、找出一个序列中某个特殊的数
(1)给定1个数组,找出其中在[0,n]中消失的数、
448. 找到所有数组中消失的数字(简单难度)
剑指 Offer 53 - II. 0~n-1中缺失的数字(简单难度)
面试题 17.04. 消失的数字(简单难度)
class Solution {
public:
vector<int> findDisappearedNumbers(vector<int>& nums) {
vector<int> res;
// 统计nums中每个数组元素出现的频率
unordered_map<int,int> dict;// nums中每个数,出现频率
for(int num:nums) dict[num]++;
// 遍历[1, n],查看不存在的数
for(int i=1;i<=nums.size();++i){
if(dict.count(i)<=0)
res.push_back(i);
}
return res;
}
};
(2)给定2个字符串,找出不一样的字符
389. 找不同(简单难度)
class Solution {
public:
char findTheDifference(string s, string t) {
unordered_map<char,int> dict;
for(char c:s) dict[c]++;
for(char c:t){
// 如果s中存在
if(dict.count(c)>0){
// 如果其频率为1,删除其key
if(dict[c]==1) dict.erase(c);
// 如果其频率大于1,频率-1
else dict[c]--;
}
// 如果s中不存在,即找到结果
else{
return c;
}
}
return ' ';
}
};
(3)给定2个数组,找出其交集
349. 两个数组的交集(简单难度)
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
vector<int> res;
unordered_map<int,int> dict;
// 将nums1的元素加入哈希表
for(auto & num : nums1){
dict[num]++;
}
// 遍历num2
for(auto & num : nums2){
// 如果哈希集中有该值,加入结果并删除
if(dict.count(num)>0) {
res.push_back(num);
dict.erase(num);
}
}
return res;//剩下的元素就是
}
};
350. 两个数组的交集 II(简单难度)
使用map容器存储每个数的频率,在该容器中查找是否存在另一个数组的元素
vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
vector<int> result_set; // 存放结果
map<int, int> m;
//统计每个数的频率
for (int i : nums1){
m[i]++;
}
//遍历num2中的元素
for (int num : nums2) {
// 如果num频率大于0,说明其在nums1中出现过
if (m[num]>0)
{
result_set.push_back(num);//将该num放入结果中
m[num]--;//频率-1
}
}
return result_set;
}
(4)给定2个字符串,判断a的字符是否都被b包含
383. 赎金信(简单难度)
bool canConstruct(string ransomNote, string magazine) {
unordered_map<char,int> map;
//统计magazine中的每个字母频率
for(int i=0;i<magazine.size();i++){
map[magazine[i]]+=1;
}
//统计ransomNote中的每个字母频率
for(int i=0;i<ransomNote.size();i++){
map[ransomNote[i]]-=1;
}
//遍历哈希表,如果某个字母频率小于0,返回false
for(unordered_map<char,int>::iterator it=map.begin();it!=map.end();it++){
if(it->second<0)
return false;
}
return true;
}
(5)给定2个字符串,判断a和b是否是字母异位词
242. 有效的字母异位词(简单难度)
剑指 Offer II 032. 有效的变位词(简单难度)
bool isAnagram(string s, string t) {
unordered_map<char,int> map;
if(s.size()!=t.size())
return false;
//统计s和t中的每个字母频率
for(int i=0;i<s.size();i++){
map[s[i]]+=1;
map[t[i]]-=1;
}
//遍历映射表
for(unordered_map<char,int>::iterator it=map.begin();it!=map.end();it++){
if(it->second!=0)
return false;
}
return true;
}
三、哈希表的中等难度的LeetCode题目
1、特殊规律
409. 最长回文串(简单难度)
class Solution {
public:
int longestPalindrome(string s) {
unordered_map<char,int> dict;//<字符,频率>
// 统计字符串中每个字母的频率
for(char c:s){
dict[c]++;
}
// 查找频率
int res=0;
int flag = 0;//是否有中心字符,如果存在奇数个相同字符则可以有
for(auto it=dict.begin();it!=dict.end();++it){
// 如果字符频率为偶数个,均可以使用
if(it->second%2==0){
res+=it->second;
}
// 如果字符频率为奇数个,则丢弃1个,其余的均可使用
else{
res+=it->second-1;
flag = 1;//此时,标记回文串可以有中心点
}
}
return res+flag;//最终结果是偶数部分的最大长度+中心字符
}
};
202. 快乐数(简单难度)
class Solution {
public:
// 取数值各个位上的单数之和
int getSum(int n) {
int sum = 0;
while (n) {
sum += (n % 10) * (n % 10);
n /= 10;
}
return sum;
}
bool isHappy(int n) {
unordered_set<int> set;
while(1){
int sum=getSum(n);//求和后的新数
if(sum==1){
return true;
}
//如果这个sum曾经出现过,说明已经陷入了无限循环了,立刻return false
if (set.find(sum) != set.end()) {
return false;
} else {
set.insert(sum);
}
n = sum;
}
}
};
2、双哈希映射
205. 同构字符串(简单难度)
此题是「290. 单词规律」的简化版,需要我们判断 ss 和 tt 每个位置上的字符是否都一一对应,即 ss 的任意一个字符被 tt 中唯一的字符对应,同时 tt 的任意一个字符被 ss 中唯一的字符对应。这也被称为「双射」的关系。因此,我们维护两张哈希表,第一张哈希表 s2t 以 s 中字符为键,映射至 t 的字符为值,第二张哈希表 t2s 以 tt中字符为键,映射至 s的字符为值。从左至右遍历两个字符串的字符,不断更新两张哈希表,如果出现冲突(即当前下标index 对应的字符 s[index] 已经存在映射且不为t[index] 或当前下标 index 对应的字符 t[index] 已经存在映射且不为s[index]时说明两个字符串无法构成同构,返回false。如果遍历结束没有出现冲突,则表明两个字符串是同构的,返回 true 即可。
class Solution {
public:
bool isIsomorphic(string s, string t) {
unordered_map<char, char> smap;//保存s->t的映射
unordered_map<char, char> tmap;//保存t->s的映射
for(int i=0;i<s.size();i++)
{
// 如果两个哈希表的映射都不存在,添加数据
if(smap.count(s[i])<=0&&tmap.count(t[i])<=0)
{
smap[s[i]]=t[i];//s->t
tmap[t[i]]=s[i];//t->s
}
// 如果当前字母已经存在并且当前字母和已有映射不同,则返回false
if(smap.count(s[i])>0&&smap[s[i]]!=t[i]) return false;
if(tmap.count(t[i])>0&&tmap[t[i]]!=s[i]) return false;
}
return true;
}
};
290. 单词规律(简单难度)
class Solution {
public:
// 分割字符串
vector<string> splitWord(string str){
vector<string> words;
string word;
// 遍历整个字符串中的字符
for (char c:str)
// 如果当前字母为空格并且
if (c == ' '&&word.size()>0) {
words.push_back(word);
word = "";
}
else{
word += str[i];
}
// 如果word有字符,加入结果
if (word.size()) words.push_back(word);
return words;
}
bool wordPattern(string pattern, string s) {
vector<string> words=splitWord(s);
if(pattern.size()!=words.size())return false;
// 双哈希法(每个字母和每个单词一一唯一对应)
unordered_map<string,char> str2ch;// 单词到字母的映射
unordered_map<char,string> ch2str;// 字母到单词的映射
// 同时遍历两个序列
for(int i=0;i<pattern.size();++i){
char ch=pattern[i]; // 获取当前字母
string str=words[i];// 获取当前单词
// 如果当前字母已有映射,检查已有映射和当前单词是否一致
if(ch2str.count(ch)>0&&ch2str[ch]!=str){
return false;
}
// 如果当前单词已有映射,检查已有映射和当前字母是否一致
if(str2ch.count(str)>0&&str2ch[str]!=ch){
return false;
}
// 当前字母和当前单词没有映射,建立映射
ch2str[ch]=str;
str2ch[str]=ch;
}
return true;
}
};
3、字符串数组找交集
1002. 查找共用字符(简单难度)
多个字符串中对应的字母频次最小值就是常用字符:
//获取一个字符串的各个字母频次
void getfeq(int * feq,const string &s){
for(int i=0;i<s.size();i++){
feq[s[i]-'a']++;
}
}
vector<string> commonChars(vector<string>& words) {
vector<string> res;
int minfeq[26] = {0};//最小频次
getfeq(minfeq,words[0]);//默认第一个字符串为最小
//循环查找最小频次
for(int i=1;i<words.size();i++){
int feq[26] = {0};
getfeq(feq,words[i]);
for(int j=0;j<26;j++){
minfeq[j] = min(minfeq[j],feq[j]);
}
}
// 将统计得到最小频次,转成字符
for(int i=0;i<26;i++){
if(minfeq[i]!=0){
for(int j = 0; j < minfeq[i]; j++){
string s(1, i + 'a'); // char -> string
res.push_back(s);
}
}
}
return res;
}
599. 两个列表的最小索引总和(简单难度)
class Solution {
public:
// 找两个字符串数组中的交集,并且交集字符串所在的索引和最小
vector<string> findRestaurant(vector<string>& list1, vector<string>& list2) {
unordered_map<string, int> dict;//餐厅名字-索引号
vector<string> res;// 结果数组
int ans = 2000;//索引和的最大值
// 遍历数组1,建立哈希映射
for(int i = 0; i < list1.size(); i++){
dict[list1[i]]=i;
}
// 遍历数组2
for(int i = 0; i < list2.size(); i++){
// 如果哈希表中找到了该字符串,说明是和数组1的交集
auto it =dict.find(list2[i]);
if(it!=dict.end()){
int newAns = it->second+i;//新的索引和=该字符串所对应的数组1的索引+i
// 如果新的索引和更小
if(newAns < ans){
ans = newAns;//记录该索引和
res.clear();// 清空结果字符串
res.push_back(list2[i]);
}
// 如果新的索引和相等
else if(newAns== ans){
res.push_back(list2[i]);
}
}
}
return res;
}
};
49. 字母异位词分组(中等难度)
剑指 Offer II 033. 变位词组(中等难度)
vector<vector<string>> groupAnagrams(vector<string>& strs) {
vector<vector<string>> res;
unordered_map<string,vector<string>> M;//<排序后的字符串,乱序的字符串数组>
// 遍历字符串数组,将每个字符串排序
for(int i=0;i<strs.size();i++){
//先对排序
string key=strs[i];
sort(key.begin(),key.end());
//哈希表添加元素
M[key].push_back(strs[i]);
}
//遍历哈希表,将其value加入结果数组
for(unordered_map<string,vector<string>>::iterator iter=M.begin();iter!=M.end();iter++){
res.push_back(iter->second);
}
return res;
}