[TOC]

​ 本文主要记录哈希表的相关LeetCode题目的实现代码,直接给出本文各个题目的答案,供有需求的读者学习或复制。

一、子串/子序列/子数组的LeetCode题目

1、前缀和+哈希表

560. 和为 K 的子数组(中等难度)

剑指 Offer II 010. 和为 k 的子数组(中等难度)

class Solution {
public:
    int subarraySum(vector<int>& nums, int k) {
        int res = 0;// 结果,连续子数组的个数
        // 初始化哈希表
        unordered_map<int, int> dict; // <前缀和,次数>
        dict[0] = 1;//
        // 遍历计算前缀和
        int preSum = 0;// 前缀和
        for (int num:nums) {
            preSum += num;
            // 哈希表中存在
            if (dict.count(preSum - k) >0) {
                res += dict[preSum - k];
            }
            // 哈希表中不存在,则新建
            ++dict[preSum];
        }
        return res;
    }
};

2、滑动窗口+哈希表

3. 无重复字符的最长子串(中等难度)

剑指 Offer 48. 最长不含重复字符的子字符串

剑指 Offer II 016. 不含重复字符的最长子字符串

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        unordered_map<char,int> dict;// <字符,频率>
        // 右指针,初始值为 -1,相当于我们在字符串的左边界的左侧,还没有开始移动
        int right=-1;
        int res=0;
        // 枚举左指针的位置,初始值隐性地表示为 -1
        for(int left=0;left<s.length();++left){
            if(left!=0) dict.erase(s[left-1]);
            // 当右指针下一步没有到达数组右边界且右指针下一步没有重复,不断地移动右指针
            while(right+1<s.length()&&!dict.count(s[right+1])){
                dict[s[right+1]]++;
                right++;
            }
            // 第 left 到 right 个字符是一个最长的无重复字符子串
            res = max(res, right - left + 1);
        }
        return res;
    }
};

438. 找到字符串中所有字母异位词(中等难度)

剑指 Offer II 015. 字符串中的所有变位词(中等难度)

vector<int> findAnagrams(string s, string p) {
        vector<int> res;
        //判断条件
        if(s.size()<p.size())
            return res;
        int l = 0;//负责记录每次字母异位词的起始位置
        int r = 0;//不断循环
        //记录频率的哈希表
        vector<int> freq_s(26, 0), freq_p(26, 0);
        // 初始化代码值
        for( int i = 0 ; i < p.size() ; i++ ){
            freq_p[p[i] - 'a' ]++;//统计p中每个字母出现的频率
            freq_s[s[r++] - 'a' ]++;//统计s中前段字母出现的频率
        }
        //如果字母频率相等
        if ( freq_s == freq_p )
            res.push_back( l );
        // 固定长度的滑动窗口
        while(r < s.size()){
            freq_s[s[r++] - 'a' ]++;//新一段字母的频率
            freq_s[s[l++] - 'a' ]--;//旧窗口字母的频率
            if ( freq_s == freq_p )
                res.push_back( l );
        }
        return res;
    }

##

3、子序列

128. 最长连续序列(中等难度)

剑指 Offer II 119. 最长连续序列(中等难度)

[最长连续序列 哈希 代码简洁易懂 【c++/java版】 - 最长连续序列 - 力扣(LeetCode) (leetcode-cn.com)](https://leetcode-cn.com/problems/longest-consecutive-sequence/solution/ha-xi-zui-qing-xi-yi-dong-de-jiang-jie-c-xpnr/)
class Solution {
public:
    // 注意子序列,最长连续子序列不包含重复元素
    int longestConsecutive(vector<int>& nums) {
        int res=0;
        // 统计每个数字频率,达到去重功能
        unordered_map<int,int> dict;
        for(int num:nums) dict[num]++;
        // 遍历所有数字,已经经过去重
        for(int num:nums){
            int cur=num;//以x为起点
            // 避免重复枚举一段x+1为起点的序列
            if(!dict.count(cur-1)){
                // 查询x+1,x+2,...,x+y是否存在
                while(dict.count(cur+1))
                    cur++;
            }
            // 本次查询的[x,y]的长度为y-x+1
            res=max(res,cur-num+1);
        }
        return res;
    }
};

674. 最长连续递增序列(简单难度)

class Solution {
public:
    int findLengthOfLCIS(vector<int>& nums) {
        int res=1;//只有1个数的情况
        int count = 1;
        // 遍历所有数字(大于1个数的情况)
        for(int i=1;i<nums.size();++i){
            // 避免重复枚举一段x+1为起点的序列
            if(nums[i-1]<nums[i]){
                count++;
            }
            else {
                count=1;
            }
            // 本次查询的[x,y]的长度为y-x+1
            res=max(count,res);
        }
        return res;
    }
};

二、哈希表处理数字之和的LeetCode题目

1. 两数之和(简单难度)

创建一个哈希表,对于每一个 x,我们首先查询哈希表中是否存在 target - x,然后将 x 插入到哈希表中,即可保证不会让 x 和自己匹配。

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        unordered_map<int, int> hashmap;//哈希映射:nums[i]->i
        // 遍历整个数组
        for (int i = 0; i < nums.size(); ++i) {
            // 如果哈希表中存在该映射,直接返回结果
            if (hashmap.count(target - nums[i]) >0) {
                return {i,hashmap[target - nums[i]]};
            }
            // 如果哈希表中不存在该映射,加入该映射
            hashmap[nums[i]] = i;
        }
        return {};
    }
};

15、三数之和(中等难度)

1.将数组排序 2.定义三个指针,i,j,k。遍历i,那么这个问题就可以转化为在i之后的数组中寻找nums[j]+nums[k]=-nums[i]这个问题,也就将三数之和问题转变为二数之和—(可以使用双指针)

vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>> result;//结果
        std::sort(nums.begin(),nums.end());//排序数组
        // 循环遍历
        for (int i = nums.size()-1; i >= 2; ){
            // 循环遍历left和right
            for (int left = 0, right = i-1; left < right; ){
                int tmp_sum = nums[left]+nums[right];
                // 如果两数之和小则移动左边界
                if (tmp_sum < -nums[i]){
                    left++;
                } 
                // 如果两数之和大则移动右边界
                else if (tmp_sum > -nums[i]){
                    right--;
                }
                // 如果两数之和符合则记录该答案 
                else {
                    vector<int> v = {nums[left], nums[right], nums[i]};
                    result.push_back(v);
                    //去重复 left
                    do{
                        left++;
                    } while (left < right && nums[left-1] == nums[left]);
                    //去重复 right
                    do{
                        right--;
                    } while (left < right && nums[right+1] == nums[right]);
                }
            }
            //去重复 c
            do{
                i--;
            } while (i >= 2 && nums[i+1] == nums[i]);
        }
        return result;
    }

18、四数之和(中等难度)

vector<vector<int>> fourSum(vector<int>& nums, int target) {
        vector<vector<int>> result;// 结果数组
        sort(nums.begin(), nums.end());
        // 遍历数组
        for (int k = 0; k < nums.size(); k++) {
            // 这种剪枝是错误的,这道题目target 是任意值
            // if (nums[k] > target) {
            //     return result;
            // }
            // 去重
            if (k > 0 && nums[k] == nums[k - 1]) {
                continue;
            }
            for (int i = k + 1; i < nums.size(); i++) {
                // 正确去重方法
                if (i > k + 1 && nums[i] == nums[i - 1]) {
                    continue;
                }
                int left = i + 1;
                int right = nums.size() - 1;
                while (right > left) {
                    if (nums[k] + nums[i] + nums[left] + nums[right] > target) {
                        right--;
                    } else if (nums[k] + nums[i] + nums[left] + nums[right] < target) {
                        left++;
                    } else {
                        result.push_back(vector<int>{nums[k], nums[i], nums[left], nums[right]});
                        // 去重逻辑应该放在找到一个四元组之后
                        while (right > left && nums[right] == nums[right - 1]) right--;
                        while (right > left && nums[left] == nums[left + 1]) left++;

                        // 找到答案时,双指针同时收缩
                        right--;
                        left++;
                    }
                }

            }
        }
        return result;
    }

454、四数之和II(中等难度)

这道题的数据量不大,0 ≤ N ≤ 500,但是如果使用暴力解法,四层循环,会超时。这道题的思路和第 1 题思路也类似,先可以将 2 个数组中的组合都存入 map 中。之后将剩下的 2 个数组进行 for 循环,找出和为 0 的组合。这样时间复杂度是 O(n^2)。当然也可以讲剩下的 2 个数组的组合也存入 map 中,不过最后在 2 个 map 中查找结果也是 O(n^2) 的时间复杂度。

int fourSumCount(vector<int>& A, vector<int>& B, vector<int>& C, vector<int>& D) {
        unordered_map<int, int> umap; //key:a+b的数值,value:a+b数值出现的次数
        // 遍历大A和大B数组,统计两个数组元素之和,和出现的次数,放到map中
        for (int a : A) {
            for (int b : B) {
                umap[a + b]++;
            }
        }
        int count = 0; // 统计a+b+c+d = 0 出现的次数
        // 在遍历大C和大D数组,找到如果 0-(c+d) 在map中出现过的话,就把map中key对应的value也就是出现次数统计出来。
        for (int c : C) {
            for (int d : D) {
                if (umap.find(0 - (c + d)) != umap.end()) {
                    count += umap[0 - (c + d)];
                }
            }
        }
        return count;
    }