第三天——哈希表
两个数组的交集
这道题其实思路很清楚,我们需要记录一个数组的不重复的元素然后再和另一个数组比较。但因为我们无法保证内容的顺序性,因此采用哈希表来存储是最合适的。
代码如下:
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
unordered_set<int> res;
unordered_set<int> num1_set(nums1.begin(), nums1.end()); // 构建v1数组的哈希表
for (int num : nums2) {
if (num1_set.find(num) != num1_set.end()) { // 检查v2中的元素是否在v1的哈希表中
res.insert(num);
}
}
return vector<int>(res.begin(), res.end());
}
};
快乐数
题目是快乐数,但是一开始做这个题真的不快乐。如果你单纯按照题目思路去考虑,难免会陷入硬算的陷阱。这时我们再回头看看题目,这里给了两个退出计算的条件:
如果循环某一个结果,则不是快乐数
如果某次结果是1,则是快乐数
如果只考虑结果为1的退出条件,我们其实会在非快乐数进入无限循环,因此需要一个哈希表记录每次的计算结果,如果重复直接返回false。
代码如下:
class Solution {
public:
bool isHappy(int n) {
unordered_set<int> sum_set;
while (1) {
int sum = 0;
while (n) { // 求每位数的平方和
sum += (n % 10) * (n % 10);
n /= 10;
}
if (sum == 1) { // 快乐数退出条件
return true;
}
if (sum_set.find(sum) != sum_set.end()) { // 如果结果已经出现过说明进入循环,失败退出
return false;
}
else {
sum_set.insert(sum); // 添加结果到哈希表
}
n = sum;
}
}
};
当然,这个是非常直观的解法,看过题解之后我们还可以用一种更加巧妙的解法去做:快慢指针。
想想我们之前在循环链表那题里使用的快慢指针,不难发现如果快指针每次算两步,慢指针每次算一步,如果有循环就会相遇。
代码如下:
class Solution {
public:
int getSum(int n) {
int sum = 0;
while (n) {
sum += (n % 10) * (n % 10);
n /= 10;
}
return sum;
}
bool isHappy(int n) {
int slow = n, fast = n;
do{
slow = getSum(slow);
fast = getSum(fast);
fast = getSum(fast);
}while(slow != fast);
return slow == 1;
}
};
这样做的好处主要是,如果采用哈希表存储,会占据过多的内存,如果是一个较大的数字,有可能算很多步还没有出现循环,这时反复的存入可能导致内存爆掉。说白就是用时间换空间。
两数之和
这道题与之前的题最大的差别就在于我们需要记录两个值:数组元素与目标数的差值和数组元素位置,因此这里要求我们存入的不再是一个单一的值,而是具有对应关系的一组值。这时单纯的使用hash_set是不够的,我们要引出另一种数据结构:hash_map
C++中的Map有三种类型:
映射 | 底层实现 | 是否有序 | 数组是否可以重复 | 能否更改数值 | 查询复杂度 | 增删复杂度 |
---|---|---|---|---|---|---|
std::map | 红黑树 | 有序 | 不可重复 | 不可修改 | O(logN) | O(logN) |
std::multimap | 红黑树 | 有序 | 可重复 | 不可修改 | O(logN) | O(logN) |
std::unordered_map | 哈希表 | 无序 | 不可重复 | 不可修改 | O(1) | O(1) |
在这道题里我们使用unordered_map效率最高。
代码如下:
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
unordered_map<int, int> map;
for (int i = 0; i < nums.size(); i++) {
auto iter = map.find(target - nums[i]);
if (iter != map.end()) {
return {iter->second, i}; // 如果找到对应存储的值就返回
}
map.insert(pair<int, int>(nums[i], i)); // 添加一对值
}
return {};
}
};
三数之和
力扣原题
接下来的这题是有点难度的。因为题目中要求了三元组不能有重复,所以如果使用之前我们的哈希表来做,则每次计算结果都要考虑是否有重复,去重的话,自己提交试试就知道了,直接超时没得说的。
这时哈希表搞不定,我们还有什么别的办法搞定呢,其实使用我们之前用过的双指针就能解决。先将数组排序,再将a+b+c=0化成寻找A+b+c=0,固定a用双指针寻找b和c就行。
代码如下:
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> res;
sort(nums.begin(), nums.end()); // 排序
for (int i = 0; i < nums.size(); i++) {
// 如果排序后第一个元素就大于零,则再求和不可能为零
if (nums[i] > 0) {
return res;
}
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
int l = i + 1;
int r = nums.size() - 1;
while (l < r) {
if (nums[i] + nums[l] + nums[r] > 0) {
r--;
}
else if (nums[i] + nums[l] + nums[r] < 0) {
l++;
}
else {
// 存入找到的三元组
res.push_back(vector<int>{nums[i], nums[l], nums[r]});
// 去重逻辑应该放在找到一个三元组之后
while (r > l && nums[r] == nums[r - 1]) r--;
while (r > l && nums[l] == nums[l + 1]) l++;
// 找到答案时,双指针同时收缩
r--;
l++;
}
}
}
return res;
}
};