哈希表
文章目录
定义
1.哈希表是根据关键码的值而直接进行访问的数据结构,一般用来快速判断一个元素是否出现集合里。
2.哈希函数是将关键词映射到哈希表上的索引。
例如将学生信息放入哈希表中,(通过哈希函数把名字转化为数值,一般哈希函数是通过特定编码方式,可以将其他数据格式转化为不同的数值,这样就把学生名字映射为哈希表上的索引数字了。)
注:如果哈希函数得到的数值大于 哈希表的大小了,此时为了保证映射出来的索引数值都落在哈希表上,我们会在再次对数值做一个取模的操作,就要我们就保证了学生姓名一定可以映射到哈希表上了。(不止只有取模操作,还有平方取中法,折叠法等)
3.哈希碰撞:如果关键词数量大于哈希表的大小,此时就算哈希函数计算的再均匀,也避免不了会有几个关键词同时映射到哈希表 同一个索引下标的位置。这就是哈希碰撞。
4.解决哈希碰撞方法:①拉链法:发生冲突的元素都被存储在链表中。
②线性探测法:使用线性探测法,一定要保证tableSize大于dataSize。 我们需要依靠哈希表中的空位来解决碰撞问题。
例如冲突的位置,放了小李,那么就向下找一个空位放置小王的信息。所以要求tableSize一定要大于dataSize ,要不然哈希表上就没有空置的位置来存放 冲突的数据了。
5.常见哈希数据结构:数组,set,map
集合 | 底层实现 | 是否有序 | 数值是否可以重复 | 能否更改数值 | 查询效率 | 增删效率 |
---|---|---|---|---|---|---|
std::set | 红黑树 | 有序 | 否 | 否 | O(log n) | O(log n) |
std::multiset | 红黑树 | 有序 | 是 | 否 | O(logn) | O(logn) |
std::unordered_set | 哈希表 | 无序 | 否 | 否 | O(1) | O(1) |
映射 | 底层实现 | 是否有序 | 数值是否可以重复 | 能否更改数值 | 查询效率 | 增删效率 |
---|---|---|---|---|---|---|
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) |
6.总结:当我们遇到了要快速判断一个元素是否出现集合里的时候,就要考虑哈希法。
但是哈希法也是牺牲了空间换取了时间,因为我们要使用额外的数组,set或者是map来存放数据,才能实现快速的查找。
相关题目
1.有效的字母异位词
242.有效的字母异位词
给定两个字符串 *s*
和 *t*
,编写一个函数来判断 *t*
是否是 *s*
的字母异位词。
**注意:**若 *s*
和 *t*
中每个字符出现的次数都相同,则称 *s*
和 *t*
互为字母异位词。
1.暴力for
class Solution {
public:
bool isAnagram(string s, string t) {
bool isOrNot = true;
if(s.size() != t.size())return false;
for(int i = 0;i < s.size();i++){
for(int j = 0;j< t.size();j++){
if(s[i] == t[j]) {
isOrNot = true;
t.erase(j,1);
break;
}
else isOrNot = false;
}
if(isOrNot == false)return false;
}
return true;
}
}
2.哈希表
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;
}
};
383.救赎信
给你两个字符串:ransomNote
和 magazine
,判断 ransomNote
能不能由 magazine
里面的字符构成。
如果可以,返回 true
;否则返回 false
。
magazine
中的每个字符只能在 ransomNote
中使用一次。
class Solution {
public:
bool canConstruct(string ransomNote, string magazine) {
int record[26] = {0};
if(ransomNote.size() > magazine.size())return false;
for(int i = 0;i < magazine.size();i++) record[magazine[i] - 'a']++;
for(int i = 0;i < ransomNote.size();i++) record[ransomNote[i] - 'a']--;
for(int i = 0;i < 26;i++){
if(record[i] < 0)return false;
}
return true;
}
};
*49.字母异位词分组
给你一个字符串数组,请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。
字母异位词 是由重新排列源单词的字母得到的一个新单词,所有源单词中的字母通常恰好只用一次。
1.哈希
class Solution {
public:
vector<vector<string>> groupAnagrams(vector<string>& strs){
map<string,vector<string>> mp;
vector<vector<string>>record;
for(string& s:strs){
string str(26, ' ');
for(char val:s){
str[val - 'a']++;
}
mp[str].emplace_back(s);//emplace_back() 和 push_back() 的区别,就在于底层实现的机制不同。push_back() 向容器尾部添加元素时,首先会创建这个元素,然后再将这个元素拷贝或者移动到容器中(如果是拷贝的话,事后会自行销毁先前创建的这个元素);而 emplace_back() 在实现时,则是直接在容器尾部创建这个元素,省去了拷贝或移动元素的过程。
}
for(auto val:mp){
record.emplace_back(val.second);
}
return record;
}
};。
2.哈希加排序
class Solution {
public:
vector<vector<string>> groupAnagrams(vector<string>& strs){
map<string,vector<string>> mp;
vector<vector<string>>record;
for(string& s:strs){
string key = s;
sort(key.begin(),key.end());
mp[key].emplace_back(s);
}
for(auto& val:mp){
record.emplace_back(val.second);
}
return record;
}
};
*438. 找到字符串中所有字母异位词
给定两个字符串 s
和 p
,找到 s
中所有 p
的 异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。
异位词 指由相同字母重排列形成的字符串(包括相同的字符串)。
滑动窗口解决
class Solution {
public:
vector<int> findAnagrams(string s, string p) {
vector<int>v;
int record[26] = {0};
for(int i = 0;i < p.size();i++){
record[p[i] - 'a']++;
}
for(int slowIndex = 0,fastIndex=0;fastIndex < s.size();fastIndex++){
record[s[fastIndex] - 'a']--;
while(record[s[fastIndex] - 'a'] < 0){
record[s[slowIndex] - 'a']++;
slowIndex++;
}
if(fastIndex - slowIndex + 1 == p.size()) v.push_back(slowIndex);
}
return v;
}
};
2.查找两个数组交集
349. 两个数组的交集
给定两个数组 nums1
和 nums2
,返回 它们的交集 。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序 。
示例 1:
输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2]
1.暴力for
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
vector<int>record;
sort(nums1.begin(),nums1.end());
for(int i = 0;i < nums1.size();i++){
if(i > 0 && nums1[i] == nums1[i - 1]) continue;
for(int j = 0;j < nums2.size();j++){
if(nums1[i] == nums2[j]){
record.emplace_back(nums1[i]);
break;
}
}
}
return record;
}
};
2.利用set容器 利用count算法也可以
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
vector<int>record;
set<int>s1;
set<int>s2;
for(int i = 0;i < nums1.size();i++){//set里面自动排序,且无重发
s1.emplace(nums1[i]);
}
for(int i = 0;i < nums2.size();i++){
s2.emplace(nums2[i]);
}
set<int>::iterator it = s1.begin();
while(it != s1.end()){
if(s2.find(*it) != s2.end()){//利用find算法
record.emplace_back(*it);
}
it++;
}
return record;
}
};
3.双指针加排序
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
vector<int>record;
sort(nums1.begin(),nums1.end());
sort(nums2.begin(),nums2.end());
int nums1Index = 0,nums2Index = 0;
while(nums1Index < nums1.size() && nums2Index < nums2.size()){
if(nums1[nums1Index] == nums2[nums2Index]){
if (record.size() == 0 || nums1[nums1Index] != nums1[nums1Index - 1]) {
record.push_back(nums1[nums1Index]);
}
nums1Index++;
nums2Index++;
}
else if(nums1[nums1Index] > nums2[nums2Index]){
nums2Index++;
}
else nums1Index++;
}
return record;
}
};
350.两个数组的交集 II
给你两个整数数组 nums1
和 nums2
,请你以数组形式返回两数组的交集。返回结果中每个元素出现的次数,应与元素在两个数组中都出现的次数一致(如果出现次数不一致,则考虑取较小值)。可以不考虑输出结果的顺序。
示例 1:
输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2,2]
双指针加排序
class Solution {
public:
vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
sort(nums1.begin(),nums1.end());
sort(nums2.begin(),nums2.end());
vector<int>record;
int nums1Index = 0,nums2Index = 0;
while(nums1Index < nums1.size() && nums2Index < nums2.size()){
if(nums1[nums1Index] == nums2[nums2Index]){
record.emplace_back(nums1[nums1Index]);
nums1Index++;
nums2Index++;
}
else if(nums1[nums1Index] < nums2[nums2Index]){
nums1Index++;
}
else nums2Index++;
}
return record;
}
};
3.快乐数
202. 快乐数
编写一个算法来判断一个数 n
是不是快乐数。
「快乐数」 定义为:
- 对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
- 然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
- 如果这个过程 结果为 1,那么这个数就是快乐数。
如果 n
是 快乐数 就返回 true
;不是,则返回 false
。
示例 1:
输入:n = 19
输出:true
解释:
12 + 92 = 82
82 + 22 = 68
62 + 82 = 100
12 + 02 + 02 = 1
1.递归
class Solution {
public:
vector<int> v;
bool isHappy(int n) {
int sum = 0;
while(n){
sum = sum + (n % 10) * (n % 10);
n /= 10;
}
if(find(v.begin(),v.end(),sum) != v.end()) return false;
v.emplace_back(sum);
if(sum == 1){
return true;
}
else{
return isHappy(sum);
}
}
};
2.换成set集合,利用本身find函数
3.利用快慢指针,快指针每次进行两次运算
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 slowSum = n;
int fastSum = n;
do{
slowSum = getSum(slowSum);
fastSum = getSum(getSum(fastSum));
}while(slowSum != fastSum);
return slowSum == 1;
}
};
4.两数之和
1.两数之和
给定一个整数数组 nums
和一个整数目标值 target
,请你在该数组中找出 和为目标值 target
的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。
示例 1:
输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。
1.暴力for都是O(n^2)时间复杂度
2.利用unordered_map来记录
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
vector<int> record;
unordered_map<int,int>m1;
for(int i = 0; i < nums.size();i++){
auto it = m1.find(target - nums[i]);
if(it != m1.end()){
record.emplace_back(it->second);
record.emplace_back(i);
return record;
}
m1[nums[i]] = i;
}
return record;
}
};
5.四数之和
*454. 四数相加 II
给你四个整数数组 nums1
、nums2
、nums3
和 nums4
,数组长度都是 n
,请你计算有多少个元组 (i, j, k, l)
能满足:
0 <= i, j, k, l < n
nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0
示例 1:
输入:nums1 = [1,2], nums2 = [-2,-1], nums3 = [-1,2], nums4 = [0,2]
输出:2
解释:
两个元组如下:
1. (0, 0, 0, 1) -> nums1[0] + nums2[0] + nums3[0] + nums4[1] = 1 + (-2) + (-1) + 2 = 0
2. (1, 1, 0, 0) -> nums1[1] + nums2[1] + nums3[0] + nums4[0] = 2 + (-1) + (-1) + 0 = 0
1.哈希加分组
class Solution {
public:
unordered_map<int,int>twoSum(vector<int>& nums1, vector<int>& nums2){
unordered_map<int,int>twoSum;
for(int i = 0;i < nums1.size();i++){
for(int j = 0;j < nums2.size();j++){
twoSum[nums1[i] + nums2[j]]++;
}
}
return twoSum;
}
int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
int sum = 0;
unordered_map<int,int>twoSum1 = twoSum(nums1,nums2);
unordered_map<int,int>twoSum2 = twoSum(nums3,nums4);
for(auto val:twoSum2){
if(twoSum1.find(-(val.first)) != twoSum1.end()){
sum += val.second * twoSum1[-(val.first)];
}
}
return sum;
}
};
2.改进
class Solution {
public:
int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
int sum = 0;
unordered_map<int,int>twoSum1;
for(int i = 0;i < nums1.size();i++){
for(int j = 0;j < nums2.size();j++){
twoSum1[nums1[i] + nums2[j]]++;
}
}
unordered_map<int,int>twoSum2;
for(int i = 0;i < nums1.size();i++){
for(int j = 0;j < nums2.size();j++){
if(twoSum1.find(-(nums3[i] + nums4[j])) != twoSum1.end()) sum += twoSum1[-(nums3[i] + nums4[j])];
}
}
return sum;
}
};
6.三数之和
15. 三数之和
给你一个整数数组 nums
,判断是否存在三元组 [nums[i], nums[j], nums[k]]
满足 i != j
、i != k
且 j != k
,同时还满足 nums[i] + nums[j] + nums[k] == 0
。请
你返回所有和为 0
且不重复的三元组。
**注意:**答案中不可以包含重复的三元组。
示例 1:
输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
解释:
nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。
nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。
nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。
不同的三元组是 [-1,0,1] 和 [-1,-1,2] 。
注意,输出的顺序和三元组的顺序并不重要。
1.暴力或者O(n^3)时间复杂度通过不了时间
2.双指针法加排序
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>>threeSum;
sort(nums.begin(),nums.end());
for(int i = 0;i < nums.size();i++){
if(i > 0 && nums[i] == nums[i - 1]) continue;//去重,跟前面数一样就重复了
if(nums[i] > 0)break;//最小已经大于0就结束了
int left = i + 1,right = nums.size() - 1;
while(left < right){
if(nums[i] + nums[left] + nums[right] == 0){
vector<int>v = {nums[i],nums[left],nums[right]};
threeSum.emplace_back(v);
left++;
right--;
while(left < right && nums[left] == nums[left - 1]) left++;//必须加left < right,不然left可能会大于size();right可能会小于0
while(left < right && nums[right] == nums[right + 1])right--;
}
else if(nums[i] + nums[left] + nums[right] > 0){
right--;
}
else left++;
}
}
return threeSum;
}
};
3.哈希,太过于麻烦,出错太多
7.四数之和
18. 四数之和
给你一个由 n
个整数组成的数组 nums
,和一个目标值 target
。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]]
(若两个四元组元素一一对应,则认为两个四元组重复):
0 <= a, b, c, d < n
a
、b
、c
和d
互不相同nums[a] + nums[b] + nums[c] + nums[d] == target
你可以按 任意顺序 返回答案 。
1.双指针加排序,比三数之和多一个循环
class Solution {
public:
vector<vector<int>> fourSum(vector<int>& nums, int target) {
vector<vector<int>> fourSum;
if(nums.size() < 4)return fourSum;
sort(nums.begin(),nums.end());
int i = 0,j = nums.size() - 1;
long sum = 0;
while(i < j){
if(i > 0 && nums[i] == nums[i - 1]) {//防止重复
i++;
continue;
}
if(nums[i] > target && nums[i] >= 0 )break;//四个数中最小都大于target就直接退出,当target小于0时,最小数大于0就退出
while(j > i){
if(j < nums.size() - 1 && nums[j] == nums[j + 1]){
j--;
continue;
}
int left = i + 1,right = j - 1;
while(left < right){
sum = long(nums[i]) + long(nums[left]) + long(nums[right]) + long(nums[j]); //会溢出
if(sum == long(target)){
vector<int> v = {nums[i],nums[left],nums[right],nums[j]};
fourSum.emplace_back(v);
left++;
right--;
while(left < right && nums[left] == nums[left - 1])left++;
while(left < right && nums[right] == nums[right + 1])right--;
}
else if(sum > long(target)){
right--;
}
else {
left++;
}
}
j--;
}
i++;
j = nums.size() - 1;
}
return fourSum;
}
};