哈希集合与哈希表的刷题总结

本文详细介绍了哈希表和哈希集合的概念及其在编程中的应用,包括查重模板、取交集、快乐数计算、两数之和等经典问题的解决方案。并探讨了如何设计键和在不同场景下的哈希策略,如字母异位词分组、滑动窗口和优先队列等。

概念

使用哈希函数映射到存储桶
支持快速插入和搜索
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

解释:
两个元组如下:

  1. (0, 0, 0, 1) -> A[0] + B[0] + C[0] + D[1] = 1 + (-2) + (-1) + 2 = 0
  2. (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;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值