随想录训练营5/60 | 哈希表理论基础;LC 242.有效的字母异位词;LC 349. 两个数组的交集 ;LC 202. 快乐数 ;LC 1. 两数之和

哈希表理论基础

哈希碰撞的解决办法:线性探测法;拉链法。
常见的三种哈希结构:数组、集合(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;
		}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值