哈希表
哈希表理论基础
哈希表理论基础代码随想录链接
242.有效的字母异位词
题目链接/文章讲解/视频讲解
349. 两个数组的交集
题目链接/文章讲解/视频讲解
202. 快乐数
题目链接/文章讲解/视频讲解
1.两数之和
题目链接/文章讲解/视频讲解
学习时间
2024.6.11晚上-2024.6.12凌晨1点
自己看到题目的第一想法 VS 看完代码随想录之后的想法
- LeetCode——242.有效的字母异位词(数组)
(1)自己:不记得哈希表怎么使用,暴力解法(后期有空尝试)
(2)官方解题思路(赶紧这个分析思路很巧妙,但我太菜鸡想不到)
class Solution {
public:
bool isAnagram(string s, string t) {
if (s.length() != t.length()) {
return false;
}
sort(s.begin(), s.end());
sort(t.begin(), t.end());
return s == t;
}
};
(3)数组映射
看完代码随想录思路:字符映射到数组也就是哈希表的索引下标上,因为字符a到字符z的ASCII是26个连续的数值,所以字符a映射为下标0,相应的字符z映射为下标25。第一次遍历字符串对应的下标++,第二次遍历对应的下标–,然后看最终结果是否为0。注意本题是因为限制了数值的大小所以可以用数组映射
class Solution {
public:
bool isAnagram(string s, string t) {
int count[26] = {0};
if (s.size() != t.size()) { // 一开始就判断长短是否相同
return false;
}
for (int i = 0; i < s.size(); i++) {
count[s[i] - 'a']++; // 或者-97(a的ASCII码),耗时更短
}
for (int j = 0; j < t.size(); j++) {
count[t[j] - 'a']--;
}
for (int k = 0; k < 26; k++) {
if (count[k] != 0) {
return false;
}
}
return true;
}
};
-
LeetCode——349.两个数组的交集 (set更好/数组)
小点:直接使用set 不仅占用空间比数组大,而且速度要比数组慢,set把数值映射到key上都要做hash计算的。这个耗时,在数据量大的情况,差距是很明显的。
unordered_set——可以无序,但是数值不可以重复
(1)自己:
a.思路:首先与上一题类似映射,count1001初始化为最大值(1001,因为题目有限制),对应数字的对应索引元素为对应数字;其次在nums2里循环,对应数字的对应索引元素为0;第三个for循环(count长度),寻找0,如果有的0,对res_set插入0元素对应的索引。这种方法应该是自动去重的了,但是考虑到3个for循环一直写。。感觉效率很低,所以后面闲下来可以补一下程序代码。b.思路:首先与上一题类似映射,对应数字的对应索引元素为对应数字;(好吧有点绕,图示理解);其次在nums2里循环与count对比,如果有的话,对res_set插入元素;但有个重要问题就是要对重复的进行移除操作
错误示例:(没有去重)
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
vector<int> res_set;
int count[1005] = {1005};
for(int i=0; i<nums1.size(); i++){
count[nums1[i]] = nums1[i];
}
for(int j=0; j<nums2.size(); j++){
if(count[nums2[j]] == nums2[j])
res_set.push_back(nums2[j]);//不是Push
}
return res_set;
}
};
去重搜索了一下,set是最简单的,还有一种方法:
使用 std::unique 函数对数组进行原地去重的示例。std::unique 函数是 C++ 标准库中的一个算法,它会将相邻的重复元素移到容器的尾部,并返回一个指向新的逻辑末尾的迭代器。为了使其工作,需要先对数组进行排序,这样相同的元素会变得相邻。以下是具体的实现步骤:
i)对数组进行排序,使得相同的元素相邻。
ii)使用 std::unique 函数将相邻的重复元素移到容器的尾部,并获取新的末尾迭代器。
iii)使用返回的迭代器调整容器的大小,以删除多余的元素。
下面是完整的代码示例:
#include <iostream>
#include <algorithm> // 包含 std::sort 和 std::unique
#include <vector>
int main() {
std::vector<int> arr = {1, 2, 2, 3, 4, 4, 5}; // 原始数组
// 1. 对数组进行排序
std::sort(arr.begin(), arr.end());
// 2. 使用 std::unique 将相邻的重复元素移到尾部
auto new_end = std::unique(arr.begin(), arr.end());
// 3. 调整容器大小以去除多余的元素
arr.erase(new_end, arr.end());
// 打印去重后的数组
for (int i : arr) {
std::cout << i << " ";
}
std::cout << std::endl;
return 0;
}
运行上述代码,输出结果将是:
1 2 3 4 5
解释:
std::sort(arr.begin(), arr.end()); 对数组进行排序,使得相同的元素相邻。
std::unique(arr.begin(), arr.end()); 将相邻的重复元素移到容器的末尾,并返回一个指向新逻辑末尾的迭代器。
arr.erase(new_end, arr.end()); 删除从新逻辑末尾到实际末尾之间的所有元素,完成去重操作。
这样,我们就通过 std::unique 函数实现了数组的原地去重。
(2)看了代码随想录
自己是真菜,想的思路极其繁琐
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
unordered_set<int>
result_set; // 存放结果,之所以用set是为了给结果集去重
unordered_set<int> nums_set(nums1.begin(),nums1.end()); // 将数组转化为set去重
//使用引用 (int& 或 const int&) 可以直接修改或防止拷贝元素,提升性能
for (const int& num : nums_set) {
cout << num << " "; // 打印修改后的元素
}
for (int num : nums2) {
//int 也可以替换为auto关键字可以让代码更通用,不必显式指定元素类型。
// 发现nums2的元素 在nums_set里又出现过
if (nums_set.find(num) != nums_set.end()) {
result_set.insert(num);
}
}
return vector<int>(result_set.begin(), result_set.end());
}
};
- LeetCode——202.快乐数(set)
(1)自己:把一个数拆成每个单位平方(不会);然后sum!=1循环,怎么用映射呢?
(2)看完代码随想录:题目中说了会 无限循环,那么也就是说求和的过程中,sum会重复出现,解题很重要! 当我们遇到了要快速判断一个元素是否出现集合里的时候,就要考虑哈希法了。
class Solution {
public:
int GetNewSum(int n) {
int sum = 0;
while (n) {
sum += (n % 10) * (n % 10);
n = n / 10;
}
return sum;
}
bool isHappy(int n) {
unordered_set<int> res;
while (1) {
int sum = GetNewSum(n);
if (sum == 1) {
return true;
}
if (res.find(sum) != res.end()) {//找到了返回对应的key,没找到=end()
return false;
} else {
res.insert(sum);
}
n = sum;
}
}
};
- LeetCode——1.两数之和(map)
map基本操作
(1)自己思路:从索引0开始,到size-1,因为循环到最后就一个数了,肯定不满足条件。
一个循环每个值;另一遍循环找后面元素是否=target - 当前的值。AC
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
for(int i=0; i<nums.size()-1; i++)
{
for(int j = i; j<nums.size()-1; j++){
if(nums[j+1] == (target - nums[i])){
return vector<int>{i,j+1};
}
}
}
return vector<int>{0,0};
}
};
(2)看完视频讲解:map{key,value}
这道题 我们需要 给出一个元素,判断这个元素是否出现过,如果出现过,返回这个元素的下标。
那么判断元素是否出现,这个元素就要作为key,所以数组中的元素作为key,有key对应的就是value,value用来存下标。
所以 map中的存储结构为 {key:数据元素,value:数组元素对应的下标}。
// class Solution {
// public:
// vector<int> twoSum(vector<int>& nums, int target) {
// for(int i=0; i<nums.size()-1; i++)
// {
// for(int j = i; j<nums.size()-1; j++){
// if(nums[j+1] == (target - nums[i])){
// return vector<int>{i,j+1};
// }
// }
// }
// return vector<int>{0,0};
// }
// };
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
// key存储数组值,value存储数组索引;
// 注意题目是查找元素是否出现,而map可以快速查找key,所以用key存储元素
// 题目返回数组下标,所以用value存储索引
unordered_map<int, int> map;
for (int i = 0; i < nums.size(); i++) {
int find_s = target - nums[i]; // 要查询的值
if (map.find(find_s) != map.end()) {
return { map[find_s], i};
} else {
map.insert(nums[i],i); //key,value都要存 !!!error
}
}
return {};
}
};
编译错误,对Map的insert错误使用,修改方法:
a. for中修改——卡哥代码:auto可以让iter作为一个指针
auto iter = map.find(target - nums[i]);
if(iter != map.end()) {
return {iter->second, i};
}
map.insert(pair<int, int>(nums[i], i));
b. else中修改
map[nums[i]] = i; // key,value都要存
或者
map.insert(pair<int, int>(nums[i], i));
或者
map.emplace(nums[i],i);
自己实现的困难
1、定义数组
(1)int record[26] = {0};
这是一个标准的C++数组声明,表示一个包含26个整数的数组,并且所有元素都初始化为0。
int record[26] = {0};
特点:
类型:数组中的每个元素都是一个 int 类型的整数。
大小:固定为26个元素,不能动态调整大小。
初始化:所有元素被初始化为0。若未显式初始化,默认情况下,未指定初始化值的元素将全部被初始化为0。
内存分配:在栈上分配内存。
(2) vector record[26] = {0};
这是一个包含26个 std::vector 的数组,并且试图将第一个向量初始化为0。这种写法不符合预期的用法,因为 std::vector 不接受单个整数来初始化,而是通常通过列表初始化或默认构造函数进行初始化。
#include <vector>
using namespace std;
vector<int> record[26];
特点:
类型:数组中的每个元素都是一个 std::vector 对象。
大小:固定为26个元素,但每个 std::vector 本身可以动态调整大小。
初始化:未显式初始化的情况下,每个 std::vector 默认构造为空的向量。你可以在后续代码中添加元素。
内存分配:数组本身在栈上分配,但 std::vector 中的元素在堆上分配。
2、思路繁琐
3、怎么将一个数拆成每个单位的平方,以及怎么映射?
没注意无限循环的隐藏条件!
4、对map一点都不记得。。
今日收获
1、数组映射;2、set映射;3、map映射
知识点:for()循环的另外一种写法;auto的使用