哈希/散列法/LeetCode例题

说到哈希,总感觉到它很高深,说到底它只不过是实现集合和字典(满足数据增删改查的结构)的一种方式,而不是总把它当成单独的一种数据结构或者算法去讨论。

散列法:

  1. 定义:将数据中key传给一个哈希函数f(),按照计算出来的哈希值h=f(key)将那个带有key的数据分布到一个哈希表中(大小m自定义)。
  2. 当m<数据量n的时候,会出现碰撞(即哈希值为同一个,意味着哈希值相同的数据要储存在哈希表的同一个哈希地址里),甚至大于等于的时候也会出现(取决于哈希函数),所以此时需要解决碰撞的机制。
  3. 解决碰撞1开散列:每个哈希单元格都存储着一个链表,所有对应哈希值的数据都存储在这个链表里,所以碰撞后,直接将相同哈希值的数据依次往链表里push,所以可想而知最坏情况就是所有键的哈希值都是一样,这样的话所有数据都在一个哈希地址的单元格链表里。这样的话增删改查都是遍历对应那个哈希值的链表即可。
  4. 解决碰撞2闭散列:当碰撞时,对应哈希值的单元格已经存储数据,这时我们就线性探测(往这个单元格后面依次遍历,只要找到一个空的就将元素插在里面),这种方法比前一种更有可能出现最坏情况:会导致合并的哈希单元格越来越多,到最后可能插入一个元素就需要O(n),因为可能需要遍历很多单元格才能找到空的。往哈希表里添加是这样,查找也是如此,依次往后找(但是如果哈希合并了就很麻烦了),删除更加麻烦,因为删除了以后查找某个键,就不会往后找,直接就不存在了,所以我们就需要在删除时添加个占位符,表示曾经有过数据,但是被删除了。而且闭散列有的时候防止最坏情况的发生,需要辅助双散列或者重散列。个人还是选择开散吧。

散列法对数据的操作为O(1)~O(n)(最坏情况),假如选择适当的哈希函数和哈希表的大小基本上为O(1),实现集合/字典降低了操作的时间复杂度。

与另一种实现字典的数据结构平衡二叉搜索树相比:

  1. 时间复杂度的不同:哈希平均为O(1),最坏为O(n),而平衡二叉搜索树最好最坏都是O(logn)
  2. 有序性保留:哈希不保留key有序性,而后者保留,所以散列表不适合按序遍历和按范围查询的应用。

例题:

  • LeetCode 1.两数之和
    首先暴力枚举:O(n^2),排个序双指针遍历O(nlogn),关键效率不高的原因时,我需要在遍历给定数组的时候,比如遍历到第i个,知道可以和nums[i]一起组成target的那个数x,是否在我已经遍历到那些数字当中(肯定不会在没遍历到的数字当中),所以我们需要一个数据结构去存储刚才遍历到的那些数字,并且可以数据结构是通过键(这些数字)值来增删改查的,因此我们需要一个集合这样的数据结构,而这道题明显采用哈希表会更优。
#define MP make_pair
class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        unordered_map<int,int> hash_table;
        vector<int> ans;
        for(int i = 0;i < nums.size();++i){
            if(hash_table.count(target - nums[i])){
                return {i,hash_table[target - nums[i]]};
            }
            else 
                hash_table.insert(MP(nums[i],i));
        }
       return ans;
    }
};
  • LeetCode 217.存在重复元素
    首先暴力枚举:O(n^2),排个序遍历看是否和前一个元素相同O(nlogn),关键在于遍历到第i个数时,是否知道前面出现过这个数,一旦出现就说明有重复的,所以需要实现一个集合,很明显以哈希O(1)实现的在这里更快。而且此题也可以换个思路,那就是把所有nums的元素都push到那个哈希集合里,因为在cpp中unordered_set默认只能插入不存在的元素,所以到最后比下哈希集合和nums数组的大小就知道是否重复了。
class Solution {
public:
    bool containsDuplicate(vector<int>& nums) {
        if(nums.empty()) return false;
        unordered_set<int> hash_table;
        for(int num:nums){
           hash_table.insert(num);
        }
        return hash_table.size() < nums.size();
    }
};
  • LeetCode 594.最长和谐子序列
    首先要明确一点子序列是可以不连续的,而子串是要连续的,所以这道题我们可以不用考虑滑动窗口等方法。
    这道题的关键点:如何知道某个数,和它相同的数出现了多少次,比它大1的数出现了多少次(假如在所有数字都会遍历到的情况下,我们只需要知道比它大1或者小1的即可),所以我们需要实现了以数值为键,次数为值的字典来实现查询,很明显这里用哈希实现效率会比红黑树高。
class Solution {
public:
    int findLHS(vector<int>& nums) {
        unordered_map<int,int> hash_table;
        for(int num:nums){
            hash_table[num]++;
        }
        int longest = 0;
        for(pair<int,int> t:hash_table){
            if(hash_table.count(t.first + 1)){
                longest = max(longest,t.second + hash_table[t.first + 1]);
            }
        }
        return longest;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值