本文主要记录哈希表的相关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);// 哈希表删除映射
        }
    }
};

380. O(1) 时间插入、删除和获取随机元素(中等难度)

剑指 Offer II 030. 插入、删除和随机访问都是 O(1) 的容器

/*该题要求实现一个随机集合,其插入、删除和查询的效率都是O(1)
必定需要借助哈希表
*/
class RandomizedSet {
private:
    // 可变数组,可在O(1)内完成获取随机元素操作
    vector<int> nums;
    //哈希表<val,索引>,可在O(1)内完成插入和删除
    unordered_map<int, int> dict;
public:
    RandomizedSet() {
        srand((unsigned)time(NULL));// 设置随机种子,不设置也可以成功
    }
    bool insert(int val) {
        if (dict.count(val)) return false;
        int index = nums.size();
        nums.emplace_back(val);// 向数组中添加
        dict[val] = index;// 向字典中添加
        return true;
    }
    bool remove(int val) {
        if (!dict.count(val)) return false;//val不存在
        int index = dict[val];// 获取val的索引
        // 将数组最后一个元素拷贝到val的位置
        int last = nums.back();// 获取last的值
        nums[index] = last;//将val的位置覆盖为last
        dict[last] = index;//更新last的索引
        // 删除数组最后一个元素
        nums.pop_back();// 移除最后一个元素
        dict.erase(val);// 移除val的映射
        return true;
    }
    int getRandom() {
        int randomIndex = rand()%nums.size();// 取[0,n-1]的随机数
        return nums[randomIndex];
    }
};

二、哈希表的简单难度

1、找出一个序列中某个特殊的数

(1)找出数组中出现次数超过一半的数字

169. 多数元素(简单难度)

剑指 Offer 39. 数组中出现次数超过一半的数字(简单难度)

给定一个大小为 n 的数组 nums ,返回其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。

你可以假设数组是非空的,并且给定的数组总是存在多数元素。

进阶:尝试设计时间复杂度为 O(n)、空间复杂度为 O(1) 的算法解决此问题。

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. 只出现一次的数字(简单难度)

给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。

进阶:你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?

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;
    }
};

137. 只出现一次的数字 II(中等难度)

剑指 Offer II 004. 只出现一次的数字 (中等难度)

给你一个整数数组 nums ,除某个元素仅出现 一次 外,其余每个元素都恰出现 三次 。请你找出并返回那个只出现了一次的元素。

进阶:你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?

剑指 Offer II 070. 排序数组中只出现一次的数字(中等难度)

540. 有序数组中的单一元素(中等难度)

给定一个只包含整数的有序数组 nums ,每个元素都会出现两次,唯有一个数只会出现一次,请找出这个唯一的数字。

进阶: 采用的方案可以在 O(log n) 时间复杂度和 O(1) 空间复杂度中运行吗?

387. 字符串中的第一个唯一字符(简单难度)

给定一个字符串 s ,找到 它的第一个不重复的字符,并返回它的索引 。如果不存在,则返回 -1

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. 第一个只出现一次的字符(简单难度)

在字符串 s 中找出第一个只出现一次的字符。如果没有,返回一个单空格。 s 只包含小写字母。

点评

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. 存在重复元素(简单难度)

给你一个整数数组 nums 。如果任一值在数组中出现 至少两次 ,返回 true ;如果数组中每个元素互不相同,返回 false

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. 数组中重复的数字(简单难度)

找出数组中重复的数字。

在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。

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(简单难度)

给你一个整数数组 nums 和一个整数 k ,判断数组中是否存在两个 不同的索引 i 和 j ,满足 nums[i] == nums[j] 且 abs(i - j) <= k 。如果存在,返回 true ;否则,返回 false 。

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. 消失的数字(简单难度)

给你一个含 n 个整数的数组 nums ,其中 nums[i] 在区间 [1, n] 内。请你找出所有在 [1, n] 范围内但没有出现在 nums 中的数字,并以数组的形式返回结果。

进阶:你能在不使用额外空间且时间复杂度为 O(n) 的情况下解决这个问题吗? 你可以假定返回的数组不算在额外空间内。

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. 找不同(简单难度)

给定两个字符串 st ,它们只包含小写字母。

字符串 t 由字符串 s 随机重排,然后在随机位置添加一个字母。

请找出在 t 中被添加的字母。

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. 两个数组的交集(简单难度)

给定两个数组 nums1nums2 ,返回 它们的交集 。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序

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(简单难度)

给你两个整数数组 nums1 和 nums2 ,请你以数组形式返回两数组的交集。返回结果中每个元素出现的次数,应与元素在两个数组中都出现的次数一致(如果出现次数不一致,则考虑取较小值)。可以不考虑输出结果的顺序。

使用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. 赎金信(简单难度)

给你两个字符串:ransomNote 和 magazine ,判断 ransomNote 能不能由 magazine 里面的字符构成。

如果可以,返回 true ;否则返回 false 。

magazine 中的每个字符只能在 ransomNote 中使用一次。

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. 有效的变位词(简单难度)

给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。

注意:若 s 和 t 中每个字符出现的次数都相同,则称 s 和 t 互为字母异位词。

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;
    }

1207. 独一无二的出现次数(简单难度)

class Solution {
public:
    bool uniqueOccurrences(vector<int>& arr) {
        unordered_map<int,int> dict;
        unordered_set<int> set;
        for(int c:arr) dict[c]++;
        for(auto iter:dict){
            if(!set.count(iter.second)) set.insert(iter.second);
            else return false;
        }
        return true;
    }
};

三、哈希表的中等难度

1、特殊规律

409. 最长回文串(简单难度)

给定一个包含大写字母和小写字母的字符串 s ,返回 通过这些字母构造成的 最长的回文串的长度 。

在构造过程中,请注意 区分大小写 。比如 “Aa” 不能当做一个回文字符串。

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 += c;
            }
        // 如果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;
    }

四、子串/子序列/子数组

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;
    }
};

五、哈希表处理数字之和

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 {};
    }
};

剑指 Offer 57. 和为s的两个数字

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        int left=0;//左指针
        int right=nums.size()-1;//右指针
        int sum=0;
        while(left<=right){
            sum=nums[left]+nums[right];
            // 如果两数和==target,直接返回
            if(sum==target) return {nums[left],nums[right]};
            // 如果两数和>target,说明应该降低两数和,右指针左移即可
            else if(sum>target) right--;
            // 如果两数和<target,说明应该增加两数和,左指针右移即可
            else left++;
        }
        return {nums[left],nums[right]};
    }
};

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

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

class Solution {
public:
    /*题目要求答案不重复,并且i,j,k是三个不同的数
    只要保证i<=j<=k,并且每重循环中都去重i,j,k就可以保证答案不重复
    i正序遍历,j正序遍历,k反序遍历,因为i固定时,j增加,k减少才能得到相同的和*/
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>> res;
        sort(nums.begin(),nums.end());//排序数组
        // 循环遍历
        int n = nums.size();
        int low, high, twoSum, tmp;// i+low+high=0
        // 固定i,[0,n-3],写成i<n也可以通过
        for (int i=0;i<n-2;++i) {
            if (i>0&&nums[i]==nums[i-1]) continue;// 防止第1个数i重复
            // 正序遍历low,反序遍历high
            low=i+1,high=n-1,twoSum = -nums[i];
            while(low<high){
                tmp=nums[low]+nums[high];
                if(tmp>twoSum)high--;
                else if(tmp<twoSum) low++;
                else{
                    res.push_back({nums[i], nums[low], nums[high]});
                    low++;high--;// 防止第二与第三个数重复
                    while(low<high&&nums[low]==nums[low-1])low++;
                    while(low<high&&nums[high]==nums[high+1])high--;
                }
            }
        }
        return res;
    }
};

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

class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
        vector<vector<int>> res;
        sort(nums.begin(), nums.end());  // 从小到大排序
        int n = nums.size();
        int low, high, twoSum, tmp;// i+j+low+high=0,low+high=twoSum
        // 枚举i,[0,n-1]
        for(int i=0;i<n-3; i++) {
            if(i>0&&nums[i]==nums[i-1]) continue;  // 防止第1个数i重复
            // 枚举j,[i+1,n-1]
            for(int j=i+1; j<n-2;j++) {
                if (j>i+1&&nums[j]==nums[j-1]) continue;  // 防止第2个数j重复
                low=j+1, high=n-1, twoSum=target-nums[i]-nums[j];
                while(low<high) {
                    tmp = nums[low]+nums[high];
                    if (tmp>twoSum) high--;// 如果low+high大,high--
                    else if (tmp<twoSum) low++;// 如果low+high小,low--
                    else {
                        // 添加结果
                        res.push_back({nums[i], nums[j], nums[low], nums[high]});
                        low++, high--;  // 防止第三与第四个数重复
                        while(low<high&&nums[low]==nums[low-1])low++;
                        while(low<high&&nums[high]==nums[high+1])high--;
                    } 
                }
            }
        }
        return res;
    }
};

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;
    }