算法之哈希表| 青训营

哈希表

理论基础哈希表

哈希表是一种根据关键码的值而直接进行访问的数据结构。它通过散列函数将关键码映射到数组的索引位置,从而实现快速的查找、插入和删除操作。一般哈希表都是用来快速判断一个元素是否出现在集合里。

牺牲了空间换取了时间

哈希表的优势在于其平均时间复杂度为 O(1),但在某些情况下,哈希碰撞可能会导致查找性能下降。

哈希碰撞

哈希碰撞是指两个不同的关键码被映射到了同一个索引位置的情况。在哈希表中,解决哈希碰撞的主要方法有拉链法和线性探测法。

拉链法

拉链法是解决哈希碰撞的常见方法之一。发生冲突的元素都被存储在同一个索引位置的链表中,每个索引位置维护一个链表结构。

线性探测法

线性探测法是另一种解决哈希碰撞的方法。当发生碰撞时,它会依次检查哈希表的下一个位置,直到找到一个空位为止。

常见的三种哈希结构
  • 数组:哈希表可以使用数组来实现,数组的索引表示哈希值。
  • Set(集合):哈希集合是不重复元素的集合,它可以快速判断元素是否存在。
  • Map(映射):哈希映射是一种键值对的集合,可以根据键快速查找对应的值。

示例题目

1. 有效的字母异位词

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

解法: 使用哈希表统计字符串 s 中每个字符出现的次数,并在遍历字符串 t 时,对哈希表进行相应的减少。最后检查哈希表中的计数是否全为零。

暴力解法O(n^2)

两层for循环,同时还要记录字符是否重复出现

哈希表O(n)

只需要将 s[i] - ‘a’ 所在的元素做+1 操作即可,并不需要记住字符a的ASCII,只要求出一个相对数值就可以了同样在遍历字符串t的时候,对t中出现的字符映射哈希表索引上的数值再做-1的操作。最后检查一下,record数组如果有的元素不为零0,说明字符串s和t一定是谁多了字符或者谁少了字符,return false。

bool isAnagram(string s, string t) {
    unordered_map<char, int> count;
    for (char c : s) {
        count[c]++;
    }
    for (char c : t) {
        count[c]--;
    }
    for (const auto& entry : count) {
        if (entry.second != 0) {
            return false;
        }
    }
    return true;
}
2. 两个数组的交集

给定两个数组,编写一个函数来计算它们的交集。

解法: 使用哈希表记录数组1中元素出现的次数,然后遍历数组2,找到数组1中存在的元素并加入结果集。

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的元素在nums_set里又出现过
        if (nums_set.find(num) != nums_set.end()) {
            result_set.insert(num);
        }
    }
    return vector<int>(result_set.begin(), result_set.end());
}
3. 快乐数

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

解法: 使用哈希表记录每次计算的结果,判断是否出现循环或者得到1。

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;
    }
}
4. 两数之和

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

解法: 使用哈希表记录每个元素的索引,遍历数组时查找是否存在目标值与当前元素的差值,如果存在,则找到了两个数的和为目标值。

cppCopy code
vector<int> twoSum(vector<int>& nums, int target) {
    unordered_map<int, int> map; // key: 数组元素, value: 数组元素的索引
    for (int i = 0; i < nums.size(); i++) {
        int complement = target - nums[i];
        if (map.find(complement) != map.end()) {
            return {map[complement], i};
        }
        map[nums[i]] = i;
    }
    return {};
}
5. 四数相加II(哈希法)

给定四个包含整数的数组列表 A, B, C, D,计算有多少个元组 (i, j, k, l) ,使得 A[i] + B[j] + C[k] + D[l] = 0。

解法: 使用两个哈希表,分别记录 A 和 B 两个数组中元素的和出现的次数。然后在遍历 C 和 D 两个数组时,查找是否存在目标值与当前元素的相反数 。

int fourSumCount(vector<int>& A, vector<int>& B, vector<int>& C, vector<int>& D) {
        unordered_map<int, int> umap; //key:a+b的数值,value:a+b数值出现的次数
        // 遍历大A和大B数组,统计两个数组元素之和,和出现的次数,放到map中
        for (int a : A) {
            for (int b : B) {
                umap[a + b]++;
            }
        }
        int count = 0; // 统计a+b+c+d = 0 出现的次数
        // 在遍历大C和大D数组,找到如果 0-(c+d) 在map中出现过的话,就把map中key对应的value也就是出现次数统计出来。
        for (int c : C) {
            for (int d : D) {
                if (umap.find(0 - (c + d)) != umap.end()) {
                    count += umap[0 - (c + d)];
                }
            }
        }
        return count;
    }
posted @ 2023-07-30 22:22  LucianaiB  阅读(56)  评论(0)    收藏  举报  来源