代码随想录算法训练营第6天
代码随想录算法训练营第6天 | 哈希表理论基础,242.有效的字母异位词,349. 两个数组的交集,第202题. 快乐数,1. 两数之和
一、刷题部分
1.1 哈希表理论基础
-
原文链接:代码随想录
-
题目链接:🈚️
-
首先 hash 表是根据元素的值来访问元素的。
-
hash 表可以快速地判断元素是否在其中,是一种用空间换时间的数据结构。
-
hash 函数结果可能相同,就是 hash 碰撞,可以用拉链法 或 线性探测法 来解决。
-
哈希相关算法可以使用以下 3 种结构来实现:
- 数组
- 集合 set
- 映射 map
集合 | 底层实现 | 是否有序 | 数值是否可以重复 | 能否更改数值 | 查询效率 | 增删效率 |
---|---|---|---|---|---|---|
std::set | 红黑树 | 有序 | 否 | 否 | O(log n) | O(log n) |
std::multiset | 红黑树 | 有序 | 是 | 否 | O(logn) | O(logn) |
std::unordered_set | 哈希表 | 无序 | 否 | 否 | O(1) | O(1) |
映射 | 底层实现 | 是否有序 | 数值是否可以重复 | 能否更改数值 | 查询效率 | 增删效率 |
---|---|---|---|---|---|---|
std::map | 红黑树 | key有序 | key不可重复 | key不可修改 | O(logn) | O(logn) |
std::multimap | 红黑树 | key有序 | key可重复 | key不可修改 | O(log n) | O(log n) |
std::unordered_map | 哈希表 | key无序 | key不可重复 | key不可修改 | O(1) | O(1) |
- 哈希算法优先使用下图左边的4种结构:
1.2 242.有效的字母异位词
- 原文链接:代码随想录
- 题目链接:242. 有效的字母异位词 - 力扣(LeetCode)
1.2.1 题目描述
给定两个字符串 s
和 t
,编写一个函数来判断 t
是否是 s
的 字母异位词。
字母异位词是通过重新排列不同单词或短语的字母而形成的单词或短语,并使用所有原字母一次。
示例 1:
输入: s = "anagram", t = "nagaram"
输出: true
示例 2:
输入: s = "rat", t = "car"
输出: false
提示:
1 <= s.length, t.length <= 5 * 104
s
和t
仅包含小写字母
进阶: 如果输入字符串包含 unicode 字符怎么办?你能否调整你的解法来应对这种情况?
1.2.2 初见想法
尽管哈希表的功能函数怎么写的还不会,但是不妨碍我来分析一下这题怎么结合哈希表来解。如果 s 用一个哈希表存,那么遍历一遍 t,让 t 里每个元素都判断是否在 s 中。这样的想法是否可行呢?看例子中会出现一个字串含相同字母的情况,那么这个算法就不太合适。
或者我可以做一个映射,维护一个新的哈希表,将 s 的每个元素依次填入,s 每次填入的时候既填字母本身又填出现的次数,如 abca 就填 a1 b1 c1 a2。同理遍历 t 的时候也用类似的方式做验证,这样就可以解决字母重复的问题了。
接下来就是学习一下哈希表的具体写法就可以开始实现我的想法了。
class Solution {
public:
bool isAnagram(string s, string t) {
if (s.size() != t.size()) {
return false;
}
//创建一个空表
unordered_set<string> mySet;
//创建一个26长度的集合,表示字母出现的次数
int letterCounts[26] = {0};
//边添加到集合边维护数组
for (int i = 0; i < s.size(); i++) {
int index = int(s[i] - 'a');
letterCounts[index]++;
string info = s[i] + to_string(letterCounts[index]);
mySet.insert(info);
//cout << "插入了" << info << endl;
}
//遍历并验证
bool result = true;
for (int i = 0; i < t.size(); i++) {
int index = int(t[i] - 'a');
string info = t[i] + to_string(letterCounts[index]);
//cout << "检索了" << info << endl;
if (mySet.find(info) == mySet.end()) {
//查不到直接返回 false
return false;
}
letterCounts[index]--;
}
return true;
}
};
试了一下使用 unordered_set 的方法,这个类有 insert(XXX) 和 find(XXX) 的方法,还是挺方便的。
1.2.3 看录后想法
大受震撼,为什么我没想到可以用这么简单的方法就做好,还是局限在哈希表里了。或者说这个题解用的方法就是一种广义的哈希表呢,字母与下标一一映射。
class Solution {
public:
bool isAnagram(string s, string t) {
if (s.size() != t.size()) {
return false;
}
//创建一个26长度的集合,表示字母出现的次数
int letterCounts[26] = {0};
//遍历s,统计各字母出现次数
for (int i = 0; i < s.size(); i++) {
int index = int(s[i] - 'a');
letterCounts[index]++;
}
//遍历t,将各字母的次数减掉
for (int i = 0; i < t.size(); i++) {
int index = int(t[i] - 'a');
letterCounts[index]--;
}
//遍历letterCounts,全0则匹配上了
bool result = true;
for (int i = 0; i < 26; i++) {
if (letterCounts[i] != 0) {
return false;
}
}
return true;
}
};
1.2.4 遇到的困难
本题思路较为清晰,暂未遇到困难。
1.3 349. 两个数组的交集
- 原文链接:代码随想录
- 题目链接:349. 两个数组的交集 - 力扣(LeetCode)
1.3.1 题目描述
给定两个数组 nums1
和 nums2
,返回它们的交集。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序 。
示例 1:
输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2]
示例 2:
输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出:[9,4]
解释:[4,9] 也是可通过的
提示:
1 <= nums1.length, nums2.length <= 1000
0 <= nums1[i], nums2[i] <= 1000
1.3.2 初见想法
暴力就是双循环,一下子就能想到,现在想想有没有更好的方法。如何用哈希表来应用到这里呢?就利用一下 stl 的 unordered_set 来实现吧。不过说实话感觉效率跟暴力差不多。
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
//开一个 unordered_set 来把 nums1 的数据存进去
unordered_set<int> mySet;
//无重存储结果
unordered_set<int> result;
for (int i = 0; i < nums1.size(); i++) {
mySet.insert(nums1[i]);
}
//遍历 nums2 看是否在 mySet 存在
for (int i = 0; i < nums2.size(); i++) {
if (mySet.find(nums2[i]) != mySet.end()) {
result.insert(nums2[i]);
}
}
vector<int> result_vec(result.begin(),result.end());
return result_vec;
}
};
1.3.3 看录后想法
对啊,为什么在定义 mySet 的时候没有想到直接赋值呢。STL库用的还是不熟练。遍历的写法也要学习一下。
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
//开一个 unordered_set 来把 nums1 的数据存进去
unordered_set<int> mySet(nums1.begin(), nums1.end());
//无重存储结果
unordered_set<int> result;
//遍历 nums2 看是否在 mySet 存在
for (int num2 : nums2) {
if (mySet.find(num2) != mySet.end()) {
result.insert(num2);
}
}
vector<int> result_vec(result.begin(),result.end());
return result_vec;
}
};
1.3.4 遇到的困难
主要还是STL的一些语法需要学习,这一块都没好好学过。
1.4 第202题. 快乐数
- 原文链接:代码随想录
- 题目链接:202. 快乐数 - 力扣(LeetCode)
1.4.1 题目描述
编写一个算法来判断一个数 n
是不是快乐数。
「快乐数」 定义为:
- 对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
- 然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
- 如果这个过程 结果为 1,那么这个数就是快乐数。
如果 n
是 快乐数 就返回 true
;不是,则返回 false
。
示例 1:
输入:n = 19
输出:true
解释:
12 + 92 = 82
82 + 22 = 68
62 + 82 = 100
12 + 02 + 02 = 1
示例 2:
输入:n = 2
输出:false
提示:
1 <= n <= 231 - 1
1.4.2 初见想法
这题还可以和哈希表联系上吗?仔细分析一下。一个数可能会无限循环下去,我怎么知道它到底会不会无限循环呢,只需要知道这个数字之前有没有出现过就好。那确实用一个set就比较方便地解决了。
class Solution {
public:
bool isHappy(int n) {
unordered_set<int> appearedNumbers;
while (appearedNumbers.find(n) == appearedNumbers.end()) {
if(n == 1) {
return true;
}
appearedNumbers.insert(n);
n = findHappy(n);
}
return false;
}
int findHappy (int num) {
int ans = 0;
while (num != 0) {
ans += (num % 10) * (num % 10);
num /= 10;
}
return ans;
}
};
1.4.3 看录后想法
录的解法和我几乎一样。不多说了。
1.4.4 遇到的困难
🈚️
1.5 1. 两数之和
- 原文链接:代码随想录
- 题目链接:1. 两数之和 - 力扣(LeetCode)
1.5.1 题目描述
给定一个整数数组 nums
和一个整数目标值 target
,请你在该数组中找出 和为目标值 target
的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案,并且你不能使用两次相同的元素。
你可以按任意顺序返回答案。
示例 1:
输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。
示例 2:
输入:nums = [3,2,4], target = 6
输出:[1,2]
示例 3:
输入:nums = [3,3], target = 6
输出:[0,1]
提示:
2 <= nums.length <= 104
-109 <= nums[i] <= 109
-109 <= target <= 109
- 只会存在一个有效答案
进阶:你可以想出一个时间复杂度小于 O(n2)
的算法吗?
1.5.2 初见想法
这题怎么也能和哈希表联系上了???让我好好想想。
可以用一个 set 用来存(每个数与 target 的差值:下标)。对数组中的每一个数,先判断在不在 set 里,如果在则找到其下标然后和自己的下标组成答案;如果不在则将(与 target 的差值 : 下标)存进去。代码如下:
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
unordered_map<int, int> myMap;
for (int i = 0; i < nums.size(); i++) {
//在map里说明找到答案了
if (myMap.find(nums[i]) != myMap.end()) {
return {myMap[nums[i]],i};
}
//差值添加到map里
//cout << "添加{" << target - nums[i] << "," << i << "}到map里。" << endl;
myMap.insert({target - nums[i], i});
}
return {-1};
}
};
1.5.3 看录后想法
与我思路基本一样,就是map里直接存值,然后每次用差值去map里找,和我的方法是等价的。
1.5.4 遇到的困难
🈚️
二、总结与回顾
感觉开始用到C++的STL库里的东西了。这一块我想找个时间好好学一下。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性