当我们遇到了要快速判断一个元素是否出现在集合里的时候,就要考虑哈希法。
哈希表Hash table:根据关键码的值而直接进行访问的数据结构
实质:数组就是一张哈希表,哈希表中关键码就是数组的索引下标,通过下标直接访问数组中的元素
解决问题:哈希表都是用来快速判断一个元素是否出现在集合里
哈希函数:hash function:通过hashcode特定编码方式,将其他数据格式转化成不同的数值
哈希碰撞:如果学生数量大于哈希表的大小。拉链法,线性探测法
常见的三种哈希结构
- 数组
- set(集合)
- map(映射)
unordered_set底层实现为哈希表,set和multiset底层实现是红黑树,红黑树是一种平衡二叉搜索树,所以key值是有序的,但不可以修改,改动key会导致整棵树的错乱,所以只能删除和增加
unordered_map底层实现为哈希表,map和multimap的实现是红黑树。map和multimap的key也是有序的。
当使用集合来解决哈希问题的时候,有限使用unordered_set,因为查询和增删效率最优。如果集合有序,采用set,不仅有序还有重复数据,用multiset
map:是一个key val的数据结构,map中对key是有限制的,对value没有限制,因为key的存储方式是用红黑树实现的。
给定两个字符串 s
和 t
,编写一个函数来判断 t
是否是 s
的字母异位词。
注意:若 s
和 t
中每个字符出现的次数都相同,则称 s
和 t
互为字母异位词。
class Solution {
public:
bool isAnagram(string s, string t) {
//构建一个数组26个元素代表a-z,初始化为0
//便利s 将对应元素所在位置+1,遍历t在对应元素-1
//判断是否都为0
int record[26] = {0};
for (int i = 0; i < s.size(); i++) {
//并不需要记住字符的所有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){
return false;
}
}
return true;
}
};
给你两个字符串:ransomNote
和 magazine
,判断 ransomNote
能不能由 magazine
里面的字符构成。
如果可以,返回 true
;否则返回 false
。
magazine
中的每个字符只能在 ransomNote
中使用一次。
class Solution {
public:
bool canConstruct(string ransomNote, string magazine) {
int record[26] = {0};
//如果超出magazine,说明不可能
if (ransomNote.size() > magazine.size()) return false;
//先对magazine遍历
for (int i = 0; i < magazine.size(); i++) {
record[magazine[i] - 'a']++;
}
//再对ransomNote遍历,如果哈希表中发现负数,则false
for (int i = 0; i < ransomNote.size(); i++) {
record[ransomNote[i] - 'a']--;
if (record[ransomNote[i] - 'a'] < 0) {
return false;
}
}
return true;
}
};
给定两个数组 nums1
和 nums2
,返回 它们的交集。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序 。
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
//没有使用数组,是因为没有限制数组的大小
//1.初始化交集
//2.对nums1排序去重
//3.在nums2中找是否存在nums,存在则insert
//4.返回数组
unordered_set<int> result_set;
unordered_set<int> nums_set(nums1.begin(), nums1.end());
for (int num : nums2) {
//判断nums2中是否有nums1
if (nums_set.find(num) != nums_set.end()){
result_set.insert(num);
}
}
return vector<int>(result_set.begin(), result_set.end());
}
};
350. 两个数组的交集 II - 力扣(LeetCode)
给你两个整数数组 nums1
和 nums2
,请你以数组形式返回两数组的交集。返回结果中每个元素出现的次数,应与元素在两个数组中都出现的次数一致(如果出现次数不一致,则考虑取较小值)。可以不考虑输出结果的顺序。
class Solution {
public:
vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
//没有去重,求真正的交集
vector<int> result;
unordered_map<int, int> umap;
for (int num : nums1) {
umap[num]++;
}
for (int num : nums2) {
if (umap[num] > 0) {
//如果在nums2也出现,则在尾部添加元素
result.push_back(num);
//确保只添加一次到交集中
umap[num]--;
}
}
return result;
}
};
编写一个算法来判断一个数 n
是不是快乐数。
「快乐数」 定义为:
- 对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
- 然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
- 如果这个过程 结果为 1,那么这个数就是快乐数。
如果 n
是 快乐数 就返回 true
;不是,则返回 false
。
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) {
//如果sum重复出现则表示无限循环了,return false
unordered_set<int> set;
while(1) {
int sum = getSum(n);
if (sum == 1) {
return true;
}
//如果在set中发现了num,则返回false
if (set.find(sum) != set.end()) {
return false;
} else {
set.insert(sum);
}
n = sum;
}
}
};
给定一个整数数组 nums
和一个整数目标值 target
,请你在该数组中找出 和为目标值 target
的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
//可以用key保存数值,value保存数字
//遍历元素,在map中寻找是否有匹配的key,如果没找到,就把访问过的元素和下标加入map中
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
map.insert(pair<int, int> (nums[i], i));
}
return {};
}
};
给你四个整数数组 nums1
、nums2
、nums3
和 nums4
,数组长度都是 n
,请你计算有多少个元组 (i, j, k, l)
能满足:
0 <= i, j, k, l < n
nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0
class Solution {
public:
int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
unordered_map<int, int> umap;
//遍历nums1和nums2,统计两个元素之和,和出现次数,放到umap中
for (int n1 : nums1){
for (int n2 : nums2){
umap[n1 + n2]++;
}
}
//记录n1+n2+n3+n4=0出现的次数
int count = 0;
//遍历num3+nums4
for (int n3 : nums3) {
for (int n4 : nums4) {
if (umap.find(0 - (n3 + n4)) != umap.end()) {
count += umap[0 - (n3 + n4)];
}
}
}
return count;
}
};
给你一个整数数组 nums
,判断是否存在三元组 [nums[i], nums[j], nums[k]]
满足 i != j
、i != k
且 j != k
,同时还满足 nums[i] + nums[j] + nums[k] == 0
。请
你返回所有和为 0
且不重复的三元组。
注意:答案中不可以包含重复的三元组。
双指针法
首先将数组排序,有一层for循环,i从下标0的地方开始,同时定一个下标left定义在i+1的位置,定义right在数组结尾的位置。
想要在数组中找到abc使得a+b+c=0,如果nums[i]+nums[left]+nums[right] > 0,right向左移动,如果<0,left向右移动,知道left和right相遇
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> result;
sort(nums.begin(), nums.end());
for (int i = 0; i < nums.size(); i++) {
if (nums[i] > 0) {
return result;
}
//去重a
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
int left = i + 1;
int right = nums.size() - 1;
while(right > left) {
if (nums[i] + nums[left] + nums[right] > 0) right--;
else if (nums[i] + nums[left] + nums[right] < 0) left++;
else {
result.push_back(vector<int>{nums[i], nums[left], nums[right]});
//去重 left和right
while (right > left && nums[right] == nums[right-1]) right--;
while (right > left && nums[left] == nums[left+1]) left++;
right--;
left++;
}
}
}
return result;
}
};
class Solution {
public:
vector<vector<int>> fourSum(vector<int>& nums, int target) {
vector<vector<int>> result;
//排序
sort(nums.begin(), nums.end());
for (int k = 0; k < nums.size(); k++) {
//剪枝
if (nums[k] > target && nums[k] >= 0) {
break;
}
//去重
if (k > 0 && nums[k] == nums[k - 1]) {
continue;
}
for (int i = k + 1; i < nums.size(); i++) {
if (nums[i] + nums[k] > target && nums[k] + nums[i] >= 0) {
break;
}
if (i > k + 1 && nums[i] == nums[i - 1]) {
continue;
}
int left = i + 1;
int right = nums.size() - 1;
while(right > left) {
if ((long)nums[k] + nums[i] + nums[left] + nums[right] > target) right--;
else if ((long)nums[k] + nums[i] + nums[left] + nums[right] < target) left++;
else {
result.push_back(vector<int>{nums[k], nums[i], nums[left], nums[right]});
while (right > left && nums[right] == nums[right-1]) right--;
while (right > left && nums[left] == nums[left+1]) left++;
right--;
left++;
}
}
}
}
return result;
}
};