当前位置 博文首页 > pushup8的博客:LeetCode分类刷题(五):哈希表(Hash Table)

    pushup8的博客:LeetCode分类刷题(五):哈希表(Hash Table)

    作者:[db:作者] 时间:2021-09-03 18:15

    哈希表就是一种以 键-值(key-indexed) 存储数据的结构,我们只要输入待查找的值即key,即可查找到其对应的值。哈希表简单的理解:在记录的存储位置和它的关键字之间建立一个确定的对应关系f,使每个关键字和结构中一个唯一的存储位置相对应。

    使用哈希查找有两个步骤:

    • 使用哈希函数将被查找的键转换为数组的索引。在理想的情况下,不同的键会被转换为不同的索引值,但是在有些情况下我们需要处理多个键被哈希到同一个索引值的情况。所以哈希查找的第二个步骤就是处理冲突
    • 处理哈希碰撞冲突。有很多处理哈希碰撞冲突的方法,例如,拉链法和线性探测法。

    哈希表是一个在时间和空间上做出权衡的经典例子。如果没有内存限制,那么可以直接将键作为数组的索引。那么所有的查找时间复杂度为O(1);如果没有时间限制,那么我们可以使用无序数组并进行顺序查找,这样只需要很少的内存。哈希表使用了适度的时间和空间来在这两个极端之间找到了平衡。只需要调整哈希函数算法即可在时间和空间上做出取舍。

    C++中unordered_map 与 map 的对比

    • unordered_map和map类似,都是存储的key-value的值,可以通过key快速索引到value;
    • 不同的是unordered_map不会根据key的大小进行排序,存储时是根据key的hash值判断元素是否相同,而map中的元素是按照二叉搜索树存储,进行中序遍历会得到有序遍历,内部元素自动排序
    • map的内部实现是二叉平衡树(红黑树);hash_map内部是一个hash_table
    • 运行效率方面:unordered_map最高,而map效率较低但 提供了稳定效率和有序的序列。
    • 占用内存方面:map内存占用略低,unordered_map内存占用略高,而且是线性成比例的。

    LeetCode中关于哈希表的题目有以下三种类型题:

    (一)哈希表之数组相关题目:

    (二)哈希表之字符串相关题目:

    (三)哈希表之应用实例相关题目:


    (一)哈希表之数组相关题目:

    1.?Two Sum

    • Given an array of integers, return?indices?of the two numbers such that they add up to a specific target. You may assume that each input would have?exactly?one solution, and you may not use the?same?element twice.
    • 题目要求:这道题给了我们一个数组,还有一个目标数target,让我们找到两个数字,使其和为target。
    • 题目分析:为了降低时间的复杂度,需要用空间来换,使用一个HashMap,来建立数字和其坐标位置之间的映射,HashMap是常数级的查找效率,我们在遍历数组的时候,用target减去遍历到的数字,就是另一个需要的数字了,直接在HashMap中查找其是否存在即可。
    • 题目解答:
    class Solution {
    public:
        vector<int> twoSum(vector<int>& nums, int target) {
            vector<int> res(2, -1);
            unordered_map<int, int> hash;
            for(int i = 0; i < nums.size(); i++){
                if(hash.count(target - nums[i])){
                    res[0] = hash[target - nums[i]];
                    res[1] = i;
                    return res;
                }else{
                    hash[nums[i]] = i;
                }
            }
            return res;
        }
    };

    454.?4Sum II

    • Given four lists A, B, C, D of integer values, compute how many tuples?(i, j, k, l)?there are such that?A[i] + B[j] + C[k] + D[l]?is zero.
    • 题目要求:在四个数组中各取一个数字,使其和为0。
    • 题目分析:用了两个哈希表分别记录AB和CD的两两之和出现次数,然后遍历其中一个哈希表,并在另一个哈希表中找和的相反数出现的次数。
    • 题目解答:
    class Solution {
    public:
        int fourSumCount(vector<int>& A, vector<int>& B, vector<int>& C, vector<int>& D) {
            int n = A.size(), res = 0;
            unordered_map<int, int>hash1, hash2;
            for(int i = 0; i < n; i++){
                for(int j = 0; j < n; j++){
                    hash1[A[i] + B[j]]++;
                    hash2[C[i] + D[j]]++;    
                }
            }
            for(auto m : hash1) res += m.second * (hash2[-m.first]);
            return res;
        }
    };

    217.?Contains Duplicate

    • Given an array of integers, find if the array contains any duplicates. Your function should return true if any value appears at least twice in the array, and it should return false if every element is distinct.
    • 题目要求:给定一个整数数组,判断数组中是否包含重复元素。如果数组中任意一个数字出现了至少两次,你的函数应该返回true,如果每一个元素都是唯一的,返回false。
    • 题目分析:使用一个哈希表,遍历整个数组,如果哈希表里存在,返回false,如果不存在,则将其放入哈希表中。
    • 题目解答:
    class Solution {
    public:
        bool containsDuplicate(vector<int>& nums) {
            unordered_map<int, int> hash;
            for(int i = 0; i < nums.size(); i++){
                if(hash.find(nums[i]) != hash.end()) return true;
                else hash[nums[i]] = i;
            }
            return false;
        }
    };

    349.?Intersection of Two Arrays

    • Given two arrays, write a function to compute their intersection.
    • 题目要求:找两个数组交集的部分(不包含重复数字)。
    • 题目分析:用个set把nums1都放进去,然后遍历nums2的元素,如果在set中存在,说明是交集的部分,加入结果的数组中,然后将数组转化set(去除重复的元素),最后再把结果set转为vector的。
    • 题目解答:
    class Solution {
    public:
        vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
            unordered_set<int> set(nums1.begin(), nums1.end());
            vector<int> res;
            for(auto num : nums2){
                if(set.count(num)) res.push_back(num);
            }
            unordered_set<int> res_set(res.begin(), res.end());
            return vector<int>(res_set.begin(), res_set.end());
        }
    };

    350.?Intersection of Two Arrays II

    • Given two arrays, write a function to compute their intersection.
    • 题目要求:找两个数组交集的部分(包含重复数字)。
    • 题目分析:用个set把nums1都放进去,然后遍历nums2的元素,如果在set中存在,说明是交集的部分,加入结果的数组中。
    • 题目解答:
    class Solution {
    public:
        vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
            unordered_map<int, int> hash;
            vector<int> res;
            for(auto num : nums1) hash[num]++;
            for(auto num : nums2){
                if(hash[num]-- >0) res.push_back(num);
            }
            return res;
        }
    };

    (二)哈希表之字符串相关题目:

    290.?Word Pattern

    • Given a?pattern?and a string?str, find if?str?follows the same pattern.Here?follow?means a full match, such that there is a bijection between a letter in?pattern?and a?non-empty?word in?str.
    • 题目要求:这道题给我们一个模式字符串,又给我们一个单词字符串,让我们求单词字符串中单词出现的规律是否符合模式字符串中的规律。
    • 题目分析:用两个哈希表来完成,分别将字符和单词都映射到当前的位置,那么我们在遇到新字符和单词时,首先看两个哈希表是否至少有一个映射存在,如果有一个存在,则比较两个哈希表映射值是否相同,不同则返回false。如果两个表都不存在映射,则同时添加两个映射。
    • 题目解答:
    class Solution {
    public:
        bool wordPattern(string pattern, string str) {
            unordered_map<char, int>hash1;
            unordered_map<string, int>hash2;
            istringstream in(str);
            int i = 0;
            for(string word; in >> word; i++){
                if(hash1.find(pattern[i]) != hash1.end() || hash2.find(word) != hash2.end()){
                    if(hash1[pattern[i]] != hash2[word]) return false;
                }else{
                    hash1[pattern[i]] = i + 1;
                    hash2[word] = i + 1;
                }
            }
            return i == pattern.size();
        }
    };

    205.?Isomorphic Strings

    • Given two strings?s?and?t, determine if they are isomorphic. Two strings are isomorphic if the characters in?s?can be replaced to get?t.
    • 题目要求:求同构字符串,就是说原字符串中的每个字符可由另外一个字符替代,可以被其本身替代,相同的字符一定要被同一个字符替代,且一个字符不能被多个字符替代,即不能出现一对多的映射。
    • 题目分析:用两个哈希表分别来记录原字符串和目标字符串中字符出现情况,由于ASCII码只有256个字符,所以我们可以用一个256大小的数组来代替哈希表,并初始化为0,我们遍历原字符串,分别从源字符串和目标字符串取出一个字符,然后分别在两个哈希表中查找其值,若不相等,则返回false,若相等,将其值更新为i + 1,因为默认的值是0,所以我们更新值为i + 1,这样当 i=0 时,则映射为1,如果不加1的话,那么就无法区分是否更新了。
    • 题目解答:
    class Solution {
    public:
        bool isIsomorphic(string s, string t) {
            int n = s.size(), hash1[256] = {0}, hash2[256] = {0};
            for(int i = 0; i < n; i++){
                if(hash1[s[i]] != hash2[t[i]]) return false;
                hash1[s[i]] = i + 1;
                hash2[t[i]] = i + 1;
            }
            return true;
        }
    };

    383.?Ransom Note

    • Given an arbitrary ransom note string and another string containing letters from all the magazines, write a function that will return true if the ransom note can be constructed from the magazines ; otherwise, it will return false.Each letter in the magazine string can only be used once in your ransom note.
    • 题目要求:这道题是判断给定字符串能否由另一字符串中字符组合生成。
    • 题目分析:通过Hash Table统计magazine字符串中各字符出现的次数,然后遍历ransomNote字符串,每遍历一个字符在Hash Table中将其计数减一,如果计数为负则返回false,如果顺利遍历完整个ransomNote字符串则返回true。由于字符只有26种,因此可以用vector来取代unordered_map实现Hash Table的功能,一定程度上提高执行效率。
    • 题目解答:
    class Solution {
    public:
        bool canConstruct(string ransomNote, string magazine) {
            unordered_map<int, int> hash;
            for(auto ch : magazine) hash[ch]++;
            for(auto ch : ransomNote){
                if(--hash[ch] < 0) return false;
            }
            return true;
        }
    };

    (三)哈希表之应用实例相关题目:

    500.?Keyboard Row

    • Given a List of words, return the words that can be typed using letters of?alphabet?on only one row's of American keyboard like the image below.
    • 题目要求:这道题给了我们一些单词,问哪些单词可以由键盘上的一行中的键符打出来。
    • 题目分析:把键盘的三行字符分别保存到三个set中,然后遍历每个单词中的每个字符,分别看当前字符是否在三个集合中,如果在,对应的标识变量变为1,我们统计三个标识变量之和就知道有几个集合参与其中了。
    • 题目解答:
    class Solution {
    public:
        vector<string> findWords(vector<string>& words) {
            unordered_set<char> set1{'q','w','e','r','t','y','u','i','o','p'};
            unordered_set<char> set2{'a','s','d','f','g','h','j','k','l'};
            unordered_set<char> set3{'z','x','c','v','b','n','m'};
            vector<string> res;
            for(auto word : words){
                int r1 = 0, r2 = 0, r3 = 0;
                for(auto ch : word){
                    if(ch < 'a') ch += 32;
                    if(set1.count(ch)) r1 = 1;
                    if(set2.count(ch)) r2 = 1;
                    if(set3.count(ch)) r3 = 1;
                    if(r1 + r2 + r3 != 1) break;
                }
                if(r1 + r2 + r3 == 1) res.push_back(word);
            }
            return res;
        }
    };

    593.?Valid Square

    • Given the coordinates of four points in 2D space, return whether the four points could construct a square. The coordinate (x,y) of a point is represented by an integer array with two integers.
    • 题目要求:这道题给了我们四个点,让我们验证这四个点是否能组成一个正方形。
    • 题目分析:只需要对四个点,两两之间算距离,然后用个集合set来放距离就行了,如果最后集合中不存在0,且里面只有两个数的时候,说明是正方形。
    • 题目解答:
    class Solution {
    public:
        bool validSquare(vector<int>& p1, vector<int>& p2, vector<int>& p3, vector<int>& p4) {
            unordered_set<int> set{get_distance(p1, p2),get_distance(p1, p3),get_distance(p1, p4),get_distance(p2, p3),get_distance(p2, p4),get_distance(p3, p4)};
            return !set.count(0) && set.size() == 2;
        }
        int get_distance(vector<int>& m, vector<int>& n){
            return (m[0]-n[0])*(m[0]-n[0]) + (m[1]-n[1])*(m[1]-n[1]);
        }
    };

    554.?Brick Wall

    • There is a brick wall in front of you. The wall is rectangular and has several rows of bricks. The bricks have the same height but different width. You want to draw a vertical line from the?top?to the?bottomand cross the?least?bricks.
    • 题目要求:这道题给了我们一个砖头墙壁,上面由不同的长度的砖头组成,让我们选个地方从上往下把墙劈开,使得被劈开的砖头个数最少,前提是不能从墙壁的两边劈,这样没有什么意义。
    • 题目分析:使用一个哈希表来建立每一个断点的长度和其出现频率之间的映射,这样只要我们从断点频率出现最多的地方劈墙,损坏的板砖一定最少。
    • 题目解答:
    class Solution {
    public:
        int leastBricks(vector<vector<int>>& wall) {
            int mx = 0;
            unordered_map<int, int> hash;
            for(auto row : wall){
                int sum = 0;
                for(int i = 0; i < row.size() - 1; i++){
                    sum += row[i];
                    hash[sum]++;
                    mx = max(mx, hash[sum]);
                }
            }
            return wall.size() - mx;
        }
    };

    525.?Contiguous Array

    • Given a binary array, find the maximum length of a contiguous subarray with equal number of 0 and 1.
    • 题目要求:这道题给了我们一个二进制的数组,让我们找邻近的子数组使其0和1的个数相等。
    • 题目分析:用到一个trick,遇到1就加1,遇到0,就减1,这样如果某个子数组和为0,就说明0和1的个数相等。知道了这一点,我们用一个哈希表建立子数组之和跟结尾位置的坐标之间的映射。如果某个子数组之和在哈希表里存在了,说明当前子数组减去哈希表中存的那个子数字,得到的结果是中间一段子数组之和,必然为0,说明0和1的个数相等,我们更新结果res。
    • 题目解答:
    class Solution {
    public:
        int findMaxLength(vector<int>& nums) {
            int res = 0, sum = 0;
            unordered_map<int, int> hash{{0, -1}};
            for(int i = 0; i < nums.size(); i++){
                sum += (nums[i] == 1) ? 1 : -1;
                if(hash.count(sum)) res = max(res, i - hash[sum]);
                else hash[sum] = i;
            }
            return res;
        }
    };

    560.?Subarray Sum Equals K

    • Given an array of integers and an integer?k, you need to find the total number of continuous subarrays whose sum equals to?k.
    • 题目要求:这道题给了我们一个数组,让我们求和为k的连续子数组的个数。
    • 题目分析:用一个哈希表来建立连续子数组之和跟其出现次数之间的映射,初始化要加入{0,1}这对映射,这是为啥呢,因为我们的解题思路是遍历数组中的数字,用sum来记录到当前位置的累加和,我们建立哈希表的目的是为了让我们可以快速的查找sum-k是否存在,即是否有连续子数组的和为sum-k,如果存在的话,那么和为k的子数组一定也存在,这样当sum刚好为k的时候,那么数组从起始到当前位置的这段子数组的和就是k,满足题意,如果哈希表中事先没有m[0]项的话,这个符合题意的结果就无法累加到结果res中,这就是初始化的用途。
    • 题目解答:
    class Solution {
    public:
        int subarraySum(vector<int>& nums, int k) {
            int sum = 0, res = 0;
            unordered_map<int, int> hash{{0, 1}};
            for(int i = 0; i < nums.size(); i++){
                sum += nums[i];
                res += hash[sum - k];
                hash[sum]++;
            }
            return res;
        }
    };

    299.?Bulls and Cows

    • You are playing the following?Bulls and Cows?game with your friend: You write down a number and ask your friend to guess what the number is. Each time your friend makes a guess, you provide a hint that indicates how many digits in said guess match your secret number exactly in both digit and position (called "bulls") and how many digits match the secret number but locate in the wrong position (called "cows"). Your friend will use successive guesses and hints to eventually derive the secret number. Write a function to return a hint according to the secret number and friend's guess, use?A?to indicate the bulls and?B?to indicate the cows.?
    • 题目要求:这道题提出了一个叫公牛母牛的游戏,其实就是之前文曲星上有的猜数字的游戏,有一个四位数字,你猜一个结果,然后根据你猜的结果和真实结果做对比,提示有多少个数字和位置都正确的叫做bulls,还提示有多少数字正确但位置不对的叫做cows,根据这些信息来引导我们继续猜测正确的数字。
    • 题目分析:需要用哈希表,可以用一次循环就搞定的,在处理不是bulls的位置时,我们看如果secret当前位置数字的映射值小于0,则表示其在guess中出现过,cows自增1,然后映射值加1,如果guess当前位置的数字的映射值大于0,则表示其在secret中出现过,cows自增1,然后映射值减1。
    • 题目解答:
    class Solution {
    public:
        string getHint(string secret, string guess) {
            int m[256] = {0}, bulls = 0, cows = 0;
            for(int i = 0; i < secret.size(); i++){
                if(secret[i] == guess[i]) bulls++;
                else{
                    if(m[secret[i]]-- > 0) cows++;
                    if(m[guess[i]]++ < 0) cows++;
                }
            }
            return to_string(bulls) + "A" + to_string(cows) + "B";
        }
    };

    如果各位看官们,大神们发现了任何错误,或是代码无法通过OJ,或是有更好的解法,或是有任何疑问,意见和建议的话,请一定要在帖子下面评论区留言告知博主啊多谢多谢,祝大家刷得愉快,刷得精彩,刷出美好未来~

    cs