(6/60)哈希表理论基础、有效的字母异位词、两个数组的交集、快乐数、两数之和

哈希表理论基础

定义

哈希表是根据关键码的值而直接进行访问的数据结构。

特点是查询时间复杂度为O(1)。

哈希函数

哈希表存储过程:键---映射--->数值(索引)中,完成这个映射过程的函数。

哈希碰撞

哈希函数映射到同一位置时称为哈希碰撞。

解决方案有两种:

  1. 拉链法。在原位置上向后接链表存储。
  2. 线性探测法。在表中线性遍历下去寻找空位。

常见哈希结构

  1. 数组
  2. 集合(set)
  3. 映射(map)

C++提供的set数据结构

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

C++提供的map数据结构

映射 底层实现 是否有序 数值是否可以重复 能否更改数值 查询效率 增删效率
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)

(从上往下依次为:不可重复、可重复、不可重复且无序)

红黑树实现的key是有序的,但只能增删,不可修改。

总结

  1. 快速判断元素是否存在于集合内时,要想到哈希法。

  2. 本质是空间换时间


有效字母异位词

leetcode:242. 有效的字母异位词

排序法

思路

很简单的想法,排序完之后对比俩字符串是否相等。

复杂度分析

时间复杂度:O(NlogN)。两个排序,单个排序O(NlogN)。

空间复杂度:O(1)。

注意点

代码实现

class Solution {
public:
    // 排序法
    bool isAnagram(string s, string t) {
        sort(s.begin(),s.end());
        sort(t.begin(),t.end());

        if(s == t)
            return true;
        else
            return false;
    }
};

哈希法

思路

要判断某些元素出现次数是否相等,想到哈希法。

元素数量确定且小(26个字母),所以用数组的数据结构。

  1. 遍历一个字符串,对映射位置的元素++;再遍历另一个字符串,对映射位置的元素--
  2. 遍历映射数组,看元素是否还为初始化的值,全为初始值则两字符串为有效字母异位词,否则不是。

复杂度分析

时间复杂度:O(N+M)。两个并列循环,N、M分别是s、t的长度,加一个常数次的循环。

空间复杂度:O(1)。开了常数个(26)空间的数组。

注意点

  1. 数组初始化

    1. 使用花括号 {} 初始化:

      cppCopy Codeint arr[3] = {1, 2, 3};
      

      这种方式可以用于静态数组和动态数组的初始化,也可以用于多维数组的初始化。

    2. 不使用花括号 {} 初始化:

      cppCopy Codeint arr[3] = {0};    // 所有元素都初始化为0
      int arr[3] = {};     // 所有元素都初始化为0
      int arr[] = {1, 2};  // 数组长度自动推断为2
      

      这种方式也适用于静态数组和动态数组的初始化,但是不适用于多维数组的初始化。

    3. 使用数组下标逐个赋值:

      cppCopy Codeint arr[3];
      arr[0] = 1;
      arr[1] = 2;
      arr[2] = 3;
      

      这种方式可以用于静态数组和动态数组的初始化,也可以用于多维数组的初始化。

    4. 使用循环语句逐个赋值:

      cppCopy Codeint arr[3];
      for (int i = 0; i < 3; i++) {
          arr[i] = i + 1;
      }
      

      这种方式可以用于静态数组和动态数组的初始化,也可以用于多维数组的初始化。在多维数组的初始化时,需要嵌套多层循环。

  2. 数组不能直接.size()获取长度。可以通过sizeof(arr)/sizeof(arr[0])的方式获取数组长度。

代码实现

class Solution {
public:
    // 哈希法
    bool isAnagram(string s, string t) {
        // 用一个size为26的数组来映射字符出现次数
        int hash[26] = {0}; // *数组初始化方式
        // s中出现的字符对应位置++
        for(int i = 0;i < s.size();i++){
            hash[ s[i] -'a' ]++;	// 细节,字母减去'a'刚好就映射到了0~25的数组下标
        }
        // t中出现的字符对应位置--
        for(int j = 0;j < t.size();j++){
            hash[ t[j] -'a' ]--;
        }
        // 如果最终数组元素全为0,则是字母异位词
        for(int k = 0;k < 26;k++){
            if(hash[k] != 0)
                return false;
        }

        return true;
    }
};

两个数组的交集

leetcode:349. 两个数组的交集

哈希法(集合)

思路

输出结果唯一且无序,直接念unordered_set身份证号了,所以返回结果用无序集转vector。

中间过程,交集如何找:

  1. 另设一个集合,遍历存入数组1的值。
  2. 遍历数组2,看数组2元素是否已经存在于中间集,如果是则将元素存入结果集。

复杂度分析

时间复杂度:O(N)。

空间复杂度:O(N+M)。N是nums1的大小,是nums1、nums2的交集大小。

注意点

  1. vector有构造函数可以用迭代器。

代码实现

class Solution {
public:
    // 哈希法(集合)
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
        unordered_set<int> result_set;   // 结果集
        unordered_set<int> temp_set;	// 中间集(过程)
        for(int num : nums1){
            temp_set.insert(num);
        }

        for(int num : nums2){
            if(temp_set.find(num) != NULL){
                result_set.insert(num);
            }
        }

        return vector<int>(result_set.begin(),result_set.end());
    }
};

哈希法(数组)

思路

同集合法。已知数组长度<=1000时可以用数组来做映射。

复杂度分析

时间复杂度:O(N)。

空间复杂度:O(N)。N是两数组交集大小。

注意点

略。

代码实现

class Solution {
public:
posted @ 2024-01-30 17:34  Tazdingo  阅读(2385)  评论(0)    收藏  举报