代码随想录day6 | LeetCode242. 有效的字母异位词、LeetCode349. 两个数组的交集、LeetCode202. 快乐数、LeetCode1. 两数之和
day5为周日,休息日
今天是哈希表开篇
哈希表基础
-
哈希表是根据关键码的值而直接进行访问的数据结构。
-
一般哈希表都是用来快速判断一个元素是否出现集合里。
-
空间换时间的一种策略
-
将元素映射到哈希表上就涉及到了hash function (哈希函数)。
-
一般hash code是通过特定编码方式,可以将其他数据格式转化为不同的数值
-
通过取模解决hash Code得到的数值大于哈希表大小情况
-
哈希碰撞:不同元素映射到同一下标位置(哈希函数选择不好会提升出现概率,元素数量大于哈希表的大小时一定会出现)
-
哈希碰撞解决方法:
-
拉链法
选择适当的哈希表的大小,这样既不会因为数组空值而浪费大量内存,也不会因为链表太长而在查找上浪费太多时间。
-
线性探测法
一定要保证tableSize大于dataSize。 我们需要依靠哈希表中的空位来解决碰撞问题。
-
-
三种哈希结构
无论底层采用什么实现形式,对外提供的都是哈希法的使用方式,即依靠键(key)来访问值(value),所以使用这些数据结构来解决映射问题的方法,我们依然称之为哈希法
-
数组
-
set (集合)
集合 底层实现 是否有序 数值是否可以重复 能否更改数值 查询效率 增删效率 std::set 红黑树 有序 否 否 O(log n) O(log n) std::multiset 红黑树 有序 是 否 O(logn) O(logn) std::unordered_set 哈希表 无序 否 否 O(1) O(1) 红黑树是一种平衡二叉搜索树,所以key值是有序的,但key不可以修改,改动key值会导致整棵树的错乱,所以只能删除和增加。
-
map(映射)
加。
映射 底层实现 是否有序 数值是否可以重复 能否更改数值 查询效率 增删效率 std::map 红黑树 key有序 key不可重复 key不可修改 O(logn) O(logn) std::multimap 红黑树 key有序 key可重复 key不可修改 O(log n) O(log n) std::unordered_map 哈希表 key无序 key不可重复 key不可修改 O(1) O(1) std::map 和std::multimap 的key是有序的(红黑树)(这个问题也经常作为面试题,考察对语言容器底层的理解)。
-
-
使用集合时,优先使用unordered_set,因为它的查询和增删效率是最优的,如果需要集合是有序的,那么就用set,如果要求不仅有序还要有重复数据的话,那么就用multiset。
总结:
快速判断一个元素是否出现集合里
判断一个元素是否出现过
第一时间想到哈希法
有效的字母异位词
自己敲
判断t字符串中字符是否在s字符串中字符构成的集合中出现过,采用哈希
未要求集合有序或有重复数据,采用multiset,存26个连续的0表示a-z的初始数量为0
class Solution {
public:
bool isAnagram(string s, string t) {
multiset_set<int> set = //初始化a-z个0;
for(int i=0;i<s.size();i++){
set(s[i]-a)++;
}
for(int i=0;i<t.size();i++){
set(t[i]-a)--;
}
for(int i;i<set.size();i++){
//如果一个元素不为0,则返回false
}
//返回true
}
};
看题解
暴力解法
暴力的解法,两层for循环,同时还要记录字符是否重复出现
哈希表
别忘了数组!
这道题目可以定义一个数组,来记录字符串s里字符出现的次数。
因为字符a到字符z的ASCII是26个连续的数值,所以字符a映射为下标0,相应的字符z映射为下标25
再遍历 字符串s的时候,只需要将 s[i] - ‘a’ 所在的元素做+1 操作即可,并不需要记住字符a的ASCII,只要求出一个相对数值就可以了
class Solution {
public:
bool isAnagram(string s, string t) {
int record[26] = {0};
for (int i = 0; i < s.size(); i++) {
// 并不需要记住字符a的ASCII,只要求出一个相对数值就可以了
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) {
// record数组如果有的元素不为零0,说明字符串s和t 一定是谁多了字符或者谁少了字符。
return false;
}
}
// record数组所有元素都为零0,说明字符串s和t是字母异位词
return true;
}
};
总结
-
求字符串长度采用
size()
方法sizeof()
可以以类型、指针、数组和函数等作为参数,其值在编译时计算,用于获取操作数所占空间的字节数大小。strlen()
函数其参数只能为char*
(字符型指针),所以在计算字符数组(char str[ ])
时,一般用strlen()
函数(也只能用于计算字符数组了)。size()
函数以及length()
函数都用于计算字符串(string)长度,不能用char*
作为参数。除此之外,size()
函数还可以获取vector类型的长度。
原文参考:C++求字符串长度————sizeof()、size()、strlen()以及length()详解_c++字符串长度-CSDN博客
两个数组的交集
题目链接:LeetCode349. 两个数组的交集
自己敲
求两数组交集,即判断一个数组中元素是否在另一个数组元素集合里,采用哈希
要求输出结果唯一且不要求集合有序,采用unordered_set
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
//将unordered_set初始化为nums1
//遍历nums2,如果元素在unordered_set,就存入数组s
//遍历输出数组s
}
};
由于忘记STL操作,所以只写了思路
看题解
什么时候采用数组?
题目限制了数值大小时
如果哈希值比较少、特别分散、跨度非常大,使用数组就造成空间的极大浪费
思路如图所示:
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());
for (int num : nums2) {
// 发现nums2的元素 在nums_set里又出现过
if (nums_set.find(num) != nums_set.end()) {
result_set.insert(num);
}
}
return vector<int>(result_set.begin(), result_set.end());
}
};
总结
题目不难,主要是学会使用unordered_set用法
std::unordered_set<string> things {16}; // 16 buckets
std::unordered_set<string> words {"one", "two", "three", "four"};// Initializer list
std::unordered_set<string> some_words {++std::begin(words), std::end (words)}; // Range
std::unordered_set<string> copy_wrds {words}; // Copy constructor
原文参考:C++ unordered_set定义及初始化详解 (biancheng.net)
快乐数
题目链接:LeetCode202. 快乐数
自己敲
判断无限循环,循环,证明n及n变化后的数构成的集合中有重复的数
所以解题要判断当前数是否在历史数据这个集合中重复,采用哈希
不需要存入的元素有序,但要可以重复,存入元素数量不确定,采用vector作为集合
class Solution {
public:
bool isHappy(int n) {
vector<int> s;
while(n!=1){
for(int i=0;i<s.size();i++){
if(n==s[i]){
return false;
}
}
s.push_back(n);
int sum=0;
//将n各个位上的数求和
while(n>0){
sum+=(n%10)*(n%10);
n/=10;
}
n=sum;
}
return true;
}
};
看题解
和我思路一致,只是题解集合用的时unordered_set
题目中说了会 无限循环,那么也就是说求和的过程中,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) {
unordered_set<int> set;
while(1) {
int sum = getSum(n);
if (sum == 1) {
return true;
}
// 如果这个sum曾经出现过,说明已经陷入了无限循环了,立刻return false
if (set.find(sum) != set.end()) {
return false;
} else {
set.insert(sum);
}
n = sum;
}
}
};
总结
set.find(sum) != set.end()
,注意此处unordered_set用法,这个判断条件为真,即在set集合中找到了sum
两数之和
题目链接:LeetCode1. 两数之和
自己敲
没啥思路,暴力求解
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
vector<int> s;
for(int i=0;i<nums.size();i++){
if(nums[i]<=target){
s.push_back(nums[i]);
}
}
vector<int> result;
for(int i=0;i<s.size()-1;i++){
for(int j=i+1;j<s.size();j++){
if(s[i]+s[j]==target){
result.push_back(i);
result.push_back(j);
return result;
}
}
}
return result;
}
};
看题解
本题我们需要一个集合来存放我们遍历过的元素,然后在遍历数组的时候去询问这个集合,某元素是否遍历过,也就是 是否出现在这个集合
本题,我们不仅要知道元素有没有遍历过,还要知道这个元素对应的下标,需要使用 key value结构来存放,key来存元素,value来存下标,那么使用map正合适。
再来看一下使用数组和set来做哈希法的局限。
- 数组的大小是受限制的,而且如果元素很少,而哈希值太大会造成内存空间的浪费。
- set是一个集合,里面放的元素只能是一个key,而两数之和这道题目,不仅要判断y是否存在而且还要记录y的下标位置,因为要返回x 和 y的下标。所以set 也不能用。
这道题目中并不需要key有序,选择std::unordered_map 效率更高
map用来做什么?
map目的用来存放我们访问过的元素,因为遍历数组的时候,需要记录我们之前遍历过哪些元素和对应的下标,这样才能找到与当前元素相匹配的(也就是相加等于target)
map中key和value分别表示什么?
这道题 我们需要 给出一个元素,判断这个元素是否出现过,如果出现过,返回这个元素的下标。
那么判断元素是否出现,这个元素就要作为key,所以数组中的元素作为key,有key对应的就是value,value用来存下标。
所以 map中的存储结构为 {key:数据元素,value:数组元素对应的下标}。
在遍历数组的时候,只需要向map去查询是否有和目前遍历元素匹配的数值,如果有,就找到的匹配对,如果没有,就把目前遍历的元素放进map中,因为map存放的就是我们访问过的元素。
过程如下:
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++) {
// 遍历当前元素,并在map中寻找是否有匹配的key
auto iter = map.find(target - nums[i]);
if(iter != map.end()) {
return {iter->second, i};
}
// 如果没找到匹配对,就把访问过的元素和下标加入到map中
map.insert(pair<int, int>(nums[i], i));
}
return {};
}
};
总结
auto
自动类型推导:使用auto
关键字,编译器会根据初始化表达式的类型自动推导出变量的类型
auto x = 10; // x 被推导为 int
auto y = 3.14; // y 被推导为 double
auto str = "Hello, World!"; // str 被推导为 const char*