哈希表概念
哈希表也叫散列表,哈希表是一种数据结构,它提供了快速的插入操作和查找操作,无论哈希表总中有多少条数据,插入和查找的时间复杂度都是为O(1),因为哈希表的查找速度非常快,所以在很多程序中都有使用哈希表,例如拼音检查器。
哈希表也有自己的缺点,哈希表是基于数组的,我们知道数组创建后扩容成本比较高,所以当哈希表被填满时,性能下降的比较严重。
哈希表采用的是一种转换思想,其中一个中要的概念是如何将「键」或者「关键字」转换成数组下标?在哈希表中,这个过程有哈希函数来完成,但是并不是每个「键」或者「关键字」都需要通过哈希函数来将其转换成数组下标,有些「键」或者「关键字」可以直接作为数组的下标。我们先来通过一个例子来理解这句话。
我们上学的时候,大家都会有一个学号「1-n号」中的一个号码,如果我们用哈希表来存放班级里面学生信息的话,我们利用学号作为「键」或者「关键字」,这个「键」或者「关键字」就可以直接作为数据的下标,不需要通过哈希函数进行转化。如果我们需要安装学生姓名作为「键」或者「关键字」,这时候我们就需要哈希函数来帮我们转换成数组的下标。
以c++代码为例:
这里重点在于理解哈希表内的值通过键值对应,是一个根据关键码值(Key value)而直接进行访问的数据结构。
1.两数之和
给定一个整数数组 nums
和一个整数目标值 target
,请你在该数组中找出 和为目标值 target
的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。
示例 1:
输入:nums = [2,7,11,15], target = 9 输出:[0,1] 解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。
不难想到用哈希表存储数组,具体步骤为:
1.遍历数组,找用target-nums[i]在哈希表中是否有对应值
2.若有,输出两个下标
3.若没有,将当前nums[i](值)以及i(键值)存到哈希表中,以便后续的查找
代码如下:
#include <iostream>
#include <vector>
#include <unordered_map>
using namespace std;
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
unordered_map<int, int> hashtable;
for(int i = 0; i < nums.size(); i++) {
auto it = hashtable.find(target - nums[i]);
if(it != hashtable.end()) {
return {it->second, i};
}
hashtable[nums[i]] = i;
}
return {};
}
};
int main() {
vector<int> nums = {2, 7, 11, 15}; // 注意使用大括号来初始化向量
int target = 9;
Solution solution;
vector<int> result = solution.twoSum(nums, target);
if (!result.empty()) {
cout << “[”<<result[0] << "," << result[1] << "]"<<endl;
} else {
cout << "No solution found" << endl;
}
return 0;
}
49.字母异位词分组
给你一个字符串数组,请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。
字母异位词 是由重新排列源单词的所有字母得到的一个新单词。
示例 1:
输入: strs = ["eat", "tea", "tan", "ate", "nat", "bat"]
输出: [["bat"],["nat","tan"],["ate","eat","tea"]]
示例 2:
输入: strs = [""]
输出: [[""]]
示例 3:
输入: strs = ["a"]
输出: [["a"]]
提示:
1 <= strs.length <= 104
0 <= strs[i].length <= 100
strs[i]
仅包含小写字母
思路概括:建立哈希表来存取这些字母词,对于每一个进行遍历排序得到一个键值,那么形如ate与eat这种只会有一个对应的键值,再将这些异位词添加到对应键值下。最后新建一个新的数组,依据键值输出这些异位词。
代码:
#include <iostream>
#include <vector>
#include <string>
#include <unordered_map>
#include <algorithm>
using namespace std;
class Solution {
public:
vector<vector<string>> groupAnagrams(vector<string>& strs) {
unordered_map<string, vector<string>> mp;
for(auto& str : strs){
string key = str;
sort(key.begin(), key.end());
mp[key].emplace_back(str);
}
vector<vector<string>> ans;
for(auto it = mp.begin(); it != mp.end(); it++){
ans.emplace_back(it->second);
}
return ans;
}
};
int main() {
vector<string> strs = {"eat", "tea", "tan", "ate", "nat", "bat"};
Solution solution;
vector<vector<string>> results = solution.groupAnagrams(strs);
// 输出结果
cout << "[";
for (size_t i = 0; i < results.size(); ++i) {
cout << "[";
for (size_t j = 0; j < results[i].size(); ++j) {
cout << "\"" << results[i][j] << "\"";
if (j != results[i].size() - 1) {
cout << ",";
}
}
cout << "]";
if (i != results.size() - 1) {
cout << ",";
}
}
cout << "]" << endl;
return 0; //坦白说这样输出确实有点麻烦,但一般面试手撕不会在力扣上写,所以遇到只能认了
}
128.最长连续序列
给定一个未排序的整数数组 nums
,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。
请你设计并实现时间复杂度为 O(n)
的算法解决此问题。
示例 1:
输入:nums = [100,4,200,1,3,2]
输出:4
解释:最长数字连续序列是 [1, 2, 3, 4]。它的长度为 4。
示例 2:
输入:nums = [0,3,7,2,5,8,4,6,0,1] 输出:9
提示:
0 <= nums.length <= 105
-109 <= nums[i] <= 109
这里使用的算法思想是利用哈希表来实现对整数数组中的连续序列的查找。具体来说,算法思路如下:
-
**构建哈希表:**首先,我们构建一个哈希表,用来存储每个整数以及与之相连的连续序列的长度。哈希表的键是整数,值是该整数所在连续序列的长度。
-
**遍历整数数组:**然后,我们遍历整数数组中的每个整数,对于每个整数,我们进行如下操作:
- 如果当前整数已经在哈希表中存在,说明已经处理过该整数,跳过;
- 否则,我们找出该整数左侧和右侧相连的整数,并获取它们所在连续序列的长度,然后计算包含当前整数的连续序列的长度;
- 接着,我们更新哈希表中当前整数及其左右相邻整数所在序列的长度信息。
-
**寻找最长连续序列:**在遍历过程中,我们不断更新哈希表中的序列长度信息,同时记录下最长的连续序列长度。最终,遍历完成后,我们就得到了整数数组中的最长连续序列的长度。
这个算法的核心思想是利用哈希表记录每个整数及其所在连续序列的长度,从而在线性时间复杂度内找出最长的连续序列。通过哈希表,我们可以快速地查找整数的左右相邻整数,并更新它们所在序列的长度信息,从而在遍历过程中实现对连续序列的查找和更新。
代码:
#include <iostream>
#include <vector>
#include <unordered_map>
using namespace std;
class Solution {
public:
int longestConsecutive(vector<int>& nums) {
unordered_map<int,int> m;
int result = 0;
for(int n : nums){
// 检查是否已经处理过该整数,如果是,则跳过
if(m.find(n) != m.end()) continue;
// 查找当前整数左侧相连序列的长度
int left = m.find(n-1) == m.end() ? 0 : m[n-1];
// 查找当前整数右侧相连序列的长度
int right = m.find(n+1) == m.end() ? 0 : m[n+1];
// 计算包含当前整数的连续序列的长度
int len = left + right + 1;
// 将当前整数添加到哈希表中,并更新相邻整数的序列长度信息
m[n] = 1;
m[n-left] = m[n+right] = len;
// 更新最长连续序列的长度
result = max(result, len);
}
return result;
}
};
int main(){
vector<int> nums = {100, 101, 102, 103, 104};
Solution solution;
int res = solution.longestConsecutive(nums);
cout << res << endl;
return 0;
}