这4道题的前3道主要让人了解了set和map的各项基本用法,题目本身的方法方面没有难度。第4题低于O(n²)的方法还是不容易想到的,这次难得自己想到,不过具体实现上相比题解还要很大优化空间。
第1题(242.有效的字母异位词)是典型的哈希表问题,但又不完全是。做题过程主要学习了解了unordered_map的初始化、增改元素、遍历的方法,顺利通过了。
#include<unordered_map>
class Solution {
public:
bool isAnagram(string s, string t) {
if (s.size() != t.size()) {
return false;
}
unordered_map<char, int> nums;
for (int i = 0; i < s.size(); ++i) {
if (nums.find(s[i]) == nums.end()) {
nums[s[i]] = 1;
}
else {
nums[s[i]]++;
}
}
for (int i = 0; i < t.size(); ++i) {
if (nums.find(t[i]) == nums.end()) {
return false;
}
else {
nums[t[i]]--;
if(nums[t[i]] < 0) {
return false;
}
}
}
for (auto it = nums.begin(); it != nums.end(); it++) {
if (it->second > 0) {
return false;
}
}
return true;
}
};
然而,说是通过了却又没真正地通过。看了题解代码才意识到为什么时间、空间占用排名如此靠后,原来这道题目并不需要unordered_map这种数据结构。因为key只可能26个字母,且他们有顺序,那么只需要长度为26的int的数组即可。
public:
bool isAnagram(string s, string t) {
if (s.size() != t.size()) {
return false;
}
int nums[26] = {0}; // 只有在初始化为0时可以用这种写法
for (int i = 0; i < s.size(); ++i) {
nums[s[i] - 'a']++;
}
for (int i = 0; i < t.size(); ++i) {
nums[t[i] - 'a']--;
}
for (int i = 0; i < 26; ++i) {
if (nums[i] != 0) {
return false;
}
}
return true;
}
};
在实现过程中也学习到int数组的初始化只有当初始化为0时才可以写为int a[n] = {0},初始化目标值为其他时都不能用这种写法,否则只会使a[0]初始化成功。一般可以使用memset(a, x, sizeof(a)),其中x为目标初始化值。memset()的头文件为<cstring>。
二刷:忘记map的遍历方式。
第2题(349. 两个数组的交集)比较简单,过程中主要熟悉了unordered_set的基本用法和遍历方法。然而这个简单题还是没有一次性AC,因为起初在遍历nums2时直接将在集合中存在的数字添加进了返回值vector,导致vector存放了重复元素。更改为将交集结果存入新的unordered_set后在对其遍历并取值存入vector后正确。
#include<unordered_set>
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
unordered_set<int> nums, ansSet;
for (int i = 0; i < nums1.size(); ++i) {
nums.insert(nums1[i]);
}
vector<int> ansVec;
for (int i = 0; i < nums2.size(); ++i) {
if (nums.find(nums2[i]) != nums.end()) {
ansSet.insert(nums2[i]);
}
}
for (auto it = ansSet.begin(); it != ansSet.end(); ++it) {
ansVec.push_back(*it);
}
return ansVec;
}
};
而在看题解时,学到了unordered_set与vector相互转化的方法,参数只需填入各自的.begin()和.end()即可,使用后代码量减少很多。
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
unordered_set<int> nums(nums1.begin(), nums1.end()), ans;
for (int i = 0; i < nums2.size(); ++i) {
if (nums.find(nums2[i]) != nums.end()) {
ans.insert(nums2[i]);
}
}
return vector<int>(ans.begin(), ans.end());
}
};
二刷:结果也要求不重复,忘记用set来保存结果,最后再转vector。
第3题(202. 快乐数)也比较简单,不断判断新的结果是否在集合中,不存在就放入到集合中继续循环即可。
class Solution {
public:
bool isHappy(int n) {
int nNext = 0;
unordered_set<int> nums;
while (nums.find(n) == nums.end()) {
nums.insert(n);
nNext = 0;
while (n) {
nNext += (n % 10) * (n % 10);
n /= 10;
}
if (nNext == 1) {
return true;
}
n = nNext;
}
return false;
}
};
然而再次没有一次性AC,因为忘记了在循环中将n加入到集合中和初始化nNext为0,以及弄错循环条件,循环条件应当是未找到,即.find() == .end()。其中将n加入到循环中的时机很重要,应该放在循环的最开始处,否则原始n不会被添加。且不能放在循环开始前,因为会导致循环不能开始。
二刷:忘记用set来判断是否出现过的方法。
第4题(1. 两数之和)如果只是O(n²)就很简单,但低于O(n²)的方法想了半天才想到可以用target减掉所有数,得到每个数期望的另一个数,用它们构成集合。再重新遍历数组,在集合中查找当前数,如果存在,即找到。但又没这么简单,因为还面临2个问题:
- 题目要求返回下标,在集合中虽找到了,但下标却无法得到。于是改集合为map,用value来存储下标。
- 有时会遇到数本身与期望数相同的情况,如[3, 3]和target = 6,这种情况如何避免“错误地查找成功”?有了上面的下标后这个问题也就解决了,即判断找到数字的下标与当前下标不等时才认为是找到了。
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
multimap<int, int> minus;
for (int i = 0; i < nums.size(); ++i) {
minus.insert({target - nums[i], i});
}
vector<int> ans;
for (int i = 0; i < nums.size(); ++i) {
auto it = minus.find(nums[i]);
if (it != minus.end() && it->second != i) {
ans.push_back(i);
ans.push_back(it->second);
break;
}
}
return ans;
}
};
此外,还学习到multimap在插入元素时不能像map一样用下标方式插入,只能用multimap.insert({key, valus})。另外需要注意multimap容易丢掉i,错写为multmap。
看了题解发现其基本思路跟自己的一致,但更简洁高效,主要是只对数组遍历了1次,每次首先从map中查找目标,找到即返回,没找到则将当前数字和下标添加进map。这样做相比自己方法的好处主要有3点:
- 可以做到用map就解决问题。查找当前数字的期望数字时,当前数字还未加进map,所以不会有map中不会有重复元素,所以就用不到multimap;
- 查找效率更高。map是不断扩大的,所以查找规模是逐渐增大,而不像自己的方法一样每次查找都是最大规模;
- 流程更简单,代码更简洁。
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
map<int, int> minus;
for (int i = 0; i < nums.size(); ++i) {
auto it = minus.find(nums[i]);
if (it != minus.end()) {
return {it->second, i};
}
minus.insert({target - nums[i], i});
}
return {};
}
};
另外,从题解代码中也学会了2点:
- vector的赋值或返回可以直接写为{v1, v2, ···};
- map.insert()中除可以写作{key, value}外也可以写为pair<typeKey, typeValue>(key, value)。
二刷:只想到第一种方法,没想到更优的第二种。