哈希表总结

哈希表总结

1、 基础知识

哈希表又称为散列表
使用哈希表解决问题的时候,要用到的数据结构为:数组,Set,Map,数组很简单,主要说一下Set和Map
C++里面,Set和Map分别提供三种数据结构(图来自代码随想录)

  • Map(映射)
    img
    map作为关联容器的一种,储存的都是pair对象(pair<const K, T> ),储存的时候用pair类模板创建的键值对,需要注意的是:使用 map 容器存储的各个键值对,键的值既不能重复也不能被修改。
  • Set(集合)
    img
    set 容器存储的各个键值对,其键和值完全相同,也就意味着它们的类型相同,因此 set 容器类模板的定义中,仅有第 1 个参数用于设定存储数据的类型
    可以看到,unordered的map和set查找效率和增删效率是很优的,优先使用。如果需要集合是有序的,那就可以用set和map,要求重复数据的话,multi是可以用的。
    如果我们要快速判断一个元素是否在集合里出现过的时候,就需要考虑哈希法,具体的选择思路后续的题目总结中会介绍。

2、 相关算法题及解决思路

题目描述:
img
设定一个二十六位的数组就可以解决:

class Solution {
public:
    bool isAnagram(string s, string t) {
        bool result;
        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']--;
        }
        /* cout << (sizeof(record)/sizeof(record[0])) << endl;
        cout << 26 << endl;  */
        for (int i = 0; i < (sizeof(record)/sizeof(record[0])); i++) {
            result = true;
            if(record[i] != 0)
            {
                result = false;
                break;
            }
        }
        return result;

    }
};
  • 两个数组的交集
    题目描述:img
    可以使用unordered_set,将其中一个数组插入,(符合不重复,无需排序),然后在unordered_set中寻找
class Solution {
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
        int hash[1005] = {0};
        unordered_set <int> res;
        for (int num : nums1) {
            hash[num] = 1;
        }
        for (int num : nums2) {
            if (hash[num] == 1) {
                res.emplace(num);
            }
        }
        return vector<int>(res.begin() , res.end());

    }
};

题目描述:
img
这个题最重要的就是无限循环,无限循环就意味着相加结果会出现重复的情况,可以使用unordered_set储存相加结果,若出现重复,返回false,相加等于1,返回true。

class Solution {
public:

    int getsum(int num) {
        int sum = 0;
        while (num != 0) {
            sum += (num % 10) * (num % 10);
            num /= 10;
        }
        return sum;
        
    }
    bool isHappy(int n) {
        unordered_set<int> sum_res_his;
        int sum_res = getsum(n);
        int c = 0;
        int a = 0;
        while (sum_res != 1) {
            if (sum_res_his.find(sum_res) != sum_res_his.end()) {
                //cout << c << endl;
                //cout << a << endl;
                return false;
            }
            else {
                sum_res_his.insert(sum_res);
                //c++;

            }
            sum_res = getsum(sum_res);
            //a++;
        }
        return true;
    }
};

题目描述:img
每种输入只会对应一个答案,就意味着数组当中只有这两个数可以完成条件,且不能重复使用
把这个数组放到map中,key为数值,对应的值为下标,遍历map容器,寻找相加为target的,并返回对应即可。可以边放入map,边寻找。

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        std::unordered_map <int, int> his;
        for (int i = 0; i < nums.size(); i++) {
            auto iter = his.find(target - nums[i]);
            if (iter != his.end()) {
                return vector<int>{iter->second, i};
            }//寻找部分
            his.insert(pair<int ,int>(nums[i], i));//放入部分
        }
        return {};
        
    }
};

img
和解决两书相加的思路一致,先组合前两个数组,相加的和作为key放入map当中,对应的值为相加为此值的组数,再在map中寻找能和后两个数组相加为0的,然后统计前两个数组形成的map里面的次数即可(因为若满足条件,map存储的必然是所出现的次数)

class Solution {
public:
    int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
        unordered_map <int, int> sum_resu;
        for (auto i1 : nums1) {
            for (auto i2 : nums2) {
                sum_resu[i1 + i2]++;
            }
        }
        int count = 0;
        for (auto i3 : nums3) {
            for (auto i4 :nums4) {
                if (sum_resu.find(0 - (i3 + i4)) != sum_resu.end()) {
                    count += sum_resu[(0 - (i3 + i4))];
                }
            }
        }
        return count;

    }
};

img

这个题让人头皮发麻,最主要的要求是不重复,在数组内找出来不重复的三元组让其相加等于0。如果用哈希法写,去重很困难,有很多的问题都会想不到,博主头铁写了一下,到现在都没搞清楚,可能实力还不够,我们直接来说双指针法。双指针法在链表中也用到过,非常好理解,并且思路可以适用于四数之和,五数之和......
双指针法同样,也面临的问题就是,如何去重,具体的思路就是:指定第一个数a,然后在a的下一个数作为b,数组的最后一个数作为c,如果三数之和大于0,c向前一个数(有序的好处),如果小于0,b向前一步,并且,在a,b,c选择挪动的时候需要考虑怎么去重,去重方式很重要。

img
代码如下:

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>> res;
        sort(nums.begin(), nums.end());
        for (int i = 0; i < nums.size(); i++) {
            if (nums[0] > 0) {
                break;
            }
            if (i > 0 && nums[i] == nums[i-1]) {
                continue;
            }
            int right = nums.size() - 1;
            int left = i + 1;
            while (left < right) {
            int sum = nums[i] + nums[left] + nums[right];
            
            if (sum > 0) {
                right--;
            }
            if (sum < 0) {
                left++;
            }
            if (sum == 0) {
            res.push_back(vector<int>{nums[i], nums[left], nums[right]});

            while (left < right && nums[right] == nums[right - 1]) {
                right--;
            } 
            while (left < right && nums[left] == nums[left + 1]) {
                left++;
            }
                left++;
                right--;
            }
            
            /* while (left < right && nums[right] == nums[right + 1]) {
                right--;
            } */
            

            }
            
        }
        return res;

    }
};

需要注意的点有:
1、排序(有助于我们快速判断,并且有助于指针的操作)
2、我们需要注意,题目要求的是三元组不能重复,但是在三元组内部,是可以重复的
3、我们需要寻找的是a+b+c=0,输出[a,b,c],a如何去重?nums[i] == nums[i-1] 作为判断条件,还是nums[i] == nums[i+1]作为判断条件?如右图第一种情况,若nums[i] == nums[i+1]作为判断条件,如下图①,就会出现直接跳过了b = -2这种情况,而nums[i] == nums[i-1]就不会这样。
4、同样的问题,带到b,c去重,b,c去重是先判断还是先找三元组?下图中②所示,如果先去重,会忽略b = 1的情况,导致跳过了a = -2,b = 1,c = 1这种情况,所以先组成三元组,然后在b,c向前的方向判断b,c是否重复即可

img

四数之和同样的道理,如下图
img

class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
        vector<vector<int>> res;
        sort(nums.begin(),nums.end());
        for (int i = 0; i < nums.size(); i++) {
            if (nums[i] > target && nums[i] > 0) break;
            if (i > 0 && nums[i] == nums[i-1]) continue; //i去重
            for (int j = i + 1; j < nums.size(); j++) {
                if (nums[i] + nums[j] > target && nums[i] + nums[j] >= 0) break;
                if (j > i + 1  && nums[j-1] == nums[j]) continue;//j去重
            int left = j + 1;
            int right = nums.size() - 1;
            while (left < right) {
            int sum = (long) nums[i] + nums[j] + nums[left] + nums[right];
            if (sum > target) {
                right--;
            }
            else if (sum < target) {
                left++;
            }
            else if (sum == target) {
                res.push_back(vector<int>{nums[i], nums[j], nums[left], nums[right]});
                while (left < right && nums[left] == nums[left + 1]) {
                left++;
            }
                while (left < right && nums[right] == nums[right - 1]) {
                right--;
            }
            left++;
            right--;
            }
            }
              
            }
        }
        return res;

    }
};

题目描述:img
就像有效字母有效的字母异位词一样,但是要求每个字符在ransomnote中只出现一次,哈希法最简单不过了,但是到底是用数组呢还是map?map的寻找建立比较费时,最好用数组就可以,再说字母总共26位,是有限的,我为了熟悉map的应用,也写了map的版本。
img

map版本

class Solution {
public:
    bool canConstruct(string ransomNote, string magazine) {
        map<char, int> store_ran; 
        // ransomNote放入map当中,key为字母,对应的值为次数
        for (int i = 0; i < ransomNote.length(); i++) {
            if (store_ran.find(ransomNote[i]) != store_ran.end()) {
                store_ran.at(ransomNote[i])++;
                                        
            }
            else {
                 store_ran.emplace(make_pair(ransomNote[i],1));
            }
        }
        for (int i = 0; i < magazine.length(); i++) {
            if (store_ran.find(magazine[i]) != store_ran.end()) {
                store_ran.at(magazine[i])--;
            }
        }//如果map中各个key对应的值为0或者小于0,则证明就是赎金信
        for (auto iter = store_ran.begin(); iter != store_ran.end(); iter++) {
            if(iter->second > 0) {
                return false;
            }
        }//遍历map,一旦出现key值对应的为大于0状态,返回false
        return true;
    }
};

数组版本

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

    }
};

参考:代码随想录

posted @ 2023-01-18 17:17  jiumaohappyboy  阅读(72)  评论(0编辑  收藏  举报