[代码随想录]哈希表

哈希表

定义

1.哈希表是根据关键码的值而直接进行访问的数据结构,一般用来快速判断一个元素是否出现集合里。
2.哈希函数是将关键词映射到哈希表上的索引。
例如将学生信息放入哈希表中,(通过哈希函数把名字转化为数值,一般哈希函数是通过特定编码方式,可以将其他数据格式转化为不同的数值,这样就把学生名字映射为哈希表上的索引数字了。)
在这里插入图片描述
注:如果哈希函数得到的数值大于 哈希表的大小了,此时为了保证映射出来的索引数值都落在哈希表上,我们会在再次对数值做一个取模的操作,就要我们就保证了学生姓名一定可以映射到哈希表上了。(不止只有取模操作,还有平方取中法,折叠法等)

3.哈希碰撞:如果关键词数量大于哈希表的大小,此时就算哈希函数计算的再均匀,也避免不了会有几个关键词同时映射到哈希表 同一个索引下标的位置。这就是哈希碰撞。
在这里插入图片描述
4.解决哈希碰撞方法:①拉链法:发生冲突的元素都被存储在链表中。
在这里插入图片描述
②线性探测法:使用线性探测法,一定要保证tableSize大于dataSize。 我们需要依靠哈希表中的空位来解决碰撞问题。
例如冲突的位置,放了小李,那么就向下找一个空位放置小王的信息。所以要求tableSize一定要大于dataSize ,要不然哈希表上就没有空置的位置来存放 冲突的数据了。
在这里插入图片描述
5.常见哈希数据结构:数组,set,map

集合 底层实现 是否有序 数值是否可以重复 能否更改数值 查询效率 增删效率
std::set 红黑树 有序 O(log n) O(log n)
std::multiset 红黑树 有序 O(logn) O(logn)
std::unordered_set 哈希表 无序 O(1) O(1)
映射 底层实现 是否有序 数值是否可以重复 能否更改数值 查询效率 增删效率
std::map 红黑树 key有序 key不可重复 key不可修改 O(logn) O(logn)
std::multimap 红黑树 key有序 key可重复 key不可修改 O(log n) O(log n)
std::unordered_map 哈希表 key无序 key不可重复 key不可修改 O(1) O(1)

6.总结:当我们遇到了要快速判断一个元素是否出现集合里的时候,就要考虑哈希法。
但是哈希法也是牺牲了空间换取了时间,因为我们要使用额外的数组,set或者是map来存放数据,才能实现快速的查找。

相关题目

1.有效的字母异位词

242.有效的字母异位词

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

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

1.暴力for

class Solution {
    
    
public:
    bool isAnagram(string s, string t) {
    
    
        bool isOrNot = true;
        if(s.size() != t.size())return false;
        for(int i = 0;i < s.size();i++){
    
    
            for(int j = 0;j< t.size();j++){
    
    
                if(s[i] == t[j]) {
    
    
                    isOrNot = true;
                    t.erase(j,1);
                    break;
                }
                else isOrNot = false;
            }
            if(isOrNot == false)return false;
        }
        return true;
    }
}

2.哈希表

class Solution {
    
    
public:
    bool isAnagram(string s, string t) {
    
    
        int record[26] = {
    
    0};
        for(int i = 0;i < s.size();i++) record[s[i] - 'a']++;
        for(int i = 0;i < t.size();i++) record[t[i] - 'a']--;
        for(int i = 0;i < 26;i++){
    
    
            if(record[i] != 0) return false;
        }
        return true;
    }
};

383.救赎信

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

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

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

class Solution {
    
    
public:
    bool canConstruct(string ransomNote, string magazine) {
    
    
        int record[26] = {
    
    0};
        if(ransomNote.size() > magazine.size())return false;
        for(int i = 0;i < magazine.size();i++) record[magazine[i] - 'a']++;
        for(int i = 0;i < ransomNote.size();i++) record[ransomNote[i] - 'a']--;
        for(int i = 0;i < 26;i++){
    
    
            if(record[i] < 0)return false;
        }
        return true;
    }
};

*49.字母异位词分组

给你一个字符串数组,请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。

字母异位词 是由重新排列源单词的字母得到的一个新单词,所有源单词中的字母通常恰好只用一次。

1.哈希

class Solution {
    
    
public:
    vector<vector<string>> groupAnagrams(vector<string>& strs){
    
    
        map<string,vector<string>> mp;
        vector<vector<string>>record;
        for(string& s:strs){
    
    
            string str(26, ' ');
            for(char val:s){
    
    
                str[val - 'a']++;
            }
            mp[str].emplace_back(s);//emplace_back() 和 push_back() 的区别,就在于底层实现的机制不同。push_back() 向容器尾部添加元素时,首先会创建这个元素,然后再将这个元素拷贝或者移动到容器中(如果是拷贝的话,事后会自行销毁先前创建的这个元素);而 emplace_back() 在实现时,则是直接在容器尾部创建这个元素,省去了拷贝或移动元素的过程。
        }
        for(auto val:mp){
    
    
            record.emplace_back(val.second);
        }
        return record;
    }
};

2.哈希加排序

class Solution {
    
    
public:
    vector<vector<string>> groupAnagrams(vector<string>& strs){
    
    
        map<string,vector<string>> mp;
        vector<vector<string>>record;
        for(string& s:strs){
    
    
            string key = s;
            sort(key.begin(),key.end());
            mp[key].emplace_back(s);
        }
        for(auto& val:mp){
    
    
            record.emplace_back(val.second);
        }
        return record;
    }
};

*438. 找到字符串中所有字母异位词

给定两个字符串 sp,找到 s 中所有 p异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。

异位词 指由相同字母重排列形成的字符串(包括相同的字符串)。

滑动窗口解决

class Solution {
    
    
public:
    vector<int> findAnagrams(string s, string p) {
    
    
        vector<int>v;
        int record[26] = {
    
    0};
        for(int i = 0;i < p.size();i++){
    
    
            record[p[i] - 'a']++;
        }
        for(int slowIndex = 0,fastIndex=0;fastIndex < s.size();fastIndex++){
    
    
            record[s[fastIndex] - 'a']--;
            while(record[s[fastIndex] - 'a'] < 0){
    
    
                record[s[slowIndex] - 'a']++;
                slowIndex++;
            }
            if(fastIndex - slowIndex + 1 == p.size()) v.push_back(slowIndex);
        }
        return v;
    }
};

2.查找两个数组交集

349. 两个数组的交集

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

示例 1:

输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2]

1.暴力for

class Solution {
    
    
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
    
    
        vector<int>record;
        sort(nums1.begin(),nums1.end());
        for(int i = 0;i < nums1.size();i++){
    
    
            if(i > 0 && nums1[i] == nums1[i - 1]) continue;
            for(int j = 0;j < nums2.size();j++){
    
    
                if(nums1[i] == nums2[j]){
    
    
                    record.emplace_back(nums1[i]);
                    break;
                }
            }
        }
        return record;
    }
};

2.利用set容器 利用count算法也可以

class Solution {
    
    
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
    
    
        vector<int>record;
        set<int>s1;
        set<int>s2;
        for(int i = 0;i < nums1.size();i++){
    
    //set里面自动排序,且无重发
            s1.emplace(nums1[i]);
        }
        for(int i = 0;i < nums2.size();i++){
    
    
            s2.emplace(nums2[i]);
        }
        set<int>::iterator it = s1.begin();
        while(it != s1.end()){
    
    
           if(s2.find(*it) != s2.end()){
    
    //利用find算法
               record.emplace_back(*it);
           }
           it++;
        }
        return record;
    }
};

3.双指针加排序

class Solution {
    
    
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
    
    
        vector<int>record;
        sort(nums1.begin(),nums1.end());
        sort(nums2.begin(),nums2.end());
        int nums1Index = 0,nums2Index = 0;
        while(nums1Index < nums1.size() && nums2Index < nums2.size()){
    
    
            if(nums1[nums1Index] == nums2[nums2Index]){
    
    
                if (record.size() == 0 || nums1[nums1Index] != nums1[nums1Index - 1]) {
    
    
                    record.push_back(nums1[nums1Index]);
                }
                nums1Index++;
                nums2Index++;
            }
            else if(nums1[nums1Index] > nums2[nums2Index]){
    
    
                nums2Index++;
            }
            else nums1Index++;
        }
        return record;
    }
};

350.两个数组的交集 II

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

示例 1:

输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2,2]

双指针加排序

class Solution {
    
    
public:
    vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
    
    
        sort(nums1.begin(),nums1.end());
        sort(nums2.begin(),nums2.end());
        vector<int>record;
        int nums1Index = 0,nums2Index = 0;
        while(nums1Index < nums1.size() && nums2Index < nums2.size()){
    
    
            if(nums1[nums1Index] == nums2[nums2Index]){
    
    
                record.emplace_back(nums1[nums1Index]);
                nums1Index++;
                nums2Index++;
            }
            else if(nums1[nums1Index] < nums2[nums2Index]){
    
    
                nums1Index++;
            }
            else nums2Index++;
        }
        return record;
    }
};

3.快乐数

202. 快乐数

编写一个算法来判断一个数 n 是不是快乐数。

「快乐数」 定义为:

  • 对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
  • 然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
  • 如果这个过程 结果为 1,那么这个数就是快乐数。

如果 n快乐数 就返回 true ;不是,则返回 false

示例 1:

输入:n = 19
输出:true
解释:
12 + 92 = 82
82 + 22 = 68
62 + 82 = 100
12 + 02 + 02 = 1

1.递归

class Solution {
    
    
public:
    vector<int> v;
    bool isHappy(int n) {
    
    
       int sum = 0;
       while(n){
    
    
           sum = sum + (n % 10) * (n % 10);
           n /= 10;
       }
       if(find(v.begin(),v.end(),sum) != v.end()) return false;
       v.emplace_back(sum);
       if(sum == 1){
    
    
           return true;
       } 
       else{
    
    
           return isHappy(sum);
       }
    }
};

2.换成set集合,利用本身find函数

3.利用快慢指针,快指针每次进行两次运算

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) {
    
    
        int slowSum = n;
        int fastSum = n;
        do{
    
    
            slowSum = getSum(slowSum);
            fastSum = getSum(getSum(fastSum));
        }while(slowSum != fastSum);
        return slowSum == 1;
    }
};

4.两数之和

1.两数之和

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。

你可以按任意顺序返回答案。

示例 1:

输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1]

1.暴力for都是O(n^2)时间复杂度

2.利用unordered_map来记录

class Solution {
    
    
public:
    vector<int> twoSum(vector<int>& nums, int target) {
    
    
        vector<int> record;
        unordered_map<int,int>m1;
        for(int i = 0; i < nums.size();i++){
    
    
            auto it = m1.find(target - nums[i]);
            if(it != m1.end()){
    
    
                record.emplace_back(it->second);
                record.emplace_back(i);
                return record;
            }
            m1[nums[i]] = i;
        }
        return record;
    }
};

5.四数之和

*454. 四数相加 II

给你四个整数数组 nums1nums2nums3nums4 ,数组长度都是 n ,请你计算有多少个元组 (i, j, k, l) 能满足:

  • 0 <= i, j, k, l < n
  • nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0

示例 1:

输入:nums1 = [1,2], nums2 = [-2,-1], nums3 = [-1,2], nums4 = [0,2]
输出:2
解释:
两个元组如下:
1. (0, 0, 0, 1) -> nums1[0] + nums2[0] + nums3[0] + nums4[1] = 1 + (-2) + (-1) + 2 = 0
2. (1, 1, 0, 0) -> nums1[1] + nums2[1] + nums3[0] + nums4[0] = 2 + (-1) + (-1) + 0 = 0

1.哈希加分组

class Solution {
    
    
public:
    unordered_map<int,int>twoSum(vector<int>& nums1, vector<int>& nums2){
    
    
        unordered_map<int,int>twoSum;
        for(int i = 0;i < nums1.size();i++){
    
    
            for(int j = 0;j < nums2.size();j++){
    
    
                twoSum[nums1[i] + nums2[j]]++;
            }
        }
        return twoSum;
    }
    int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
    
    
        int sum = 0;
        unordered_map<int,int>twoSum1 = twoSum(nums1,nums2);
        unordered_map<int,int>twoSum2 = twoSum(nums3,nums4);
        for(auto val:twoSum2){
    
    
            if(twoSum1.find(-(val.first)) != twoSum1.end()){
    
    
                sum += val.second * twoSum1[-(val.first)];
            }
        }
        return sum;
    }
};

2.改进

class Solution {
    
    
public:
    int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
    
    
        int sum = 0;
        unordered_map<int,int>twoSum1;
        for(int i = 0;i < nums1.size();i++){
    
    
            for(int j = 0;j < nums2.size();j++){
    
    
                twoSum1[nums1[i] + nums2[j]]++;
            }
        }
        unordered_map<int,int>twoSum2;
        for(int i = 0;i < nums1.size();i++){
    
    
            for(int j = 0;j < nums2.size();j++){
    
    
                if(twoSum1.find(-(nums3[i] + nums4[j])) != twoSum1.end()) sum += twoSum1[-(nums3[i] + nums4[j])];
            }
        }
        return sum;
    }
};

6.三数之和

15. 三数之和

给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != ji != kj != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请

你返回所有和为 0 且不重复的三元组。

**注意:**答案中不可以包含重复的三元组。

示例 1:

输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
解释:
nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。
nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。
nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。
不同的三元组是 [-1,0,1][-1,-1,2] 。
注意,输出的顺序和三元组的顺序并不重要。

1.暴力或者O(n^3)时间复杂度通过不了时间

2.双指针法加排序

class Solution {
    
    
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
    
    
        vector<vector<int>>threeSum;
        sort(nums.begin(),nums.end());
        
        for(int i = 0;i < nums.size();i++){
    
    
            if(i > 0 && nums[i] == nums[i - 1]) continue;//去重,跟前面数一样就重复了
            if(nums[i] > 0)break;//最小已经大于0就结束了

            int left = i + 1,right = nums.size() - 1;
            while(left < right){
    
    
                if(nums[i] + nums[left] + nums[right] == 0){
    
    
                    vector<int>v = {
    
    nums[i],nums[left],nums[right]};
                    threeSum.emplace_back(v);
                    left++;
                    right--;
                    while(left < right && nums[left] == nums[left - 1]) left++;//必须加left < right,不然left可能会大于size();right可能会小于0
                    while(left < right && nums[right] == nums[right + 1])right--;
                }
                else if(nums[i] + nums[left] + nums[right] > 0){
    
    
                    right--;
                }
                else left++;
                
            }
        }
        return threeSum;
    }
};

3.哈希,太过于麻烦,出错太多

7.四数之和

18. 四数之和

给你一个由 n 个整数组成的数组 nums ,和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] (若两个四元组元素一一对应,则认为两个四元组重复):

  • 0 <= a, b, c, d < n
  • abcd 互不相同
  • nums[a] + nums[b] + nums[c] + nums[d] == target

你可以按 任意顺序 返回答案 。

1.双指针加排序,比三数之和多一个循环

class Solution {
    
    
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
    
    
        vector<vector<int>> fourSum;
        if(nums.size() < 4)return fourSum;
        sort(nums.begin(),nums.end());
        int i = 0,j = nums.size() - 1;
        long  sum = 0;
        while(i < j){
    
    
            if(i > 0 && nums[i] == nums[i - 1]) {
    
    //防止重复
                i++;
                continue;
            }
            if(nums[i] > target && nums[i] >= 0 )break;//四个数中最小都大于target就直接退出,当target小于0时,最小数大于0就退出
            while(j > i){
    
    
               if(j < nums.size() - 1 && nums[j] == nums[j + 1]){
    
    
                    j--;
                    continue;
                }
                int left = i + 1,right = j - 1;
                while(left < right){
    
    
                    sum = long(nums[i]) + long(nums[left]) + long(nums[right]) + long(nums[j]); //会溢出
                    if(sum == long(target)){
    
    
                        vector<int> v = {
    
    nums[i],nums[left],nums[right],nums[j]};
                        fourSum.emplace_back(v);
                        left++;
                        right--;
                        while(left < right && nums[left] == nums[left - 1])left++;
                        while(left < right && nums[right] == nums[right + 1])right--;
                    }
                    else if(sum > long(target)){
    
    
                        right--;
                    }
                    else {
    
    
                        left++;
                    }
                }
                j--;
            }
            i++;
            j = nums.size() - 1;  
        }
        return fourSum;
    }
};

猜你喜欢

转载自blog.csdn.net/m0_53953432/article/details/128278625