主要是哈希表了
数组就是一张哈希表
什么时候考虑哈希法:当我们遇到要快速判断一个元素是否在集合里
哈希函数: 把学生姓名直接映射到集合上的索引
那么如果学生的数量大于哈希表的大小怎么办?
那么就是哈希碰撞:其实就是两个元素都映射到了同一位置
那么有两种解决方法:拉链法和线性探测法
拉链法:就是储存在链表里
线性探测法:首先一定要保证 你的哈希表的大小大于要放的数据 然后就是假设放满了,循环找到下一个空位去放元素
哈希结构:
那么如果你想用哈希法来解决问题的话,有以下几种哈希结构:
1 数组
2 set(集合)
3 map(映射)
首先数组没什么值得讨论的
那么c++中set有三种
1std::set 这种的底层实现是红黑树
红黑树就是一种自平衡的二叉树 那么这个树呢 有四个特点:
1 根结点是黑色
2 不能相邻两个结点是红色结点 也就是红色结点的叶子结点一定是黑色结点
3 最下面的结点一定是空的黑色叶子结点
4 每一个结点到其能到达的叶子结点的路径上都必须包含相同数量的黑色结点
因此这个set肯定是不能修改数据 因为你一修改就破坏平衡了 而且是有序的 你可以用中序遍历 时间复杂度是o(log2n)
那么其实红黑树是可以有相同元素的 其实std::set在实现的时候 先用了一个std::less的查找 确保没有相同元素再插入, 其他都和std::set一样 那么这两个肯定都是有序的 然后增删的效率都是O(log2n) 然后查找的效率也是O(log2n) 然后都不能更改数值
具体为什么是O(log n)呢 因为树是下一层是上一层的两倍,因此树的高度是log2n 因此你每次查找或者去增加 最多只是去遍历树的高度就行 因此最终的效率就是O(log下标2 n)。
那么还有一种结构是std::unordered_set 也就是哈希表, 底层实现是拉链法,首先肯定是无序的这个因为:哈希表的意义就是让元素均匀分配 你如果是想通过哈希函数来进行排序 那么就很有可能变成一个链表 那么哈希表就完全没意义了 因此你要想有序就直接用std::set来 红黑树更好 哈希表的优势就是随机访问 也就是增删和查询效率都为O(1)
另外 哈希表不能去更改元素的值 因为元素的位置是由其值的哈希决定的。因此,如果我们更改元素的值,就可能会改变它的哈希值,进而需要更改其在哈希表中的位置。 换句话说 你改元素的值 那么元素的哈希值就会改变 那么位置就会变 那么就乱了 因为没有重新定位的方法 所以你只能删了再增加
另外为什么没用重复元素呢 因为如果有重复元素就是一个格子去弄很多链表 也是一样的道理 那样效率就低很多了 所以哈希表一个是不能去有重复元素 一个是不能有顺序 一个是不能更改数值。
总结: 在做哈希表的题的时候 优先考虑std::unordered_set 如果是元素要求有序 那么再考虑std::set 如果元素有序又要重复 那么只能选择std::multiset 。 其实这两个的查询和增加删的效率都是O(log下标2n) 但哈希表的效率是O(1)
那么另外一个是hashmap:
hashmap和hashset的具体区别 那么等候捷的c++看完再说
题1 有效的字母异位词
其实还是很简单 但是一开始想的不对 一开始想的是去用multiset 但其实不是最优解 因为这个用数组就能搞定 所以一共有三种结构 数组 集合 和map 先想到的肯定是数组
注意来说 你在定义的时候 写的数组个数 就是从1开始数的 比如说 int[10] 就是10个数
class Solution {
public:
bool isAnagram(string s, string t) {
int a [26]={0};
for(int i=0;s[i]!='\0';i++){
a[s[i]-'a']++;
}
for(int i=0;t[i]!='\0';i++){
a[t[i]-'a']--;
}
for(int i=0;i<26;i++){
if(a[i]!=0)
return false;
}
return true;
}
};
还是说注意以下几个问题
1 就是c++中也可以把字符串直接当数组 但是你要注意结尾为'\0' 不是'0'。当然你也可以直接用(i<s.size()) 用size也行
2 还有你要注意一个点就是 你在定义数组 int i[10] 此时是定义了10个元素的数组 是从下标1开始数的
3 而且你要注意数组的初始化 你用int a[10] 此时数组的元素可能是任何值 它不会帮你初始化为0的
第二个题 其实实现很简单
主要是 容器用迭代器这个东西有点烦躁 迭代器其实很简单 就是xxx.begin() 和xxx.end()这两个东西
然后容器可以用xxx.find()来找到元素 找不到就会返回xxx.end()
另外还有一个定义set中的哈希表的:unordered_set <int> numsset;
那么有以下几种方式 来定义
1 直接定义一个空的set:unordered_set <int> numsset;
2 用花括号来去指定:unordered_set <int> numsset ={1,2};
3 如果你有另一个容器,那么可以去用迭代器来去定义:unordered_set <int> numsset (xxx. begin(),xxx.end());
4另外这个 unordered_set 不是数组 只能用xxx.find() 或者是 xxx.insert() 不能用xxx[]
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
unordered_set <int> result;
unordered_set <int> nums(nums1.begin(),nums1.end());
for(int i=0;i<nums2.size();i++){
if(nums.find(nums2[i])!=nums.end()){
result.insert(nums2[i]);
}
}
return vector<int> (result.begin(),result.end());
}
};
具体代码如上 还是有几个问题
1 为什么要用unordered_set呢 因为 你必须确保元素唯一 必须用集合
第三题 快乐数 是新题
那么这道题的关键就是看sum会不会重复出现 如果重复出现了就是false 不然就是true、
还有一个问题就是你这个sum怎么判断是不是重复出现呢 那么就要用到哈希表了 因为是不会有重复元素 并且是查找和记录用的 你肯定不能用一个数组 那样每次查找的时候
还有你要注意虽然数组是一种哈希表 但是查找的时间复杂度是O(n) 但是哈希表的查找时间复杂度是O(1) 因为采用了映射机制 把值映射到了下标 因此每次可以直接索引到下标
还有一个问题就是你要对于数值的各个单位的操作比较熟练才行
有一个小问题就是 你一记得更新sum的值
class Solution {
public:
int getsum (int sum) {
int tem=0;
while(sum!=0){
tem =tem+(sum%10)*(sum%10);
sum=sum/10;
}
return tem;
}
bool isHappy(int n) {
unordered_set <int> sumsum;
int sum=n;
while(getsum(sum)!=1){
if(sumsum.find(sum)!=sumsum.end()){
return false;
}
else{
sumsum.insert(sum);
sum=getsum(sum);
}
}
return true;
}
};
第四题 依旧很简单 这道题就用到了map 因为你不仅要值还要下标 那么map中的key用来存值 value用来存下标 正合适 而set中只有一个key 也就是只有一个值(因为一个键对应一个值)
所以 map中的存储结构为 {key:数据元素,value:数组元素对应的下标}。
因为key是对应的值,value此时就对应了下标。就正好是英文单词反过来的意思。
那么还是要注意几个点
1迭代器 iter 你如果用迭代器去指的话, 类型首先要是std::unordered_map<int, int>::iterator
2
map.find(target - nums[i])
这个的返回类型就是迭代器。 迭代器就是指向元素的指针,另外可以用iter->first去访问值(因为->会指到底) 用iter->second 去访问下标,而元素本身是pair类型的。
return vector<int>{iter->second, i}; 也可以显式的给出来 但是还是要用花括号去初始化。
3注意注意注意! 犯了很多次了 即使你用花括号 后面也得用分号
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 vector <int> {iter->second,i};
}
else{
map.insert({nums[i],i});
}
}
return {};
}
};