代码随想录——哈希表

快速判断一个元素是否出现集合里的时候,就要考虑哈希法!(牺牲空间换时间)

哈希表/散列表(hash table):是根据关键码的值而直接进行访问的数据结构,eg数组是通过数组下标访问。

哈希函数:数据元素的存放位置和数据元素的关键字之间建立起某种对应关系。

构造哈希函数有三个要点:

第一,运算过程要尽量简单高效,以提高哈希表的插入和检索效率;

第二,哈希函数应该具有较好的散列性,以降低哈希冲突的概率;

第三,哈希函数应具有较大的压缩性,以节省内存。

哈希碰撞:对于两个不同的关键字,通过我们的哈希函数计算哈希地址时却得到了相同的哈希地址。

拉链法:当未发生冲突时,则直接存放该数据元素;当冲突产生时,把产生冲突的数据元素另外存放在单链表中。

线性探测法:要保证tableSize大于dataSize。

常见的三种哈希结构:数组、set(集合)、map(映射)(底层实现:红黑树/哈希表)

哈希表算法原理 - 知乎 (zhihu.com)

ps:

(17条消息) 【数据结构】史上最好理解的红黑树讲解,让你彻底搞懂红黑树_小七mod的博客-CSDN博客

  1. 数组型

242. 有效的字母异位

只需要将 s[i] - ‘a’ 所在的元素做+1 操作即可,并不需要记住字符a的ASCII,只要求出一个相对数值就可以了。

//哈希解法
class Solution {
public:
    bool isAnagram(string s, string t) {
        int record[26] = {0};
        if (s.size() != t.size()) return false;
        for (int i = 0; i < s.size(); i++) {
            // 并不需要记住字符a的ASCII,只要求出一个相对数值就可以了
            record[s[i] - 'a']++;
            record[t[i] - 'a']--;
        }
        for (int i = 0; i < 26; i++) {
            if (record[i] != 0) {
                // record数组如果有的元素不为零0,说明字符串s和t 一定是谁多了字符或者谁少了字符。
                return false; //return后相当于break
            }
        }
        // record数组所有元素都为零0,说明字符串s和t是字母异位词
        return true;
    }
};
383. 赎金信
//哈希解法
// 时间复杂度: O(n)
// 空间复杂度:O(1)
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++) {
            // 并不需要记住字符a的ASCII,只要求出一个相对数值就可以了
            record[magazine[i] - 'a']++;
        }
        for (int i = 0; i < ransomNote.size(); i++) {
            record[ransomNote[i] - 'a']--;
            // 如果小于零说明ransomNote里出现的字符,magazine没有
            if(record[ransomNote[i]-'a'] < 0) {
                return false;
            }
        }
        // record数组所有元素都大于等于0,说明字符串magazine有足够字符表示ransomNote
        return true;
    }
};
法二:暴力解法
// 时间复杂度: O(n^2)
// 空间复杂度:O(1)
class Solution {
public:
    bool canConstruct(string ransomNote, string magazine) {
        for (int i = 0; i < magazine.length(); i++) {
            for (int j = 0; j < ransomNote.length(); j++) {
                // 在ransomNote中找到和magazine相同的字符
                if (magazine[i] == ransomNote[j]) {
                    ransomNote.erase(ransomNote.begin() + j); // ransomNote删除这个字符
                    break;
                }
            }
        }
        // 如果ransomNote为空,则说明magazine的字符可以组成ransomNote
        if (ransomNote.length() == 0) {
            return true;
        }
        return false;
    }
};
49. 字母异位词分组

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

  1. set型

哈希值较少、分散、跨度大,使用数组就造成空间的极大浪费!!

std::unordered_set底层实现为哈希表,std::set 和std::multiset 的底层实现是红黑树,红黑树是一种平衡二叉搜索树,所以key值是有序的,但key不可以修改,改动key值会导致整棵树的错乱,所以只能删除和增加。

349. 两个数组的交集

无序!不重复!使用unordered_set!

class Solution {
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
        unordered_set<int> result_set; // 存放结果,之所以用set是为了给结果集去重
        unordered_set<int> nums_set(nums1.begin(),nums1.end());
        for (int num :nums2) { //遍历nums2
            // 发现nums2的元素 在nums_set里又出现过
            if (nums_set.find(num) != nums_set.end()) {
                result_set.insert(num);
            }
        }
        //set转化为vector返回
        return vector<int>(result_set.begin(),result_set.end());
    }
};
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;
        }
    }
};
  1. map型

1. 两数之和
  • 数组的大小是受限制的,而且如果元素很少,而哈希值太大会造成内存空间的浪费。

  • set是一个集合,里面放的元素只能是一个key,而两数之和这道题目,不仅要判断y是否存在而且还要记录y的下标位置,因为要返回x 和 y的下标。所以set 也不能用。

  • 我们不仅要知道元素有没有遍历过,还有知道这个元素对应的下标。使用 key value结构来存放,key来存元素,value来存下标

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
    std::unordered_map<int,int> map;
    for (int i = 0;i < nums.size();i++) {
        // 遍历当前元素,并在map中寻找是否有匹配的key
        auto iter = map.find(target - nums[i]);
        if (iter != map.end()) {
            return {iter->second, i};
        }
        // 如果没找到匹配对,就把访问过的元素和下标加入到map中
        map.insert(pair<int, int>(nums[i], i));
    }
    return {};        
    }
};
454. 四数相加 II
class Solution {
public:
    int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
        unordered_map<int,int> umap; //key:a+b的数值,value:a+b数值出现的次数
        // 遍历大A和大B数组,统计两个数组元素之和,和出现的次数,放到map中
        for (int a : nums1) {
            for (int b : nums2) {
                umap[a +b]++;
            }
        }
        int count = 0; // 统计a+b+c+d = 0 出现的次数
        // 在遍历大C和大D数组,找到如果 0-(c+d) 在map中出现过的话,就把map中key对应的value也就是出现次数统计出来。
        for (int c : nums3) {
            for (int d :nums4) {
                if (umap.find(0 - (c + d)) != umap.end()) {
                    count += umap[0 - (c + d)];
                }
            }
        }
        return count;
    }
};
3. 无重复字符的最长子串
滑动窗口+哈希map!!
class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        unordered_map<char, int> window;
        int leftIndex = 0, rightIndex = 0;
        int result = 0;
        while (rightIndex < s.size()) {
            char c = s[rightIndex];
            rightIndex++;
            window[c]++;
            while (window[c] > 1) {
                char d =s[leftIndex];
                leftIndex++;
                 window[d]--;
            }
            result = max(result, rightIndex - leftIndex);
        }
        return result;
    }
};

4. 双指针法优于哈希法

15. 三数之和
class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>> result;
        sort(nums.begin(), nums.end());
        // 找出a + b + c = 0
        // a = nums[i], b = nums[left], c = nums[right]
        for (int i = 0; i < nums.size(); i++) {
            // 排序之后如果第一个元素已经大于零,那么无论如何组合都不可能凑成三元组,直接返回结果就可以了
            if (nums[i] > 0) {
                return result;
            }
            if (i > 0 && nums[i] == nums[i-1]) {
                continue;
            }
            int left = i + 1;
            int right = nums.size() - 1;
            while (right > left) {
                if(nums[i] + nums[left] + nums[right] > 0) right--;
                else if(nums[i] + nums[left] + nums[right] < 0) left++;
                else {
                    result.push_back(vector<int>{nums[i], nums[left], nums[right]});
                    // 去重逻辑应该放在找到一个三元组之后,对b 和 c去重
                    while (right > left && nums[right] == nums[right - 1]) right--;
                    while (right > left && nums[left] == nums[left + 1]) left++;
                    // 找到答案时,双指针同时收缩
                    right--;
                    left++;
                }
            }
        }
        return result;
    }
};
18. 四数之和

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值