哈希表是根据关键码的值而直接进行访问的数据结构。
快速判断一个元素是否出现集合里。
拉链法与线性探测法。
数组
set(集合)
map(映射)
集合 | 底层实现 | 是否有序 | 数值是否可以重复 | 能否更改数值 | 查询效率 | 增删效率 |
---|---|---|---|---|---|---|
std::set | 红黑树 | 有序 | 否 | 否 | O(logn) | O(logn) |
std::multiset | 红黑树 | 有序 | 是 | 否 | O(logn) | O(logn) |
std::unordered_set | 哈希表 | 无序 | 否 | 否 | O(1) | O(1) |
std::unordered_set底层实现为哈希表,std::set 和std::multiset 的底层实现是红黑树,红黑树是一种平衡二叉搜索树,所以key值是有序的,但key不可以修改,改动key值会导致整棵树的错乱,所以只能删除和增加。
映射 | 底层实现 | 是否有序 | 数值是否可以重复 | 能否更改数值 | 查询效率 | 增删效率 |
---|---|---|---|---|---|---|
std::map | 红黑树 | key有序 | key不可重复 | key不可修改 | O(logn) | O(logn) |
std::multimap | 红黑树 | key有序 | key可重复 | key不可修改 | O(logn) | O(logn) |
std::unordered_map | 哈希表 | key无序 | key不可重复 | key不可修改 | O(1) | O(1) |
std::unordered_map 底层实现为哈希表,std::map 和std::multimap 的底层实现是红黑树。同理,std::map 和std::multimap 的key也是有序的。
虽然std::set、std::multiset 的底层实现是红黑树,不是哈希表,但是std::set、std::multiset 依然使用哈希函数来做映射,只不过底层的符号表使用了红黑树来存储数据,所以使用这些数据结构来解决映射问题的方法,我们依然称之为哈希法。 map也是一样的道理。
题目
给你一个字符串数组,请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。
字母异位词 是由重新排列源单词的字母得到的一个新单词,所有源单词中的字母都恰好只用一次。
来源:力扣(LeetCode) 链接:力扣
解法一 利用素数标志特征值
思路
建立一个长度26数组存储最小的26个素数,对应26个英文字母;
设置一个flag
数组用于记录每一类字符串的特征值(字符串所有字母对应素数之积),其下标与最终结果每一类字符创下标对应;
从字符串数组开始计算每个字符串的特征值(每个字母对应素数之积),而后在flag
数组中查找该特征值,如若找到则将该字符串添加到flag
值对应下标的最终结果字符串类,如果没有找到,则在最终结果字符串类中创建一个新字符串类,并且在flag
数组中添加相同对应下标的特征值,保证result
每一类字符串与flag
数组特征值对应下标相同实现也十分简单,只需两者同步更新添加,并且都向最后添加新创建的字符串类即可;
代码
class Solution { public: vector> groupAnagrams(vector & strs) { vector > result; vector flag; //记录每一类字符串的特征值(所有字母对应素数之积) int big_sushu = 1e9+7; //一个比较大的素数 int sushu[]={2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67, 71, 73,79,83,89,97,101}; //26个素数对应26个英文字母 for(int i=0; i temp; temp.push_back(strs[i]); result.push_back(temp); } } return result; } };
解法二 哈希表
思路
创建一个map
,其值用来存储同一类型字符串数组,其key
值为按字母升序排序后字符串;
遍历字符串数组,将其按字母升序排列后作为特征值,从map
中匹配特征值并添加到相应类;
将map
中的每个值添加到最终字符串数组中;
代码
class Solution { public: vector> groupAnagrams(vector & strs) { vector > res; unordered_map > map; for(int i=0;i 2. LeetCode438. 找到字符串中所有字母异位词
题目
给定两个字符串
s
和p
,找到s
中所有p
的 异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。异位词 指由相同字母重排列形成的字符串(包括相同的字符串)。
思路(滑动窗口)
设置两个长度为26的数组,一个用于存储目标字符串字符,一个用于记录滑动窗口内字符个数
设置
left
与right
并且窗口长度保持为目标字符串长度,left
从长字符串左端开始移动,right
随left
右移随之右移,每移动一次进行一次判断,成功匹配则记录left
值。代码
class Solution { public: vectorfindAnagrams(string s, string p) { if(s.size() freq_s(26, 0), freq_p(26, 0), res; for( int i = 0 ; i < p.size() ; i++ ){ freq_p[p[i] - 'a' ]++; freq_s[s[++r] - 'a' ]++; } if ( freq_s == freq_p ) res.push_back( l ); // 固定长度的滑动窗口 while( r < s.size()-1 ){ freq_s[s[++r] - 'a' ]++; freq_s[s[l++] - 'a' ]--; if ( freq_s == freq_p ) res.push_back( l ); } return res; } }; 3. LeetCode202. 快乐数
题目
编写一个算法来判断一个数 n 是不是快乐数。
「快乐数」定义为:
对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。 然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。 如果 可以变为 1,那么这个数就是快乐数。 如果 n 是快乐数就返回 true ;不是,则返回 false
来源:力扣(LeetCode) 链接:力扣
思路
首先编写一个求取一个数各个位平方和函数;
设置一个
vector
变量用于记录每步中变量的值,若新计算得到的变量位于vector
中,则说明计算发生循环,进而最初的数不是快乐数,如果在计算过程中判断变量与1相等,说明最初的数为快乐数。代码
class Solution { public: int Get_num(int num){ int sum=0; while(num){ sum += (num%10)*(num%10); num /= 10; } return sum; } bool isHappy(int n) { unordered_setset; while(1){ int a = Get_num(n); if(a == 1) return true; if(set.find(a) != set.end()) return false; else set.insert(a); n = a; } } }; 4. Leetcode1.两数之和
题目
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。
来源:力扣(LeetCode) 链接:力扣
思路
创建一个map映射,其key存储数组某一项,其value为其下标;
顺序访问数组,在map中查找
target-nums[i]
,如果找到则返回当前下标与map对应项的第二项值,如果没有找到则将数组当前项值与下标存储进map;如果找到则中途return,若未找到则最后返回空;
之所以可以边加入边寻找有一个关键点,即两个数之和为一个定值,则这两个数互相寻找对方值是一个对称关系,则在找到其中一个数时,另一个数未出现不返回,而在找到第二个数时,第一个数已经存在,所以可以返回。
代码
class Solution { public: vectortwoSum(vector & nums, int target) { unordered_map map; for(int i=0; i second, i}; } else{ map.insert(pair (nums[i], i)); } } return {}; } }; 5. Leetcode454.四数相加 2
题目
给你四个整数数组 nums1、nums2、nums3 和 nums4 ,数组长度都是 n ,请你计算有多少个元组 (i, j, k, l) 能满足:
0 <= i, j, k, l < n
nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0
来源:力扣(LeetCode) 链接:力扣
思路
此题元组默认可以重复,所以可以使用以下方法求解;
设置一个
map
映射,其key
为nums1
与nums2
中分别取一数之和,其value
为这两数和出现的次数;两层循环遍历
nums1
与nums2
完善map
映射;两层循环遍历
nums3
与nums4
,在map
中寻找其和的相反数,如果找到则说明最终匹配数增加map
中所找项的value
。代码
class Solution { public: int fourSumCount(vector& nums1, vector & nums2, vector & nums3, vector & nums4) { unordered_map map; for(int a:nums1){ for(int b:nums2){ map[a+b]++; } } int count=0; for(int c:nums3){ for(int d:nums4){ if(map.find(0-(c+d)) != map.end()){ count += map[0-(c+d)]; } } } return count; } }; 6. LeetCode15.三数之和
题目
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组。
来源:力扣(LeetCode) 链接:力扣
思路(滑动窗口)
首先对nums数组进行排序,使得后面可以对搜索进行剪枝操作;
外层设置大循环,为遍历a元素,而在每次循环中,对a之后元素设置
left=i+1
与right=nums.size()-1
,在left
情况下,进行 left++
与right--
使得可以遍历所有三元组;循环中的剪枝操作
在a的每次循环时,如果
a>0
,则a之后的left
与right
一定大于0,则不可能找到和为0的元组,直接返回;同时如果a与上次循环a相同,则可认为上一次循环已经找出本次循环所有可能解,如果继续寻找则会发生三元组重复(由于
nums
已经排序,所以只需比较上次);在每次找到符合要求的三元组时,
left
与right
都需要移动至与原来值不同的位置,否则如果仅一个移动,根据三数之和为0,其实另一个数也是确定的,会导致三元组重复;代码
class Solution { public: vector> threeSum(vector & nums) { vector >result; sort(nums.begin(), nums.end()); for(int i=0; i 0) return result; if(i>0 && nums[i] == nums[i-1]){ continue; } int left = i+1; int right = nums.size()-1; while(left 0){ right--; }else if(nums[i]+nums[left]+nums[right] < 0){ left++; }else{ result.push_back(vector {nums[i], nums[left], nums[right]}); while(left < right && nums[right]==nums[right-1]) right--; while(left < right && nums[left]==nums[left+1]) left++; right--; left++; } } } return result; } }; 7. LeetCode18.四数之和
题目
给你一个由 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 你可以按 任意顺序 返回答案 。
来源:力扣(LeetCode) 链接:力扣
思路
整体思路与三数之和大致相同,比之增加一层循环,不过有些注意点与三数之和稍有不同;
首先每次循环中不能再判断
a>0
就直接返回,因为本次target
为任意值,当然也不能判断a>target
进行返回,这同样会有问题,因为target
可能为负数,当已判断a>target
时,a之后的负数与a这个负数相加仍然可以等于target值,不能进行剪枝操作;同时注意,四个数分别可能并未超过int最大表示范围,但之和可能超过,需要强制类型转化进行判断操作;
从本题中还发现一个重要点,对于vector的size()函数返回值为
unsigned int
类型,当nums.size()
值为1时,采用语句nums.size()-2
会变为一个非常大的数,是因为nums.size()-2
这个表达式值类型与nums.size()
类型相同,当其变为负数时,实际在内存中存储的为该数的补码,而由于类型仍然为unsigned int
,读取方式仍以此类型,所以读取后所得数值会很大。代码
class Solution { public: vector> fourSum(vector & nums, int target) { vector > result; sort(nums.begin(), nums.end()); for(int i=0; i 0 && nums[i]==nums[i-1]) continue; for(int j=i+1; j<(int)nums.size()-2; j++){ if(j>i+1 && nums[j]==nums[j-1]) continue; int left = j+1; int right = nums.size()-1; while(left < right){ if((long long) nums[i]+nums[j]+nums[left]+nums[right] > target) right--; else if((long long) nums[i]+nums[j]+nums[left]+nums[right] < target) left++; else{ result.push_back(vector {nums[i], nums[j], nums[left], nums[right]}); while(left