多一些Aha Time,发现技术的美妙🍺|

啊原来是这样呀

园龄:8年3个月粉丝:3关注:9

【LeetCode】4.哈希表与部分双指针系列

总目录:

LeetCode系列导航目录

 

0.理论基础

0.1.要点

哈希表,散列表,HashTable,一种key-value集合,最重要的是其查找速度为O(1);

哈希函数,建立key和哈希地址的映射关系,哈希函数有不同的实现方式;

哈希冲突/碰撞是指不同的key产生的哈希地址重复,有不同的策略处理这种问题,但不在此考虑;

常见的哈希结构:数组、set集合、map键值对集合;

0.2.比较

 

set和multiset的底层实现是红黑树(平衡搜索二叉树),有序,multiset允许重复

unordered_set的底层实现为哈希表,无序

 

 

 map和multimap的底层实现是红黑树,有序,multimap允许重复

unordered_map的底层实现是哈希表,无序

 

使用选择:

当需要使用集合来解决查询问题时,优先考虑unordered,

如果需要集合是有序的就用基础的set/map,如果还要求允许重复则使用multi,

 

所谓哈希法

虽然set\multiset\map\multimap使用红黑树来存储数据,但也使用哈希函数来做映射,对于这种使用数据结构来解决

映射问题的方法统称为哈希法。

0.3.总结

当需要快速判断一个元素是否在集合中时,考虑哈希法。

哈希法是牺牲了空间换时间,因为要开辟额外的空间实现某种有序才能实现快速的查找。

 

1.有效的字母异位词

1.1.问题描述

给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。
注意:若 s 和 t 中每个字符出现的次数都相同,则称 s 和 t 互为字母异位词。
链接:https://leetcode.cn/problems/valid-anagram

1.2.要点

查询出现的次数,要统计、要查询

1哈希表,map

2固定长度26的数组来统计即可

1.3.代码实例

哈希表map

复制代码
 1 #include <map>
 2 class Solution {
 3 public:
 4     bool isAnagram(string s, string t) {
 5         if(s.length()!=t.length()){
 6             return false;
 7         }
 8         
 9         int dataLen=s.length();
10         map<char,int> charMap;
11         for(int i=0;i<s.length();i++){
12             charMap[s[i]]++;
13             charMap[t[i]]--;
14         }
15 
16         for(int i=0;i<26;i++){
17             if(charMap['a'+i]!=0){
18                 return false;
19             }
20         }
21 
22         return true;
23     }
24 };
View Code
复制代码

 

2.查找共用字符串

2.1.问题描述

给你一个字符串数组 words ,请你找出所有在 words 的每个字符串中都出现的共用字符( 包括重复字符),并以数组形式返回。你可以按 任意顺序 返回答案。

输入:words = ["bella","label","roller"] 输出:["e","l","l"]
链接:https://leetcode.cn/problems/find-common-characters

2.2.要点

数组,对于数据空间已知的集合,可以使用数组来统计

哈希

2.3.代码实例

数组

复制代码
 1 class Solution {
 2 public:
 3     vector<string> commonChars(vector<string>& A) {
 4         vector<string> result;
 5         if (A.size() == 0) return result;
 6         int hash[26] = {0}; // 用来统计所有字符串里字符出现的最小频率
 7         for (int i = 0; i < A[0].size(); i++) { // 用第一个字符串给hash初始化
 8             hash[A[0][i] - 'a']++;
 9         }
10 
11         int hashOtherStr[26] = {0}; // 统计除第一个字符串外字符的出现频率
12         for (int i = 1; i < A.size(); i++) {
13             memset(hashOtherStr, 0, 26 * sizeof(int));
14             for (int j = 0; j < A[i].size(); j++) {
15                 hashOtherStr[A[i][j] - 'a']++;
16             }
17             // 更新hash,保证hash里统计26个字符在所有字符串里出现的最小次数
18             for (int k = 0; k < 26; k++) {
19                 hash[k] = min(hash[k], hashOtherStr[k]);
20             }
21         }
22         // 将hash统计的字符次数,转成输出形式
23         for (int i = 0; i < 26; i++) {
24             while (hash[i] != 0) { // 注意这里是while,多个重复的字符
25                 string s(1, i + 'a'); // char -> string
26                 result.push_back(s);
27                 hash[i]--;
28             }
29         }
30 
31         return result;
32     }
33 };
View Code
复制代码

哈希

复制代码
 1 #include <map>
 2 using namespace std;
 3 class Solution {
 4 public:
 5     vector<string> commonChars(vector<string>& words) {
 6         int dataLen=words.size();
 7         vector<string> ret;
 8         if(dataLen<1){
 9             return ret;
10         }
11 
12         int strLen=0;
13         map<char,int> baseMap,curMap;
14         //取基准值
15         strLen=words[0].length();
16         for(int j=0;j<strLen;j++){
17             baseMap[words[0][j]]++;
18         }
19 
20         
21         for(int i=1;i<dataLen;i++){
22             curMap.clear();
23             strLen=words[i].length();
24             
25             //统计当前字符串中的频率
26             for(int j=0;j<strLen;j++){
27                 curMap[words[i][j]]++;
28             }
29 
30             //取交集
31             for(int k=0;k<26;k++){
32                 baseMap['a'+k]=min(baseMap['a'+k],curMap['a'+k]);
33             }
34         }
35 
36         for(auto iter = baseMap.rbegin();iter!=baseMap.rend();iter++){
37             if(iter->second<1){
38                 continue;
39             }
40             for(int i=0;i<iter->second;i++){
41                 string str(1,iter->first);
42                 ret.push_back(str);
43             }
44         }
45 
46         return ret;
47     }
48 };
View Code
复制代码

 

3.两个集合的交集

3.1.问题描述

给定两个数组 nums1 和 nums2 ,返回 它们的交集 。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序

链接:https://leetcode.cn/problems/intersection-of-two-arrays/

3.2.要点

哈希,用nums1填充哈希表,遍历nums2来查哈希表

3.3.代码实例

哈希,注意auto&和emplace_back的细节

复制代码
 1 class Solution {
 2 public:
 3     vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
 4         vector<int> ans;
 5         unordered_set<int> us{ nums1.begin(), nums1.end() };
 6         for (auto& iter : nums2) {
 7             if (us.find(iter) != us.end()) {
 8                 us.erase(iter);
 9                 ans.emplace_back(iter);
10             }
11         }
12         return ans;
13     }
14 };
View Code
复制代码

 

4.快乐数

4.1.问题描述

今天你快乐吗~

编写一个算法来判断一个数 n 是不是快乐数。
「快乐数」 定义为:
(1)对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
(2)然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
(3)如果这个过程 结果为 1,那么这个数就是快乐数。
如果 n 是 快乐数 就返回 true ;不是,则返回 false 。

例子1:输入:n = 19 输出:true 解释:

例子2:输入:n = 2 输出:false
链接:https://leetcode.cn/problems/happy-number

4.2.要点

可以证明n不会发散增长

1哈希

观察例子1和2即可知所谓不是快乐数意味着该数字在迭代计算过程中会再次出现,导致计算陷入死循环。

用哈希统计每次计算出的数字是否出现过即可。

2快慢指针

对于一个数字计算其每位数平方和的操作可以看做->next,这样就会形成一个链表,接下来的问题就是这个链表是否存在环。

slow指针每次走1步,fast每次走2步,如果fast能等于slow则存在环,如果fast到达1则不存在环、n是快乐数

4.3.代码实例

哈希

复制代码
 1 class Solution {
 2 public:
 3     int getSquareSum(int val){
 4         if(val==0){
 5             return 0;            
 6         }
 7         int ret=0;
 8         while(val!=0){
 9             ret+=pow(val%10,2);
10             val/=10;
11         }
12         return ret;
13     }
14 
15     bool isHappy(int n) {
16         if(n==0){
17             return false;
18         }
19 
20         unordered_set<int> nums;
21         while(n!=1){
22             //该值重复出现,陷入循环,不是快乐数
23             if(nums.find(n) != nums.end()){
24                 return false;
25             }
26 
27             nums.insert(n);
28 
29             n=getSquareSum(n);
30         }
31 
32         return true;
33     }
34 };
View Code
复制代码

快慢指针

复制代码
 1 class Solution {
 2 public:
 3     int bitSquareSum(int n) {
 4         int sum = 0;
 5         while(n > 0)
 6         {
 7             int bit = n % 10;
 8             sum += bit * bit;
 9             n = n / 10;
10         }
11         return sum;
12     }
13     
14     bool isHappy(int n) {
15         int slow = n, fast = n;
16         do{
17             if(n==1){
18                 return true;
19             }
20             
21             slow = bitSquareSum(slow);
22             fast = bitSquareSum(fast);
23             fast = bitSquareSum(fast);
24         }while(slow != fast);
25         
26         return slow == 1;
27     }
28 };
View Code
复制代码

 

5.两数之和

5.1.问题描述

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target  的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。
链接:https://leetcode.cn/problems/two-sum

5.2.要点

1哈希

取A,计算Sum-A是否在集合中,注意A=Sum/2的情况

5.3.代码实例

哈希

复制代码
 1 class Solution {
 2 public:
 3     vector<int> twoSum(vector<int>& nums, int target) {
 4         unordered_map<int, int> hashtable;
 5         for (int i = 0; i < nums.size(); ++i) {
 6             auto it = hashtable.find(target - nums[i]);
 7             if (it != hashtable.end()) {
 8                 return {it->second, i};
 9             }
10             hashtable[nums[i]] = i;
11         }
12         return {};
13     }
14 };
View Code
复制代码

 

6.四数之和之4个独立集合

6.1.问题描述

给你四个整数数组 nums1、nums2、nums3 和 nums4 ,数组长度都是 n ,请你计算有多少个元组 (i, j, k, l) 能满足:
(1)0 <= i, j, k, l < n
(2)nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0

例子:

输入: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
链接:https://leetcode.cn/problems/4sum-ii

6.2.要点

1哈希

还是采用已知x,哈希查找Sum-y是否存在的方法

采用分为两组,HashMap 存一组,另一组和 HashMap 进行比对。
这样的话情况就可以分为三种:
(1)HashMap 存一个数组,如 A。然后计算三个数组之和,如 BCD。时间复杂度为:O(n)+O(n^3),得到 O(n^3).
(2)HashMap 存三个数组之和,如 ABC。然后计算一个数组,如 D。时间复杂度为:O(n^3)+O(n),得到 O(n^3).
(3)HashMap存两个数组之和,如AB。然后计算两个数组之和,如 CD。时间复杂度为:O(n^2)+O(n^2),得到 O(n^2).
根据上述我们可以得出要存两个数组算两个数组。
我们以存 AB 两数组之和为例。首先求出 A 和 B 任意两数之和 sumAB,以 sumAB 为 key,sumAB 出现的次数为 value,存入 hashmap 中。
然后计算 C 和 D 中任意两数之和的相反数 sumCD,在 hashmap 中查找是否存在 key 为 sumCD。
算法时间复杂度为 O(n2)。

6.3.代码实例

哈希

复制代码
 1 class Solution {
 2 public:
 3     int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
 4         int dataLen=nums1.size();
 5         int ret=0;
 6         int tempSum=0;
 7 
 8         //统计A+B
 9         unordered_map<int,int> numsAB;
10         for(int i=0;i<dataLen;i++){
11             for(int j=0;j<dataLen;j++){
12                 tempSum=nums1[i]+nums2[j];
13                 numsAB[tempSum]++;
14             }
15         }
16 
17         //对比CD
18         for(int i=0;i<dataLen;i++){
19             for(int j=0;j<dataLen;j++){
20                 tempSum = 0 - nums3[i] - nums4[j];
21                 if(numsAB.find(tempSum) != numsAB.end()){
22                     ret += numsAB[tempSum];
23                 }
24             }
25         }
26 
27         return ret;
28     }
29 };
View Code
复制代码

 

7.赎金信

7.1.问题描述

给你两个字符串:ransomNote 和 magazine ,判断 ransomNote 能不能由 magazine 里面的字符构成。
如果可以,返回 true ;否则返回 false 。
magazine 中的每个字符只能在 ransomNote 中使用一次。
链接:https://leetcode.cn/problems/ransom-note

7.2.要点

1哈希,使用额度消耗问题

7.3.代码实例

哈希

复制代码
 1 class Solution {
 2 public:
 3     bool canConstruct(string ransomNote, string magazine) {
 4         if (ransomNote.size() > magazine.size()) {
 5             return false;
 6         }
 7         vector<int> cnt(26);
 8         for (auto & c : magazine) {
 9             cnt[c - 'a']++;
10         }
11         for (auto & c : ransomNote) {
12             cnt[c - 'a']--;
13             if (cnt[c - 'a'] < 0) {
14                 return false;
15             }
16         }
17         return true;
18     }
19 };
View Code
复制代码

 

8.三数之和

8.1.问题描述

给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请
你返回所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组。
链接:https://leetcode.cn/problems/3sum

8.2.要点

1.排序+双指针

(1)特判,对于数组长度 n,如果数组为 null或者数组长度小于 3,返回 []。
(2)对数组进行排序。
(3)遍历排序后数组:i为遍历索引
    若 nums[i]>0:因为已经排序好,所以后面不可能有三个数加和等于 0,直接返回。
    对于重复元素:跳过,避免出现重复解
    进入双指针搜索,令左指针 L=i+1,右指针 R=n−1,当 L<R 时,执行循环:
        当 nums[i]+nums[L]+nums[R]==0,执行循环,判断左界和右界是否和下一位置重复,去除重复解。并同时将 L,R移到下一位置,寻找新的解
        若和大于 0,说明 nums[R]太大,R左移
        若和小于 0,说明 nums[L] 太小,L右移

8.3.代码实例

排序+双指针

复制代码
 1 class Solution {
 2 public:
 3     vector<vector<int>> threeSum(vector<int>& nums) {
 4         int n = nums.size();
 5         sort(nums.begin(), nums.end());
 6         vector<vector<int>> ans;
 7         
 8         int second = 0, third = 0;
 9         int curSum=0;
10         // 枚举 first
11         for (int first = 0; first < n; first++) {
12             //最小值已大于0
13             if(nums[first]>0){
14                 break;
15             }
16             // 需要和上一次枚举的数不相同
17             if (first > 0 && nums[first] == nums[first - 1]) {
18                 continue;
19             }
20 
21             //开始双指针搜索
22             second= first + 1;
23             third = n - 1; 
24             while(second < third){
25                 curSum = nums[first] + nums[second] + nums[third];
26                 if(curSum == 0){
27                     //左右边界去重
28                     while((second+1)<=third && nums[second]==nums[second+1]){
29                         second++;
30                     }
31                     while((third-1)>=second && nums[third]==nums[third-1]){
32                         third--;
33                     }
34 
35                     //记录组合
36                     ans.push_back({nums[first], nums[second], nums[third]});
37 
38                     //同时移动左右指针
39                     second++;
40                     third--;
41                 }
42                 if(curSum<0){
43                     second++;
44                 }
45                 if(curSum>0){
46                     third--;
47                 }
48             }
49         }
50         return ans;
51     }
52 };
View Code
复制代码

 

9.四数之和

9.1.问题描述

给你一个由 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
你可以按 任意顺序 返回答案 。
链接:https://leetcode.cn/problems/4sum

9.2.要点

类比三数之和

如果暴力解法n层循环嵌套,那么时间复杂度是O(n^4),

参考三数之和的排序之后双指针的方法,双指针可以去掉一层幂运算,这里套用排序+双指针后的时间复杂度为O(n^3+nlog⁡n)=O(n^3)

9.3.代码实例

排序+双指针

复制代码
 1 class Solution{
 2     public: 
 3     vector<vector<int>> fourSum(vector<int>& nums, int target) {
 4         sort(nums.begin(),nums.end());
 5         vector<vector<int> > res;
 6         if(nums.size()<4)
 7         return res;
 8         int a,b,c,d,_size=nums.size();
 9         for(a=0;a<=_size-4;a++){
10             if(a>0&&nums[a]==nums[a-1]) continue;      //确保nums[a] 改变了
11             //剪枝
12             if ((long) nums[a] + nums[a + 1] + nums[a + 2] + nums[a + 3] > target) {
13                 break;
14             }
15             if ((long) nums[a] + nums[_size - 3] + nums[_size - 2] + nums[_size - 1] < target) {
16                 continue;
17             }
18 
19             for(b=a+1;b<=_size-3;b++){
20                 if(b>a+1&&nums[b]==nums[b-1])continue;   //确保nums[b] 改变了
21                 //剪枝
22                 if ((long) nums[a] + nums[b] + nums[b + 1] + nums[b + 2] > target) {
23                     break;
24                 }
25                 if ((long) nums[a] + nums[b] + nums[_size - 2] + nums[_size - 1] < target) {
26                     continue;
27                 }
28 
29                 c=b+1,d=_size-1;
30                 while(c<d){
31 
32                     //注意防止溢出
33                     if(nums[a]+nums[b]-target<-(nums[c]+nums[d]))
34                         c++;
35                     else if(nums[a]+nums[b]-target>-(nums[c]+nums[d]))//同上
36                         d--;
37                     else{
38                         res.push_back({nums[a],nums[b],nums[c],nums[d]});
39                         while(c<d&&nums[c+1]==nums[c])      //确保nums[c] 改变了
40                             c++;
41                         while(c<d&&nums[d-1]==nums[d])      //确保nums[d] 改变了
42                             d--;
43                         c++;
44                         d--;
45                     }
46                 }
47             }
48         }
49         return res;
50     }
51 };
View Code
复制代码

 

10.总结

10.1.双指针要点

双指针的主要用途

(1)链表中特定的计算行经路程的问题;

(2)降低时间复杂度,用双指针来提高效率,一般是将O(n^2)的时间复杂度,降为O(n)

 


 

 

 

 

 

 

 

xxx.问题

xxx.1.问题描述

111

xxx.2.要点

222

xxx.3.代码实例

333

本文作者:OhOfCourse

本文链接:https://www.cnblogs.com/OhOfCourse/p/16978626.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   啊原来是这样呀  阅读(35)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起