目录
题目链接:242.有效的字母异位词
思路
字母异位词只需要保证两个单词中字母出现的次数相同即可,不用考虑顺序。另外这道题只包含小写字母,总共26个,因此使用数组做。使用哈希表做好像会增加哈希函数映射查询的时间,对哈希了解不是很深,后面慢慢研究。
步骤
①判断两个字符串长度是否相等,相等才有可能是字母异位词
②创建数组,大小26,刚好是所有小写字母的个数,用以保存字母出现的顺序
③在数组中增加字符串s中字母出现的次数
④在数组中减去字符串t中字母出现的次数
⑤如果在做减法的时候数组中出现了小于0的数,说明某个字母在两个字符串中出现的次数不一样,两个单词不是字母异位词,返回false
⑥如果上述检查并未返回false,则说明两个单词是字母异位词,返回true
代码
class Solution {
public:
bool isAnagram(string s, string t) {
// 如果两个单词的长度不一样,就一定不是字母异位词
if (s.size() != t.size()) {
return false;
}
int hash[26] = {0}; // 创建数组保存单词中字母出现的次数
// 保存字符串s中字母出现的次数
for (int i = 0; i < s.size(); i++) {
hash[s[i] - 'a']++;
}
// 从表中减去t中字母出现的次数,如果数组中出现小于0的数
// 则说明某个字母在t和s中出现的次数不一样
for (int i = 0; i < t.size(); i++) {
if (--hash[t[i] - 'a'] < 0) {
return false;
}
}
return true;
}
};
题目链接:349. 两个数组的交集
思路一:数组
题目中给了两个数组的长度范围[1,1000],元素大小范围[0,1000],因此可以创建一个长度1001的新数组,先遍历第一个数组,把出现过的数字在新数组中记录,再遍历第二个数组,查看是否出现过,再创建一个结果数组,把交集部分保存下来。
这里的结果数组类型采用unordered_set类型,是为了去重以及返回大小刚好合适的结果数组,另外学到了一个新的数组遍历for(int num:nums1){},num会依次取数组nums1中的所有元素。
代码
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
// 使用unorder_set保存结果是为了去重,该类型中不会出现重复的元素
unordered_set<int> result;
int hash[1001] = {0}; // 题目提示数组元素范围在0~1000
// 遍历数组一,记录已经出现过的数字
for (int num : nums1) {
hash[num] = 1;
}
// 遍历数组二,如果数组二中的元素在哈希表中已经出现,则元素插入到result中
for (int num : nums2) {
if (hash[num] == 1)
result.insert(num);
}
// unordered_set类型转换成vector并返回
return vector<int>(result.begin(), result.end());
}
};
思路二:哈希表
题目要求寻找两个数组中的交集,这种迅速找到一个元素是否出现过的问题,想到了哈希表中的set,另外题目要求不能重复,因此使用set中元素不重复的unordered_set。其他的都和思路一类似。
关于哈希表,set类型是针对单个元素的查找,unordered_set类型保证无重复元素,另外学到了哈希表的构建,即unordered_set<int> hash(nums1.begin(),nums1.end()),unordered_set为哈希表类型,int为数据类型,hash为哈希表的名称,begin和end即数组的开头结尾。
代码
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
unordered_set<int> result; // 创建unordered_set类型的result为了去重
// 创建哈希表,也是unordered_set类型,保存数组nums1中的元素
unordered_set<int> hash(nums1.begin(), nums1.end());
// 遍历数组nums2,查找其元素在hash中是否出现
// 如果出现插入到result中
for (int num : nums2) {
if (hash.find(num) != hash.end()) {
result.insert(num);
}
}
// 类型转换,返回结果
return vector<int>(result.begin(), result.end());
}
};
题目链接:202. 快乐数
思路
根据题目,首先应该求出正整数的每个位置上数字的平方和,这个求和过程会反复使用,因此单独写成一个函数用来调用会比较方便。
其次,要循环的话必须明确终止条件。如果一个数不是快乐数,就会进入死循环,因此我们判断当前的正整数求出的平方和在之前是否出现过,如果出现过,就说明求和过程会进入死循环,此时判断该数并非快乐数,并跳出循环。
因此该问题的核心还是要快速知道当前求出的平方和在之前是否出现过,因此还是想到使用哈希表,根据题目意思,哈希表中的不需要排序,不需要重复,因此使用unordered_set类型保存计算过的平方和。
代码
class Solution {
public:
// 计算正整数每个位置上数字的平方和
int sum(int n) {
int sum = 0;
while (n) {
sum += (n % 10) * (n % 10);
n /= 10;
}
return sum;
}
bool isHappy(int n) {
unordered_set<int> hash;
// 平方和为1时,终止循环,到函数末尾返回true
while (sum(n) != 1) {
hash.insert(n); // 将当前的正整数保存到哈希表中
// 如果计算后的平方和在哈希表中出现过,说明要进入死循环了,返回false
if (hash.find(sum(n)) != hash.end()) {
return false;
}
n = sum(n); // 更新正整数
}
return true;
}
};
题目链接:1. 两数之和
思路一:暴力解法
求两数之和的下标,且下标不能重复,那么使用双循环应该也能完成题目要求。第一层循环遍历数组,第二层循环从当前位置往后遍历,看后面的元素和当前元素相加是否等于目标值,如果相等则返回此时的下标。这种做法时间复杂度O(n²)。
代码
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
for (int i = 0; i < nums.size(); i++) {
for (int j = i + 1; j < nums.size(); j++) {
if (nums[i] + nums[j] == target) {
return {i, j};
}
}
}
return {};
}
};
思路二:哈希表
题目要求找到符合要求的下标,寻值问题可以使用哈希表,题目不仅要元素,还要下标,因此使用map,map的键值对刚好对应题目的元素和下标。因为题目要求下标不重复,且对顺序无要求,因此使用unordered_map类型。
map中的键key为第一个元素,值value为第二个元素,对应second。
代码
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
std::unordered_map<int, int> map; // 创建哈希表用来寻值
for (int i = 0; i < nums.size(); i++) {
auto sub = map.find(target - nums[i]);
// 如果上述的差可以在map中找到,就说明找到了两数之和等于目标值
// 此时返回两数下标即可,i和当前map的value
if (sub != map.end()) {
return {i, sub->second};
}
// 如果没找到,将数组当前位置的数值和下标都存进哈希表
map.insert(pair<int, int>(nums[i], i));
}
return {};
}
};
总结
哈希表的思想比较简单,但是操作起来还是有很多要注意的地方。需要了解哈希表的底层实现,这周末把这块搞清楚。
①当需要快速知道一个元素在之前是否出现过的时候可以考虑使用哈希表
②哈希法的数据结构有三种,数组,set和map
③使用数组实现哈希法的时候一般是知道元素大小的,例如242和349两道题
④set类型主要是针对寻找单个元素,另外根据不同需求有三种set类型
⑤map类型主要是针对寻找一对元素,例如1.两数之和,首先要知道数组中的两数是否等于target,其次是如果等于,需要知道其下标,这样就有了数值和下标这样一对数据,刚好对应map中的键key和值value。同样map也有三种类型
⑥哈希法中的函数,insert,find,begin,end等
⑦find函数返回的类型是一种迭代器类型,使用auto可以让编译器根据赋值语句右侧的表达式推到出变量的类型,这里返回的迭代器类型可能很长很复杂,用auto可以不用显式的指定其类型,使代码更简洁
⑧map.insert(pair<int,int>()),这里的pair是一种简单的数据类型,可以将两个值捆绑在一起,并且这两个值可以是不同类型的,一般map的insert都需要使用pair说明两个值的类型,也可以去掉,不用显式的使用pair