代码随想录算法训练营Day05|242. 有效的字母异位词、349. 两个数组的交集、202. 快乐数、1. 两数之和
242. 有效的字母异位词
题目链接:242. 有效的字母异位词
题干要求两字符串只出现小写字母,我们可以使用26位长度的数组来表示统计所有字母,不用对比ASII码进行大小写字母的判断。这里对于位数确定的哈希表直接使用数组即可。因为数组就是特殊的哈希表。
代码如下:
class Solution {
public:
bool isAnagram(string s, string t) {
int record[26] = {0};
for (int i = 0; i < s.size(); i++) {
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)
return false;
}
return true;
}
};
349. 两个数组的交集
题目链接:349. 两个数组的交集
题干首先要求输出结果每个元素唯一,即重复的相同元素只输出一次。这题与242. 有效的字母异位词
类似,可以类比成字符串的比较,但区别在于:
- 哈希表长度不定:不能使用定长的数组进行重复元素的统计
- 判断标准变化:这里对两个数组分别计数,再对相同键值的计数进行比对,均不为0即可输出到目标结果中。
考虑到本题要求元素不重复,自然想到使用无序集合来进行统计,本题主要是熟悉集合(set)相关的操作:
// 使用容器元素直接初始化集合
unordered_set num_set(nums.begin(), nums.end());
// 添加元素
num_set.insert(num);
// 删除元素
num_set.erase(num);
// 使用集合元素初始化容器
vetor<int> res(num_set.begin(), num_set.end());
代码如下:
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
unordered_set<int> result;
unordered_set<int> num_set(nums1.begin(), nums1.end());
for (int num : nums2) {
if (num_set.find(num) != num_set.end())
result.insert(num);
}
return vector<int>(result.begin(), result.end());
}
};
202. 快乐数
题目链接:202. 快乐数
题干给出了一种名为快乐树
的定义,题目中提到快乐树
可能会无限循环,这意味着快乐树
的平方和会出现重复的情况,这将作为我们模拟快乐树
演变过程的终止条件。本题为“查询一个元素是否出现过“的情况,直接联想到哈希法。
另外本题还涉及对任意整型数各个位平方求和的问题,因为无法确定位数,所以从最低位开始进行平方和累加:
int sum = 0, bit;
while (n) {
bit = n % 10;
n /= 10;
sum += bit * bit;
}
在每次求出''位平方和''后进行比对:
- "位平方和"为1,则可以确定为
快乐树
- "位平方和"不为1,则将其将入待定集合中,用以判定何时进入无限循环
代码如下:
class Solution {
public:
bool isHappy(int n) {
unordered_set<int> num_set;
while (1) {
int sum = 0, bit;
while (n) {
bit = n % 10;
n /= 10;
sum += bit * bit;
}
if (sum == 1)
return true;
if (num_set.find(sum) != num_set.end())
return false;
num_set.insert(sum);
n = sum;
}
return false;
}
};
1. 两数之和
题目链接:1. 两数之和
题干要求两元素值可以相同,但同一元素不能重复使用。例如:
输入:nums = [3,2,4], target = 6
输出:[1,2]
例子中输出的结果不能为[0,0]
,因为这种情况会用到两次。另外每次输入只会存在一个有效答案,因此找到可行解后直接终止程序即可。
①暴力解法
双循环遍历数组,找到求和为target
并且下标不重复的下标组合。
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
vector<int> res;
for (int i = 0; i < nums.size(); i++) {
int rest = target - nums[i];
for (int j = i + 1; j < nums.size(); j++) {
if (nums[j] == rest) {
res.push_back(i);
res.push_back(j);
return res;
}
}
}
return res;
}
};
②哈希法
本题为“一个元素是否在集合里”的情况,直接联想到哈希法。我们每次取出第一个元素,另外一个所需元素的值也可以确定。我们可以考虑使用哈希表的.find()
函数在散列表里搜索结果(用存储空间换时间了属于是)。所以我们需要把目前数组nums
转换成哈希表形式。现在面临选择:
-
unordered_set
:集合内只能存放单个key
,题设涉及到下标不重复的条件,不合适。 -
unordered_map
:映射内可以存放key-value
键值对,我们存放格式为(数值)-(下表)
,用来判断该键值对是否出现的元素设置为key
。这样我们找寻到目标值何时的映射时,可以通过指针unordered_map<int,int>::iterator
来访问数值对应的下标内容。unordered_map<int, int>::iterator iter = num_map.find(target); if (iter != num_map.end()) { cout << iter->first << endl; // 访问的是第一个元素(数值) cout << iter->second << endl; // 访问的是第二个元素(下标) }
关于下标不重复的问题,我们会新建一个空的unordered_map
,在每次find()
判断未找到目标值后,将该键值对插入映射中。
我们不用考虑数值相同但下标不同的键值对插入映射造成插入失败的情况,因为在外层for循环判断时,就能够直接返回结果(一个在数组元素,一个在映射元素)。因为只有唯一一个有效解,所以不用再次插入映射考虑其他重复的情况。代码如下:
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
unordered_map<int,int> num_map;
for (int i = 0; i < nums.size(); i++) {
int rest = target - nums[i];
auto iter = num_map.find(rest);
if (iter != num_map.end())
return {i, iter->second};
else
num_map.insert(pair<int,int>(nums[i], i));
}
return {};
}
};
总结
- 哈希法也是牺牲了空间换取了时间,因为我们要使用额外的数组,set或者是map来存放数据,才能实现快速的查找。
- 当我们遇到了要快速判断一个元素是否出现集合里的时候,就要考虑哈希法。
- 关于集合的使用选择:
- 当要使用集合来解决哈希问题的时候,优先使用
unordered_set
,因为它的查询和增删效率是最优的。 - 如果需要集合是有序的,那么就用
set
。 - 如果要求不仅有序还要有重复数据的话,那么就用
multiset
。
- 当要使用集合来解决哈希问题的时候,优先使用