哈希表理论基础
文章链接:哈希表理论基础
最主要思想为当我们遇到了要快速判断一个元素是否出现集合里的时候,就要考虑哈希法。但是哈希表也是牺牲了空间换取了时间,因为我们要使用额外的数组,set或是map来存放数据,才能实现快速的查找。
Leetcode 242.有效的字母异位词
题目链接:242.有效的字母异位词
暴力法思路:开两个数组记录分别两个字符串的字母出现次数,并再循环一次来进行比较两个字符串的字母是否全部一致,最后一次循环选较长的字符串的长度来进行循环 (看完视频觉得虽然我的这个暴力法比双循环的好,但其实也是比卡尔的方法多耗时,如果数据增强一点,有可能我这个方法会超时)
哈希法思路:开一个长度为26(小写字母只有26个)的数组来记录两个字符串的字母出现次数,(通过s[i] - 'a’来映射到数组上)记录s时,数组++,记录t时,数组–,再循环一遍数组,如果数组中出现不为0的数,即两个字符串不为有效的字母异位词
暴力法:
class Solution {
public:
bool isAnagram(string s, string t) {
int cnt1[6000] = { 0 }, cnt2[6000] = { 0 };
int n;
string str;
if(s.size() > t.size()) {
n = s.size();
str = s;
} else {
n = t.size();
str = t;
}
for(int i = 0; i < s.size(); i++) {
cnt1[s[i]]++;
}
for(int i = 0; i < t.size(); i++) {
cnt2[t[i]]++;
}
for(int i = 0; i < n; i++) {
if(cnt1[str[i]] != cnt2[str[i]])
return false;
}
return true;
}
};
- 时间复杂度:O(n)
- 空间复杂度:O(n)
哈希法:
class Solution {
public:
bool isAnagram(string s, string t) {
int hash[26] = { 0 };
for(int i = 0; i < s.size(); i++)
hash[s[i] - 'a'] ++;
for(int i = 0; i < t.size(); i++)
hash[t[i] - 'a'] --;
for(int i = 0; i < 26; i++) {
if(hash[i] != 0) return false;
}
return true;
}
};
- 时间复杂度:O(n)
- 空间复杂度:O(1)
Leetcode 349.两个数组的交集
题目链接:349.两个数组的交集
暴力法思路:两个数组的数相等就记录,但是如果已经记录过则直接break,这个思路有点类似于卡尔用数组法,但是我这个用了双循环来记录,太耗时间和空间。
哈希法(使用set)思路:使用unordered_set来记录result,并且开一个新的unordered_set来当作哈希表,直接用nums1来初始化,如果nums2中的元素在哈希表中找到的话则将这个数存入result中,因为unordered_set是自动去重,因此不用担心重复的问题
暴力法:
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
vector<int> result;
int hash[1001] = { 0 };
for(int i = 0; i < nums1.size(); i++) {
for(int j = 0; j < nums2.size(); j++) {
if(hash[nums1[i]] > 0) break;
if(nums1[i] == nums2[j]) hash[nums1[i]] ++;
}
}
for(int i = 0; i < 1001; i++){
if(hash[i] > 0) result.push_back(i);
}
return result;
}
};
- 时间复杂度为O(n^2)
- 空间复杂度为O(n)
哈希法:
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
unordered_set<int> result;
unordered_set<int> nums_set(nums1.begin(), nums1.end());
for(int i = 0; i < nums2.size(); i++) {
if(nums_set.find(nums2[i]) != nums_set.end()) {
result.insert(nums2[i]);
}
}
return vector<int>(result.begin(), result.end());
}
};
- 时间复杂度为O(mn)
- 空间复杂度为O(n)
Leetcode 202.快乐数
题目链接:202.快乐数
思路:求和的过程中,sum会重复出现,因此这道题目使用哈希法,来判断这个sum是否重复出现,如果重复了就是return false, 否则一直找到sum为1为止。判断sum是否重复出现就可以使用unordered_set
哈希法:
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;
}
if(set.find(sum) != set.end()) {
return false;
} else {
set.insert(sum);
}
n = sum;
}
}
};
- 时间复杂度为O(logn)
- 空间复杂度为O(logn)
Leetcode 1.两数之和
题目链接:1.两数之和
暴力法思路:双循环,如果i和j指向的值等于target则返回
使用哈希法得思考四个问题:
- 为什么会想到用哈希表:因为我们需要知道(target - nums[i])这个数是否在之前出现过
- 哈希表为什么用map:数组的大小是受限制的,并且元素很少的话,而哈希值会造成内存空间浪费。set是一个集合,里面放的元素只能是一个key,但我们这道题目需要查找y这个数是否存在,并且需要知道y的数组下标是什么,因为我们返回的不是x和y这两个数而是它们相应的数组下标
- 本题map是用来存什么的:用来存已经遍历过的数
- map中的key和value用来存什么的:key用来存数组的值,value用来存数组的下标
哈希法思路:本题,我们不仅要知道元素有没有遍历过,还要知道这个元素对应的下标,需要使用 key value结构来存放,key来存元素,value来存下标,那么使用map正合适。
暴力法:
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 {-1, -1};
}
};
- 时间复杂度为O(n^2)
- 空间复杂度为O(1)
哈希法:
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 {};
}
};
- 时间复杂度为O(n)
- 空间复杂度为O(n)
今日收获:将哈希表的三种基本形式都应用到题上,希望继续加深对哈希表的理解和实操
学习时长:一个半小时