哈希表理论基础
哈希碰撞的解决办法:线性探测法;拉链法。
常见的三种哈希结构:数组、集合(set)、映射(map)
集合(set) | 底层实现 | 是否有序 | 数值能否重复 | 能否更改数值 |
---|---|---|---|---|
std::set | 红黑数 | 是 | 否 | 否 |
std::multiset | 红黑树 | 是 | 能 | 否 |
std::unordered_set | 哈希表 | 否 | 否 | 否 |
红黑树是平衡搜索二叉树,因此是有序的,若要改变数值,则要改变二叉树的结构,因此不能改变数值。哈希表的哈希函数对于不同的值会映射不同的位置,因此也不支持更改数值。
映射(map) | 底层实现 | key是否有序 | key能否重复 | 能否更改key |
---|---|---|---|---|
std::map | 红黑树 | 是 | 否 | 否 |
std::multimap | 红黑树 | 是 | 能 | 否 |
std::unordered_map | 哈希表 | 否 | 否 | 否 |
map中有key和val,对key有限制,对val没有限制。val = hashfunction(key)
对于查找和增删的效率,哈希表比红黑树更优秀。但若要求集合是有序的则用set或者map;若要求集合是有序且可重复的则用multiset或者multimap。
总结:需要判断元素是否出现在集合中,考虑用哈希法(牺牲空间换时间)
LC 242.有效的字母异位词
题目链接:LC 242.有效的字母异位词
思路:用哈希法,数组就能解决问题,因为题干提示仅包含小写字母,因此可大小为26的int数组作为哈希表,数组下标为key,数组内的值为val。
先遍历s,用数组统计每个字出现的次数;再遍历t,每个字母出现时令数组对应的值减一。最后遍历数组,若都为0则为字母异位词。
代码:
class Solution {
public:
bool isAnagram(string s, string t) {
// int hash[26] = {0}; //创建数组,每个值初始化为0
vector<int> hash(26);//创建vector,自动将26个int赋值为0
for(char i : s){
++hash[i-'a'];
}
for(char j : t){
--hash[j-'a'];
if(hash[j-'a']<0)return false;
}
for(int k : hash){
if(k != 0)return false;
}
return true;
}
};
LC 349. 两个数组的交集
题目链接:LC 349. 两个数组的交集
思路:数组中的数没有范围因此不能用数组作为哈希表。元素没有重复的因此用set,或者unordered_set(map也可以,但能用set就不用map,map消耗更多的资源)。不考虑顺序就用unordered_set。
代码:
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
unordered_set<int> result;//因为结果要去重,所以先用set保存结果去重
unordered_set<int> hash(nums1.begin(), nums1.end());//将nums1变为set
for(int i : nums2){
if(hash.find(i) != hash.end()){//若在hash中找到i,则将i插入result中
result.insert(i);
}
}
return vector<int>(result.begin(),result.end());
}
};
LC 202. 快乐数
题目链接:LC 202. 快乐数
思路:每轮按着规则计算得到结果,判断结果之前是否出现过,若出现过则返回false;若为1则返回true;若未出现过且不为1则将该数加入哈希表中。该题还有一个难点是将数按位进行拆分并计算平方和。
代码:
class Solution {
public:
int calcu(int n){
int result = 0;
while(n){
int temp = n%10;
result = result + temp*temp;
n = n/10;
}
return result;
}
bool isHappy(int n) {
unordered_set<int> hash;
while(1){
if(n==1)return true;
if(hash.find(n)!=hash.end())return false;//若找到n,就返回false
else{
hash.insert(n);//若没找到则将其放入哈希表中
}
n = calcu(n);
}
}
};
LC 1. 两数之和
题目链接:LC 1. 两数之和
思路:由于返回的是数组下标,所以哈希表用map,map的key为数组值,val为数组下标,需要通过数组值来寻找符合的另一个数。遍历数组,若map中含有目标值-遍历值,则直接返回两数下标,若map中不含目标值-遍历值,则将遍历值放入map中。
注:若数组的值相同时,unordered_map只保存之前的,后面加入的不保存。(自动去重)
代码:
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
unordered_map<int, int> hash;//创建map
for(int i=0; i<nums.size(); i++){//遍历数组
int temp = target - nums[i];//找map中是否有temp
auto te = hash.find(temp);
if(te != hash.end()){//若有,则直接返回下标
return vector<int> {i, te->second};
}
else{//若没有,把遍历到的值放入map中
pair<int, int> p = {nums[i],i};
hash.insert(p);
}
}
return vector<int> {};
}
};
------------------------------------额外题目---------------------------------
(以上为训练营每日任务,本部分为自己刷题进度)
二叉树的迭代遍历
递归本质上是将函数的局部变量、参数值、返回地址等压入栈中,递归返回的时候从栈顶弹出本次递归的各个参数到上次递归中。因此,也可直接用栈进行迭代遍历。
前序遍历:先处理中间节点,然后是左子树、右子树。那么可以让根节点先入栈,再将栈顶元素弹出,然后让弹出的栈顶元素的右子树和左子树分别入栈。下次迭代同样,将栈顶元素弹出(根节点的左子树节点)…若弹出的元素没有子树,则只需弹出即可。
代码
class Solution{
public:
vector<int> traversal(TreeNode* root){
stack<TreeNode*> st;//创建栈,里面保存的是节点的地址
vector<int> result;//遍历的结果
if(root==nullptr)return result;//若为空树,直接返回
st.push(root);//不是空树,将根节点放入栈中
//因为需要先弹出栈顶元素,对栈顶元素的左右子树进行处理,所以需要根节点先入栈
while(!st.empty()){//当栈st非空的时候,一直进行以下操作
TreeNode* temp = st.top();//保存栈顶元素,为了让栈顶元素的子树可以入栈,若先pop,则找不到栈顶元素了
st.pop();
result.push_back(temp->val);//读取栈顶元素的值(先中间)
if(temp->right!=nullptr)st.push(temp->right);//再右
if(temp->left!=nullptr)st.push(temp->left);//最后左。这样从栈中弹出的时候就是先左后右
}
return result;
}
}
中序遍历,是有些困难的。因为从根节点遍历的时候,是从上往下遍历的,然而不是先弹出栈顶元素,而是一直遍历节点左子树,直到节点没有左子树后弹出栈顶元素,弹出后看有没有右子树,若有则将右子树进栈,重复上述过程(一直遍历左子树…);若没有右子树弹出栈顶元素。
代码
class Solution{
public:
vector<int> traversal(TreeNode* root){
stack<TreeNode*> st;//栈初始化
vector<int> result;//结果初始化
TreeNode* cur = root;//用cur来保存该处理的节点,因为处理顺序和遍历顺序不一样
while(cur!=nullptr || !st.empty()){//当cur为空且st为空时跳出循环(树的最右空节点)
if(cur!=nullptr){
st.push(cur);
cur = cur->left;
}
else{
cur = st.top();
st.pop()
result.push_back(cur->val);
cur = cur->right;//当右子树为空时,直接处理栈顶元素
}
}
return result;
}
}
后序遍历:和前序遍历相似,差别为入栈时先左子树再右子树,得到的result需要反转才能返回。(先序是中左右,先中右左,再反转为左右中,即为后序遍历)
代码
class solution{
public:
vector<int> traversal(TreeNode* root){
stack<TreeNode*> st;
vector<int> result;
if(root==nullptr)return result;
st.push(root);
while(!st.empty()){
TreeNode* temp = st.top();
st.pop();
result.push_back(temp->val);
st.push(temp->left);
st.push(temp->right);
}
reverse(result.begin(),result.end());
return result;
}
}