代码随想录day6|哈希表理论基础、242有效的字母异位词 、249.两个数组的交集、202 快乐数、1.两数之和

哈希表理论基础

杂货店商品价格问题,就可以引出哈希表

哈希表

通过哈希函数将商品名映射到哈希表上,得到一个整数,这个整数就是哈希表上对应商品的索引,我们可以把商品价格存放到这个索引里面.

我们只需要将商品名给哈希函数,哈希函数得出商品索引,再在哈希表上搜索这个索引,就可以得到这个商品的价格

哈希函数

BV1qR4y1V7g6

  1. 功能:输入数据,生成整数

  2. 特点:

    1. 不同的输入,输出不同的整数

    2. 相同的输入,输出相同的整数

不过呢,事实上,任何算法都不能保证,不同的输入一定是输出不同的整数

如果出现不同输入,输出同样的整数,这样称为冲突(collision)

SHA-256 MD5成熟的哈希算法

常见的三种哈希结构

  • 数组

  • set (集合)

  • map(映射)

set(集合)

特点

不存储重复元素(自动去重) ✅ 默认按小到大排序(std::set,基于红黑树)无序哈希表实现(std::unordered_set常见操作:插入、查找、删除

map(映射/字典)

特点

存储 key-value(键值对)键(key)唯一,不能重复默认按 key 升序排序(std::map,基于红黑树)无序哈希表实现(std::unordered_map常见操作:插入、查找、删除、更新

上面是通俗的讲了set和map,下面讲讲具体的数组-set-map构成的哈希表

和一般数组、set、map不一样噢

数组构成的哈希表

  • value 本身就是我要的值

  • value 输入哈希函数,得到一个整数(索引),然后将 value 存入该索引对应的数组位置。

  • 查找时,同样用 value 计算哈希值,找到索引,取出 value

  • 适用于 key 取值范围小的情况,查找和插入 O(1)

  • 如果 key 过大,数组会很浪费空间

unordered_set(基于哈希表的集合)

  • value 送入哈希函数,得到一个索引,然后将 value 存入这个索引对应的位置。

  • 查找时,value 再次计算哈希值,找到索引,即可判断是否存在

  • unordered_set 不保证元素的顺序,它的排列由哈希函数决定,并不会按照从小到大排序

  • unordered_set 中也不允许有重复的元素。插入重复的元素时,容器会忽略这一操作。

  • 查找/插入的平均时间复杂度 O(1)

  • 如果哈希冲突严重,最坏情况下查找/插入变成 O(n)

🚨 修正点:unordered_set 存储顺序是 由哈希函数决定的,不是按从小到大排列的。 如果你要有序集合,应该用 set(基于红黑树)。

unordered_map(基于哈希表的键值对存储)

  • key 送入哈希函数,得到一个索引,然后将 key -> value 对存入该索引对应的位置。

  • 查找时,key 再次计算哈希值,找到索引,取出 value

  • unordered_map 不保证 key 的存储顺序,顺序由哈希函数决定

  • 查找/插入的平均时间复杂度 O(1)

  • 如果哈希冲突严重,最坏情况下查找/插入变成 O(n)

242.有效的字母异位词


class Solution { public: bool isAnagram(string s, string t) { int result[26]={0}; for(int i=0;i<s.size();i++) { result[s[i]-'a']++; } for(int j=0;j<t.size();j++) { result[t[j]-'a']--; } for(int a=0;a<26;a++) { if(result[a]!=0) { return false; } } return true; } };

错误

第二个表写错字母了

核心思想

result[26] 理解为一种非常简单的哈希表,用于记录每个字母出现的次数。这个数组模拟了哈希表的功能,但它没有使用哈希函数,而是通过字符的 ASCII 值直接映射到数组索引。

或者说这里的哈希函数是s[i] - 'a'将字母映射进哈希表 result[26]

249.两个数组的交集

输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序 。->用set哈希

错误

  1. 为什么int it = cross.find(nums2[j]);是错的

cross.find(nums2[j]) 返回的是一个 迭代器,而不是一个整数。

应该使用一个 迭代器类型 来接收 find 返回的结果。auto it = cross.find(nums2[j]);

  1. result.insert(nums2[j]); 是错的

unordered_set 使用 insert 添加元素,但 vector 并没有 insert 方法来添加单个元素(它的 insert 方法是用于在指定位置插入多个元素)。你应该使用 push_back 来将元素加入 vector


class Solution { public: vector<int> intersection(vector<int>& nums1, vector<int>& nums2) { std::unordered_set<int> cross; vector<int> result; for(int i=0;i<nums1.size();i++) { cross.insert(nums1[i]); } for(int j=0;j<nums2.size();j++) { auto it = cross.find(nums2[j]); if(it!=cross.end()) { result.push_back(nums2[j]); cross.erase(it); } } return result; } };

erase的使用

cross.erase(it);删除it指向的数据,因为我们已经将数据送入result了,如果不删除,那么下一次遇到还会往result中送入一次,就会错误.

使用哈希表的优势

利用 unordered_set 存储一个数组的元素,并通过哈希表快速查找另一个数组的元素是否存在于第一个数组中。 unordered_set它在查找、插入和删除操作上具有平均常数时间复杂度 O(1),而在传统的数组或列表中,查找某个元素需要线性扫描,时间复杂度是 O(n)。

202.快乐数

思路

如果不是快乐数,求和的过程中,sum会重复出现

所以这道题目使用哈希法,来判断这个sum是否重复出现,如果重复了就是return false, 否则一直找到sum为1为止。

还有一个难点就是求和的过程,如果对取数值各个位上的单数操作不熟悉的话,做这道题也会比较艰难。


// 取数值各个位上的单数之和 int getSum(int n) { int sum = 0; while (n) { sum += (n % 10) * (n % 10); n /= 10; } return sum; }

代码


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) { std::unordered_set<int> sumlist; while(1) { n=getsum(n); auto it = sumlist.find(n); if(it==sumlist.end()) { sumlist.insert(n); } else if(n==1) return true; else return false; } } };

错误

对于unordered_set的正确使用:

erase 使用不当,会导致错误。

unordered_set 中,cross.erase(it) 删除的是 it 迭代器所指向的元素。

具体来说,it 是通过 find 查找得到的一个迭代器,指向 unordered_set 中一个已经存在的元素。erase(it) 调用后,会从 unordered_set 容器中删除这个元素。

1.两数之和

暴力解法


class Solution { public: vector<int> twoSum(vector<int>& nums, int target) { int v=0; int size1 = nums.size(); vector<int> result; for(int i=0;i<size1;i++){ v=target-nums[i]; for(int j=i+1;j<size1;j++) { if(nums[j]==v){ result.push_back(i); result.push_back(j); return result; } } } return result; } };

使用哈希表(什么时候用哈希法)

什么时候使用哈希法,当我们需要查询一个元素是否出现过,或者一个元素是否在集合里的时候,就要第一时间想到哈希法。

本题呢,我就需要一个集合来存放我们遍历过的元素,然后在遍历数组的时候去询问这个集合,某元素是否遍历过,也就是 是否出现在这个集合。

因为本题,我们不仅要知道元素有没有遍历过,还要知道这个元素对应的下标,需要使用 key value结构来存放,key来存元素,value来存下标,那么使用map正合适。


class Solution { public: vector<int> twoSum(vector<int>& nums, int target) { std::unordered_map<int,int> mymap; for(int i=0;i<nums.size();i++) { auto it = mymap.find(target-nums[i]); { if(it!=mymap.end()) { return {it->second, i}; } else mymap.insert(pair<int,int>(nums[i],i)); } } return {}; } };

学到的

  1. map因为有键值对,所以建立的时候是unordered_map<int,int> mymap;

  2. 同样是因为有键值对,所以插入是mymap.insert(pair<int,int>(nums[i],i));

  3. 键值对中,谁作为键谁作为值是根据要解决的问题来的,并不是索引一定要作为键,而数据作为值

    1. 具体而言,本文我们是查找值,返回索引

    2. 值是用来被查找的内容,所以键应该是数据,而值反而应该是索引

这里我们知道,索引可以是数组的下标

但是索引广义的意思是用来寻找的标志,当我们需要寻找值来找索引时,索引和值的定位就反过来了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值