Day6 哈希表 LeetCode 242 349 202 1

61 阅读4分钟

理论基础

hash table(哈希表、散列表)

  • 用来**快速判断某个元素是否在集合里的时候,用,**查找效率O(1)牺牲空间换时间

  • 元素映射到哈希表index需要哈希函数,hash function

  • 如果映射出现冲突则会发生哈希碰撞,hash collisions,一般采用拉链法或线性探测法

    • 拉链法,冲突位置用链表,将冲突元素存储其中,避免一次申请过大内存
    • 需要table size大于dataSize,冲突的话接着往下找合适位置

常见三种结构

  • 数组
  • set(集合)
set底层实现有序否数值是否可重复能否改值查询效率增删效率
std::set红黑树O(longN)O(longN)
std::multiset红黑树O(longN)O(longN)
std::unordered_set哈希表O(1)O(1)
  • 红黑树是一种平衡二叉搜索树,所以key值有序,但key不可改,改动会导致整个树错乱
  • 需要用集合时,优先unordered_set,查找增删最优,如果有序则set,如果还要重复数据就multiset
  • map(映射)
map底层实现key有序否key是否可重复key能否改值查询效率增删效率
std::map红黑树O(longN)O(longN)
std::multimap红黑树O(longN)O(longN)
std::unordered_map哈希表O(1)O(1)
  • map是key value对,对key有限制,value没有,因为key的存储方式采用红黑树
  • 虽然set multiset底层实现是红黑树,不是哈希,而且用红黑树来索引和存储,但是我们使用的方式仍然是key value,所以还是称哈希法,map也是这个原理

242. 有效的字母异位词

心得

  • 上手就是map,而且对unordered_map基本操作不熟悉
  • 应该针对不同情况具体分析,某些优化的点进行思考,如为什么用一个数组即可,如全是小写字母暗示数据量少使用数组

题解

  • 当前数据量少,统计数组一个即可,同时需要注意需要赋初始值,最好以后写代码都显式赋值,养成好习惯,否则很有可能用的垃圾值
class Solution {
public:
    bool isAnagram(string s, string t) {
        int hash[26] = {0}; // 由于小写仅26个即可,定义一个即可,新增++,减--即可
        for (int i = 0; i < s.size(); i++){
            hash[s[i] - 'a']++;
        }
        for (int i = 0; i < t.size(); i++){
            hash[t[i] - 'a']--;
        }
        for (int i = 0; i < 26; i++){
            if (hash[i] != 0){
                return false;
            }
        }

        return true;

    }
};

349. 两个数组的交集

心得

  • 看到交集想到的是,交集和剩余元素组成原数组,类似set感觉,但是其实2个块间的set,并没有什么用,导致误导,应该仔细审题,尤其是记录类,尽量往哈希上靠
  • 同时想用set发现容器基本操作不熟,需要加强基本知识学习

题解

  • 本题原来数组大小未做限制,而且强调返回结果唯一,结果考虑set,重点考虑set的思路
  • 注意for循环的不需要索引的简介写法和遍历的写法find,以及容器的插入操作和容器间的相互转换
  • 限制大小有限制也可以用数组做
// 使用set
class Solution {
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
        unordered_set<int> result_set;
        unordered_set<int> nums1_set(nums1.begin(), nums1.end());
        for (int num : nums2){
            if (nums1_set.find(num) != nums1_set.end()){
                result_set.insert(num);
            }
        }
        return vector<int>(result_set.begin(), result_set.end());
    }
};

// 使用数组
class Solution {
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
        unordered_set<int> result;
        int hash[1001] = {0};
        for (int num : nums1){
            hash[num] = 1;
        }
        for (int num : nums2){
            if (hash[num] == 1){
                result.insert(num);
            }
        }
        return vector<int>(result.begin(), result.end());
    }
};

202. 快乐数

心得

  • 看到题目想的是结果是1xxx0的形式是快乐数,然后往勾股定理那边偏,但是忽略无限循环的含义
  • 还有没有完全理解题意,对于无限循环有误解,其实只要找到重复的第一个无限循环的数字,其他位置会按照之前的结果依次开始
  • 涉及到求和一定要注意初始化,否则可能会存在起始值不对的问题

题解

  • 两个结果一个是1应该是无限循环,对于结果1的情况,仔细处理好取每位值求平方,无非取余 除法运算
  • 对于循环,由于长度待定,且需要查询才能知道是否重复,所以用哈希记录,出现循环立刻返回false
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;
            }
            if (set.find(sum) != set.end()){
                return false;
            } else {
                set.insert(sum);
            }
            n = sum;
        }

    }
};

1. 两数之和

心得

  • 知道用hash但是 不会写iter,包括返回两个元素的自动转换效果,基本语法不熟练,即使知道算法无从下手

题解

  • 为什么用哈希,需要记录下来之前的值和索引,反复查询,涉及此哈希最好,同时保存数值和索引因此用map
  • 为什么用key是值而不是索引,因为查询的就是key
  • 注意auto 返回的iter写法,获取其值用的second,以及返回数据的大括号写法
class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        unordered_map<int, int> map;
        for (int i = 0; i < nums.size(); i++) {
            auto iter = map.find(target - nums[i]);
            if (iter != map.end()) {
                return {iter->second, i};
            }     
            map.insert(pair<int, int>(nums[i], i));
        }

        return {};
        
    }
};