目录
- [1.leetcode-454. 四数相加 II](#1.leetcode-454. 四数相加 II)
- [2.leetcode-383. 赎金信](#2.leetcode-383. 赎金信)
- [3.leetcode-205. 同构字符串](#3.leetcode-205. 同构字符串)
- [4.leetcode-128. 最长连续序列](#4.leetcode-128. 最长连续序列)
- 5.总结
1.leetcode-454. 四数相加 II
(1)题目描述
给你四个整数数组 nums1、nums2、nums3 和 nums4 ,数组长度都是 n ,请你计算有多少个元组 (i, j, k, l) 能满足:
0 <= i, j, k, l < n
nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0
(2)思路与方法
对于这道题可能首先想到的就是对于四个数组进行循环遍历,但是这种方法的时间复杂度时n^4,不建议使用,所以我们想到两组两组遍历。
本题解题步骤:
1.首先定义 一个unordered_map,key放a和b两数之和,value 放a和b两数之和出现的次数。
2.遍历大A和大B数组,统计两个数组元素之和,和出现的次数,放到map中。
3.定义int变量count,用来统计 a+b+c+d = 0 出现的次数。
4.在遍历大C和大D数组,找到如果 0-(c+d) 在map中出现过的话,就用count把map中key对应的value也就是出现次数统计出来。
5.最后返回统计值 count 就可以了
cpp
class Solution {
public:
int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
unordered_map<int,int> map;
int count=0;
for(int a:nums1)
{
for(int b:nums2)
{
map[a+b]++;
}
}
for(int c:nums3)
{
for(int d:nums4)
{
int target=0-c-d;
count+=map[target];
}
}
return count;
}
};
2.leetcode-383. 赎金信
(1)题目描述
给定一个赎金信 (ransom) 字符串和一个杂志(magazine)字符串,判断第一个字符串 ransom 能不能由第二个字符串 magazines 里面的字符构成。如果可以构成,返回 true ;否则返回 false。
(题目说明:为了不暴露赎金信字迹,要从杂志上搜索各个需要的字母,组成单词来表达意思。杂志字符串中的每个字符只能在赎金信字符串中使用一次。)
(1)暴力解法
那么第一个思路其实就是暴力枚举了,两层for循环,不断去寻找,代码如下:
cpp
class Solution {
public:
bool canConstruct(string ransomNote, string magazine) {
for (int i = 0; i < magazine.length(); i++) {
for (int j = 0; j < ransomNote.length(); j++) {
// 在ransomNote中找到和magazine相同的字符
if (magazine[i] == ransomNote[j]) {
ransomNote.erase(ransomNote.begin() + j); // ransomNote删除这个字符
break;
}
}
}
// 如果ransomNote为空,则说明magazine的字符可以组成ransomNote
if (ransomNote.length() == 0) {
return true;
}
return false;
}
};
(2)哈希法
因为题目所只有小写字母,那可以采用空间换取时间的哈希策略, 用一个长度为26的数组还记录magazine里字母出现的次数。
然后再用ransomNote去验证这个数组是否包含了ransomNote所需要的所有字母。
依然是数组在哈希法中的应用。
一些同学可能想,用数组干啥,都用map完事了,其实在本题的情况下,使用map的空间消耗要比数组大一些的,因为map要维护红黑树或者哈希表,而且还要做哈希函数,是费时的!数据量大的话就能体现出来差别了。 所以数组更加简单直接有效!
cpp
class Solution {
public:
bool canConstruct(string ransomNote, string magazine) {
vector<int> ans(26,0);
int n2=magazine.size();
int n1=ransomNote.size();
for(auto& a:magazine)
{
ans[a-'a']++;
}
for(int i=0;i<n1;++i)
{
ans[ransomNote[i]-'a']--;
if(ans[ransomNote[i]-'a']<0)
{
return false;
}
}
return true;
}
};
3.leetcode-205. 同构字符串
给定两个字符串 s 和 t ,判断它们是否是同构的。
如果 s 中的字符可以按某种映射关系替换得到 t ,那么这两个字符串是同构的。
每个出现的字符都应当映射到另一个字符,同时不改变字符的顺序。不同字符不能映射到同一个字符上,相同字符只能映射到同一个字符上,字符可以映射到自己本身。
(1)哈希法
需要我们判断 s和 t每个位置上的字符是否都一一对应,即 s 的任意一个字符被 t 中唯一的字符对应,同时 t 的任意一个字符被 s 中唯一的字符对应。这也被称为「双射」的关系。
我们维护两张哈希表,第一张哈希表 s2t 以 s 中字符为键,映射至 t 的字符为值,第二张哈希表 t2s以 t 中字符为键,映射至 s 的字符为值。从左至右遍历两个字符串的字符,不断更新两张哈希表,如果出现冲突,即当前下标 index 对应的字符 s[index] 已经存在映射且不为 t[index]或当前下标 index 对应的字符 t[index] 已经存在映射且不为 s[index]时说明两个字符串无法构成同构,返回 false 。
cpp
class Solution {
public:
bool isIsomorphic(string s, string t){
unordered_map<char,char> map1;
unordered_map<char,char> map2;
for(int i=0;i<s.size();++i)
{
char x=s[i];
char y=t[i];
if(map1.count(x)&&map1[x]!=y||map2.count(y)&&map2[y]!=x)
{
return false;
}
map1[x]=y;
map2[y]=x;
}
return true;
}
};
(2)直接对比查找
cpp
class Solution {
public:
bool isIsomorphic(string s, string t){
for(int i=0;i<s.size();++i)
{
if(s.find(s[i])!=t.find(t[i]))
return false;
}
return true;
}
};
4.leetcode-128. 最长连续序列
给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。
请你设计并实现时间复杂度为 O(n) 的算法解决此问题。
1.创建一个名为 num_set 的无序集合(unordered_set),用于存储 nums 向量中的唯一整数。集合确保消除重复的数字。
2.代码遍历 nums 向量中的每个元素,并将其插入到 num_set 中。
3.将变量 longestStreak 初始化为 0,表示目前为止找到的最长连续子序列的长度。
4.代码遍历 num_set 中的每个元素 num:
如果 num 是连续子序列的起始数字(即 num - 1 不在 num_set 中),则进入循环。
在循环内部,将变量 currentNum 设置为 num,并将 currentStreak 初始化为 1(因为 num 本身就构成长度为 1 的子序列)。
只要 num_set 中存在 currentNum + 1,代码将持续增加 currentNum 和 currentStreak。这个循环计算从 num 开始的连续子序列的长度。
使用 max 函数将 longestStreak 更新为当前 longestStreak 和 currentStreak 的最大值,以确保它保留到目前为止找到的最长子序列的长度。
5.在遍历所有元素后,函数返回 longestStreak,它表示给定向量中最长连续子序列的长度。
cpp
class Solution {
public:
int longestConsecutive(vector<int>& nums) {
unordered_set<int> num_set;
for (const int& num : nums) {
num_set.insert(num);
}
int longestStreak = 0;
for (const int& num : num_set) {
if (!num_set.count(num - 1)) {
int currentNum = num;
int currentStreak = 1;
while (num_set.count(currentNum + 1)) {
currentNum += 1;
currentStreak += 1;
}
longestStreak = max(longestStreak, currentStreak);
}
}
return longestStreak;
}
};
5.总结
数组作为哈希表
在242.有效的字母异位词 (opens new window)中,我们提到了数组就是简单的哈希表,但是数组的大小是受限的!
这道题目包含小写字母,那么使用数组来做哈希最合适不过。
在383.赎金信 (opens new window)中同样要求只有小写字母,那么就给我们浓浓的暗示,用数组!
本题和242.有效的字母异位词 (opens new window)很像,242.有效的字母异位词 (opens new window)是求 字符串a 和 字符串b 是否可以相互组成,在383.赎金信 (opens new window)中是求字符串a能否组成字符串b,而不用管字符串b 能不能组成字符串a。
一些同学可能想,用数组干啥,都用map不就完事了。
上面两道题目用map确实可以,但使用map的空间消耗要比数组大一些,因为map要维护红黑树或者符号表,而且还要做哈希函数的运算。所以数组更加简单直接有效!
set作为哈希表
在349. 两个数组的交集 (opens new window)中我们给出了什么时候用数组就不行了,需要用set。
这道题目没有限制数值的大小,就无法使用数组来做哈希表了。
主要因为如下两点:
数组的大小是有限的,受到系统栈空间(不是数据结构的栈)的限制。
如果数组空间够大,但哈希值比较少、特别分散、跨度非常大,使用数组就造成空间的极大浪费。
map作为哈希表
在1.两数之和 (opens new window)中map正式登场。
来说一说:使用数组和set来做哈希法的局限。
数组的大小是受限制的,而且如果元素很少,而哈希值太大会造成内存空间的浪费。
set是一个集合,里面放的元素只能是一个key,而两数之和这道题目,不仅要判断y是否存在而且还要记录y的下标位置,因为要返回x 和 y的下标。所以set 也不能用。
map是一种<key, value>的结构,本题可以用key保存数值,用value在保存数值所在的下标。所以使用map最为合适。
本篇我们从哈希表的理论基础到数组、set和map的经典应用,把哈希表的整个全貌完整的呈现给大家。