哈希表专题

目录

Leetcode 1.两数之和

Leetcode 454.四数相加 II

Leetcode 49.字母异位词分组 

Leetcode 128. 最长连续序列

======前缀和与哈希表专题======

Leetcode 560. 和为 K 的子数组

======原地哈希专题======

Leetcode 41. 缺失的第一个正数

Leetcode LCR 120. 寻找文件副本

Leetcode 448. 找到所有数组中消失的数字

Leetcode 442. 数组中重复的数据

======字符串哈希======

板子

Acwing 841.字符串哈希(模板题)

Acwing 1460.我在哪?

Acwing 3508.最长公共子串


题解之前:

1.有关unordered_map的count功能:查询key!

contains

Leetcode 1.两数之和

 解题思路:

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        vector<int> res;
        // key:具体的数值(便于count查看是否存在) value:具体的下标索引idx(ans)
        unordered_map<int, int> s;     
        for(int i = 0; i < nums.size(); i ++){
            int left =  target - nums[i];
            if(s.count(left)){   // 已经存在了
                res.push_back(i); res.push_back(s[left]);
                break;
            }
            s[nums[i]] = i;
        }
        return res;
    }
};

Leetcode 1865. 找出和为指定值的下标对

思路:本质是两数之和的变题,需要利用类成员变量的思想,维护两个数组和一个哈希表。这题在add之后,记得还要维护一下哈希表的最新状态。

Code:

class FindSumPairs {
    vector<int> nums1, nums2;
    unordered_map<int, int> cnt;
public:
    FindSumPairs(vector<int>& nums1, vector<int>& nums2) {
        this -> nums1 = nums1;
        this -> nums2 = nums2;
        for (int x: nums2)  cnt[x] ++;
    }
    void add(int index, int val) {
        cnt[nums2[index]] --;
        nums2[index] += val;
        cnt[nums2[index]] ++;
    }
    int count(int tot) {
        int res = 0;
        for (int x: nums1)  res += cnt[tot - x];
        return res;
    }
};

Leetcode 454.四数相加 II

解题思路:

代码:

class Solution {
public:
    int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
        int res = 0;
        unordered_map<int, int> s;
        for(auto a: nums1)
            for(auto b: nums2)
                s[a + b] ++;
        
        for(auto c: nums3)
            for(auto d: nums4)
                res += s[-c - d]; 
        return res;
    }
};

Leetcode 49.字母异位词分组 

算法核心:选择什么作为哈希表的key?

解法一:排序+hash

 

class Solution {
public:
    vector<vector<string>> groupAnagrams(vector<string>& strs) {
        vector<vector<string>> ans;
        unordered_map<string, int> s;
        int idx = -1;
        for(auto v: strs){
            string cp = v;
            sort(v.begin(), v.end());
            if(!s.count(v)){
                vector<string> t = {cp};
                ans.push_back(t);
                s[v] = ++ idx;
            }else{
                ans[s[v]].push_back(cp);
            }
        }
        return ans;
    }
};

解法二:计数+hash

 

class Solution {
public:
    vector<vector<string>> groupAnagrams(vector<string>& strs) {
        int idx = -1;
        vector<vector<string>> ans;
        unordered_map<string, int> map;
        for(auto v: strs){
            vector<int> cnt(26, 0);  // 计数数组
            // 初始化计数数组
            for(char c: v)
                cnt[c - 'a'] ++;
            // 生成字符串key
            string key = "";
            for(int i = 0; i < 26; i ++){
                key += 'a' + i;
                key += cnt[i];
            }
            // 看看是否出现过
            if(!map.count(key)){    // 没有出现过
                vector<string> t = {v};
                ans.push_back(t);
                map[key] = ++ idx;
            }else                   // 出现过
                ans[map[key]].push_back(v);
        }
        return ans;
    }
};

Leetcode 128. 最长连续序列

解题思路:

代码:

class Solution {
public:
    int longestConsecutive(vector<int>& nums) {
        unordered_set<int> S;
        for(auto& v: nums)  S.insert(v);    // 去重
        int res = 0;                        // 初始化最长连续序列长度为0
        for(auto& v: S){
            if(!S.count(v - 1)){            // 不是序列首部数据就直接跳过
                // 进入本代码块说明是本连续序列的首部
                int curnum = v;
                int len = 1;                // 当前长度
                while(S.count(curnum + 1)){ // 存在下一个连续数的时候
                    curnum ++, len ++;
                }
                res = max(res, len);
            }   
        }
        return res;
    }
};

Leetcode 594. 最长和谐子序列

错因:写的时候当成连续的子数组了->用的二分答案。

分析由于max-min=1,相当于数组只能有x和x+1两个数,那么这个数组的长度就是x的个数+(x+1)的个数。又因为是子序列,可以隔着选出所有的x和x+1。那么问题转化为统计数组中每个数字的个数即可。

Code:O(N)

class Solution {
public:
    int findLHS(vector<int>& nums) {
        unordered_map<int, int> cnt;
        for (int x: nums)   cnt[x] ++;
        int res = 0;
        for (auto& [x, c]: cnt)
            if (cnt.contains(x + 1))
                res = max(res, c + cnt[x + 1]);
        return res;  
    }
};

======前缀和与哈希表专题======

核心技巧:“枚举右,维护左”

相关题单:(1.2节部分)

分享丨【题单】常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树) - 力扣(LeetCode)https://leetcode.cn/circle/discuss/mOr1u6/

Leetcode 560. 和为 K 的子数组

class Solution {
public:
    int subarraySum(vector<int>& nums, int k) {
        int presum = 0, n = nums.size(), res = 0;
        unordered_map<int, int> hash;
        hash[0] ++; // hash的含义:左边前缀和大小的取值个数的映射
        // 那么, 左边取值和的大小0的值,至少本身就算1个(左边都不要,只要自身一个即可)
        for(int i = 0; i < n; i ++){
            presum += nums[i];
            res += hash[presum - k];    // 不存在的键自动取值为0
            hash[presum] ++;
        }
        return res;
    }
};

Leetcode 2588. 统计美丽子数组数目

Case分析:

class Solution {
public:
    long long beautifulSubarrays(vector<int>& nums) {
        long long res = 0;
        int s = 0;
        unordered_map<int, int> cnt{{0, 1}};
        for (int x : nums){
            s ^= x;
            res += cnt[s ^ 0];      // 异或运算中,只有1能改变答案,所以此处等价于cnt[s]
            cnt[s] ++;
        }
        return res;
    }
};

Leetcode 437. 路径总和 III

思路同560.前缀和+哈希表

class Solution {
public:
    int pathSum(TreeNode* root, int targetSum) {
        int ans = 0;
        unordered_map<long long, int> cnt;
        cnt[0] ++;  // 理由同560,前面都不要
        function<void(TreeNode*, long long)> dfs =[&](TreeNode* root, long long presum)->void{
            if(root == nullptr) return;
            presum += root -> val;
            ans += (cnt.count(presum - targetSum)? cnt[presum-targetSum]: 0);
            cnt[presum] ++;
            dfs(root -> left, presum);
            dfs(root -> right, presum);
            cnt[presum] --;     // 恢复现场,回溯
        };
        dfs(root, 0);
        return ans;
    }
};//时间复杂度:O(n),其中 n 是二叉树的节点个数。空间复杂度:O(n)。

 

======原地哈希专题======

原地哈希就相当于,让每个数字n都回到下标为n-1的家里。

而那些没有回到家里的就成了孤魂野鬼流浪在外,他们要么是根本就没有自己的家(数字小于等于0或者大于nums.size()),要么是自己的家被别人占领了(出现了重复)。

这些流浪汉被临时安置在下标为i的空房子里,之所以有空房子是因为房子i的主人i+1失踪了(数字i+1缺失)。

因此通过原地构建哈希让各个数字回家,我们就可以找到原始数组中重复的数字还有消失的数字。

Leetcode 41. 缺失的第一个正数

        原地哈希的while注意,会帮每一个这个位置上数字进行归位,一旦归位后的数字再在for中遍历到,那么就不会再执行了,所以时间复杂度是O(n)的。

class Solution {
public:
    int firstMissingPositive(vector<int>& nums) {
        int n = nums.size();    
        for(int i = 0; i < n; i ++)     // while进行持续归位
            while (nums[i] > 0 && nums[i] <= n && nums[nums[i] - 1] != nums[i])
                swap(nums[i], nums[nums[i] - 1]);
        for(int i = 0; i < n; i ++){
            if(i + 1 != nums[i])
                return i + 1;
        }
        return n + 1;   // 执行到这一步说明前面都占满了, [1, n],那么没出现过的必然是n + 1
    }
};

Leetcode LCR 120. 寻找文件副本

class Solution {
public:
    // 抓住题目的条件: 0<=id<=n-1可以使用原地哈希
    int findRepeatDocument(vector<int>& documents) {
        int n = documents.size();
        int i = 0;
        while (i < n){
            if(i == documents[i])   {
                i ++;       // 只有完成当前坑位的任务,才会i++
                continue;   // 已经对应上坑位了,跳过
            }
            if(documents[i] == documents[documents[i]]) //  找到一组重复值
                return documents[i];
            swap(documents[i], documents[documents[i]]);// 说明不相等,swap
        }
        return -1;
    }
};

Leetcode 448. 找到所有数组中消失的数字

Leetcode 442. 数组中重复的数据

======字符串哈希======

板子

核心思想:将字符串看成P进制数,P的经验值是 131 或 13331,取这两个值的冲突概率低
小技巧:取模的数用2 ^ 64,这样直接用unsigned long long存储,溢出的结果就是取模的结果
 
typedef unsigned long long ULL;
ULL h[N], p[N]; // h[k]存储字符串前k个字母的哈希值, p[k]存储 P^k mod 2^64
 
// 初始化
p[0] = 1;
for (int i = 1; i <= n; i ++ )
{
    h[i] = h[i - 1] * P + str[i]; //str[]也是从第一个为有效位数
    p[i] = p[i - 1] * P;
}
 
// 计算子串 str[l ~ r] 的哈希值
ULL get(int l, int r)
{
    return h[r] - h[l - 1] * p[r - l + 1];
}

Acwing 841.字符串哈希(模板题)

Acwing 1460.我在哪?

Acwing 3508.最长公共子串

NC277160 不是烤串故事(牛客周赛Round 56 F题)

牛客周赛Round 56补题-优快云博客https://blog.youkuaiyun.com/zjjaibc/article/details/141310348?csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22141310348%22%2C%22source%22%3A%22zjjaibc%22%7D

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_Ocean__

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值