242. 有效的字母异位词
题目链接:click here
文章讲解:click here
题目描述:给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。
示例 1: 输入: s = “anagram”, t = “nagaram” 输出: true
示例 2: 输入: s = “rat”, t = “car” 输出: false
说明: 你可以假设字符串只包含小写字母。思路:
不仅要求字母相同,还要求相同字母出现的次数完全相同。
可以把 s 以 {键,值} = {元素,次数} 的形式,存在 unordered_map 里,
然后遍历 t,检测 t 内元素是否在 unordered_map 中存在,不存在直接报错。
存在则将对应 ‘值’ 减一。
最后遍历 unordered_map 的所有元素,值全为0,则输出 true, 反之 false。讲解思路:
大同小异,使用了 数组 来完成 ‘值’ 的初始化,以及限制总体内存开销,并减少 hash function 的计算。
事实上,可以把 ‘char’ - ‘a’ 视为自定义 hash function。
在已经数据规模的条件下,判断有限总数的元素集合中某个元素是否曾出现过,可以使用数组来优化内存。代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44class Solution
{
public:
bool isAnagram(string s, string t)
{
std::unordered_map<char, int> um;
for (auto &c_s : s)
{
if (um.find(c_s) == um.end())
{
um[c_s] = 1;
}
else
{
um[c_s]++;
}
}
for (auto &c_t : t)
{
if (um.find(c_t) == um.end())
{
return false;
}
else
{
um[c_t]--;
if (um[c_t] < 0)
{
return false;
}
}
}
for (auto item : um)
{
if (item.second != 0)
{
return false;
}
}
return true;
}
};讲解代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21class Solution {
public:
bool isAnagram(string s, string t) {
int record[26] = {0};
for (int i = 0; i < s.size(); i++) {
// 并不需要记住字符a的ASCII,只要求出一个相对数值就可以了
record[s[i] - 'a']++;
}
for (int i = 0; i < t.size(); i++) {
record[t[i] - 'a']--;
}
for (int i = 0; i < 26; i++) {
if (record[i] != 0) {
// record数组如果有的元素不为零0,说明字符串s和t 一定是谁多了字符或者谁少了字符。
return false;
}
}
// record数组所有元素都为零0,说明字符串s和t是字母异位词
return true;
}
};时空复杂度分析:
完整遍历两个string:O(n) 时间复杂度
使用unordered_map存储:O(n) 空间复杂度
使用数组存储:O(1) 空间复杂度
349. 两个数组的交集
题目链接:click here
文章讲解:click here
题目描述:给定两个数组,编写一个函数来计算它们的交集。
思路:
所谓求交,就是求大家都有的部分。
先把数组1转到set1里去重,再遍历数组2,查找元素是否存于set1中,若是,则转存到set_res里。
这里用了两个set, set1是为了给数组1去重,set_res是为了给结果去重,本质上也是对数组2去重。讲解思路:
对结果没有顺序要求,从底层实现出发,可以选用 unordered_set 代替 set,二者的底层分别是 哈希表和红黑树,显然前者的插入和搜索效率更高。
set 可以很方便地从 vector 生成,调用 set 的构造函数,传入 vector 的起始、终止迭代器即可;同理,vector也是,这些内置的数据结构彼此之间转换和拷贝数据都可以这样进行。代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19class Solution
{
public:
vector<int> intersection(vector<int> &nums1, vector<int> &nums2)
{
std::unordered_set<int> set_res;
std::unordered_set<int> set_1(nums1.begin(), nums1.end());
for (auto &num : nums2)
{
if (set_1.find(num) != set_1.end())
{
set_res.insert(num);
}
}
return std::vector<int>(set_res.begin(), set_res.end());
}
};时空复杂度分析:
遍历数组2,将数组1转为 u_set,将 u_set 转为结果输出:O(n+m) 时间复杂度
使用unordered_set存储数组1和结果:O(n) 空间复杂度
202. 快乐数
题目链接:click here
文章讲解:click here
题目描述:编写一个算法来判断一个数 n 是不是快乐数。
「快乐数」定义为:对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和,然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。如果 可以变为 1,那么这个数就是快乐数。
如果 n 是快乐数就返回 True ;不是,则返回 False 。思路:
我一开始很好奇这个循环过程中,一直不变成1,该如何退出循环。
讲解提供了关键点“结果是无限循环的”,解题方法就清楚了。
首先需要会通过对正整数模10除10法提取各个位置上的数字。然后累加其平方和,计算完成后,
检查最终结果是否为1,是,则返回 True, 反之则检查结果是否已经存在于 unordered_set 里,若存在,说明循环,返回 False,
若不存在,将结果存入 unordered_set 里,以备后续检索重复出现的历史值。
因为这里无需其有序,只是为了搜索历史值是否重复出现,所以选用具有降重功能最轻便的 unordered_set。
- 代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35class Solution
{
public:
bool isHappy(int n)
{
std::unordered_set<int> res_;
int temp = n;
while (true)
{
int res = 0;
while (temp > 0)
{
int factor = temp % 10;
res += factor * factor;
temp /= 10;
}
if (res == 1)
{
return true;
}
if (res_.find(res) == res_.end())
{
res_.insert(res);
}
else
{
return false;
}
temp = res;
}
}
}; - 时间复杂度:O(logn)
- 空间复杂度:O(logn)
- 这里还不是很理解为什么?
- 数字 n 的位数大约是 log10(n) !,所以会做log10(n)次乘法
而快乐数的迭代是有限的,要不然是快乐数,退出,要不然陷入循环然后主动退出,所以while的次数是有上限的,进而时间复杂度就是内部的乘法次数,即O(logn)。
另外,就空间而言,由于上述过程有限,所以 res_ 需要存储的结果也是有限的,不会高达O(n)量级,所以空间复杂度就是O(logn)这个量级。
1. 两数之和
题目链接:click here
文章讲解:click here
题目描述:给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。思路:
前向遍历数组,将遍历过的数及其下标存储在 能快速搜索的 哈希表里,当前遍历的结果通过 哈希表 快速查找想要的历史值(这里的目的是当前值+某个历史值 = target,所以查找时用 target-当前值 来作为键,能找到,二者相加即为target)。
若能找到,则可输出结果。有点像SLAM里存储历史关键帧的描述子,构建词袋,然后实时帧的描述子 在词袋中进行检索,能检索到相似的,就构建一帧候选回环。
这里面描述子向量若能通过合理的 hash function 转为 hash key,存 hash value(一般是对应帧点云、Pose的指针),则后续的描述子就能以O(1)的速度查找词袋中是否有相似描述子。相关工作见:BTC: A Binary and Triangle Combined Descriptor for 3D Place Recognition.代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22class Solution
{
public:
vector<int> twoSum(vector<int> &nums, int target)
{
std::unordered_map<int, int> um;
int index = 0;
for (auto &num : nums)
{
if (um.find(target - num) == um.end())
{
um.insert({num, index++});
}
else
{
return std::vector<int>({index, um[target - num]});
}
}
return std::vector<int>();
}
};时间复杂度:O(n) 遍历一遍数组
空间复杂度:O(n) 存储一遍数组
今日收获
个人博客更新。若Blog有误或是有其他想交流的,左侧有联系方式。
复习:
今日核心思想:当我们需要查询一个元素是否出现过,或者一个元素是否在集合里的时候,就要第一时间想到哈希法。
有效的字母异位词:规模有限的比对问题,可以先用有限大小的数组保存其中一个词条的字频,再遍历另一个词条核对字频
字频一致则比对成功。
两个数组的交集:求交问题,需要利用 u_set 做去重,另外还掌握了 std 容器之间相互转换的构造方法,传入旧容器的起止迭代器即可。
快乐数:核心的是理解题意中,若不快乐,会无限循环。意味着,识别循环可以用u_set(哈希表)。
两数之和:技巧题,利用 哈希表 来存储、查找历史值,利用哈希表,将当前值与历史值快速比对是否 match,本题 match 的 准则是 target - now = history,以后可能会遇到变种。耗时:2h