算法刷题 | Leetcode哈希表
不想睡觉,深夜刷题,对简单题重拳出击。白天天做完两数之和、三数之和、四数之和
总结在前
三种哈希表:数组、set和map。
哈希表类型 | 使用场景 | 代表题 |
---|---|---|
数组 | 索引大小范围较小,1e6以内的int数组都是可接受的。 | 242,383 |
set | 索引大小无范围或范围过大。可判断是否出现,可去重。 | 349,202 |
map | 索引大小无范围或范围过大,需要存储其他信息,如出现次数,某数下标等。 | 1,15,18,454 |
unordered_set效率比multiset和set高,前者底层为哈希,后两者底层为红黑树。
unordered_set效率比multiset和set高,前者底层为哈希,key无序,后两者底层为红黑树,key有序。
Leetcode 242. 有效的字母异位词
-
法一:两个数组分别标记两个串各个字母出现次数,两个循环完成统计,再用一个循环判断两个数组是不是相同。
-
优化一:两个数组分别标记两个串各个字母出现次数,一个循环完成统计,再用一个循环判断两个数组是不是相同。
-
优化二:一个数组分别标记两个串各个字母出现次数,一个循环完成统计,再用一个循环判断两个数组是不是相同。
思路:先加后减,统计s字符串中字母次数时计数数组对应下标元素加一,而统计t字符串中字母次数时计数数组对应下标元素减一,有种抵消的感觉。
class Solution {
public:
bool isAnagram(string s, string t) {
int a[26]={0};
if(s.length()!=t.length()) return false;
for(int i=0;i<s.length();i++){
a[s[i]-'a']++;
a[t[i]-'a']--;
}
for(int i=0;i<26;i++){
if(a[i]!=0) return false;
}
return true;
}
};
Leetcode 383. 赎金信
跟242题很像哇,放一起看。
区别是242题中两个串长度应该是一样的,而383题不一定。因而最后一个循环中返回false的条件有些区别,242题中先加后减完是刚好抵消为0的,不为0则不符合题目条件;383题中a串可以比b串短,则先加后减完若小于0是可以的,因为b串中该字符个数可能多于a串中字符个数,等于0则是刚好相等,大于0则说明a串中该字符个数大于b串,则a串不可能由b串构成。
class Solution {
public:
bool canConstruct(string a, string b) {
int f[26] = {0};
for(char ch : a){ f[ch - 'a']++; }
for(char ch : b){
if(f[ch-'a']){ f[ch - 'a']--; } // 一个个字符进行抵消
}
for(int i=0; i<26; i++){
if(f[i]>0) return false; // 有字符没抵消完,则a不能由b中的字符构成
}
return true;
}
};
Leetcode 349. 两个数组的交集
又是一道没什么好说的题。
class Solution {
public:
vector<int> intersection(vector<int>& a, vector<int>& b) {
bool f[1000+5] = {0};
for(int i=0; i<a.size(); i++) f[a[i]] = 1;
a.clear(); // a数组没用了,清空掉刚好用来存放交集
for(int i=0; i<b.size(); i++){
if(f[b[i]]) a.push_back(b[i]), f[b[i]]=0; // 把f[b[i]]赋值为0是为了去重
}
return a;
}
};
- STL复习版:可以用set存放结果,自动去重。
class Solution {
public:
vector<int> intersection(vector<int>& a, vector<int>& b) {
bool f[1000+5] = {0};
for(int i=0; i<a.size(); i++) f[a[i]] = 1;
unordered_set<int> res;
for(int i=0; i<b.size(); i++){
if(f[b[i]]) res.insert(b[i]);
}
return vector<int>(res.begin(), res.end()); // 返回类型要求vector,强制转换
}
};
Leetcode 202. 快乐数
class Solution {
public:
int add(int n){
int sum = 0;
while(n){
int i = n%10;
sum += i*i;
n /= 10;
}
return sum;
}
bool isHappy(int n) {
long sum = add(n);
unordered_set<long> f;
f.insert(sum);
while(sum!=1){
sum = add(sum);
if(f.find(sum)!=f.end()) return false;
f.insert(sum);
}
return true;
}
};
Leetcode 1. 两数之和
- 法一:O(n^2)暴力
class Solution {
public:
vector<int> twoSum(vector<int>& a, int target) {
for(int i=0; i<a.size(); i++){
for(int j=i+1; j<a.size(); j++){
if(a[j] == (target - a[i])){
return {i,j};
}
}
}
return {}; // 本来是声明了一个新的vecter<int>存的,但是现学现用,这种更优雅
}
};
- 法二:哈希
class Solution {
public:
vector<int> twoSum(vector<int>& a, int target) {
vector<int> ans;
unordered_map<int, int> mp;
for(int i=0; i<a.size(); i++){
mp[a[i]]=i;
}
for(int j=0; j<a.size(); j++){
if(mp.find(target-a[j])!=mp.end() && j!=mp[target-a[j]]){// 找得到
return {j, mp[target-a[j]]}; // 新的返回数据形式出现了
}
}
return ans;
}
};
Leetcode 454. 四数相加 II
自己一开始的错误解法:将map声明成<int, bool>型,只标记a+b所有可能出现的取值,但是可能有多个二元组的和为同一个值,而这多个二元组是可以跟由c、d组成的元组排列组合成新的四元组的,简单来说就是丢解了。所以应该是<int, int>型,统计a+b所有可能及其出现次数,统计答案时将满足答案的所有出现次数相加即可。
class Solution {
public:
int fourSumCount(vector<int>& a, vector<int>& b, vector<int>& c, vector<int>& d) {
unordered_map<int, int> mp; // 标记a+b的所有可能性
int n = a.size();
for(int i=0; i<n; i++){
for(int j=0; j<n; j++){
mp[a[i]+b[j]]++;
}
}
int ans=0, tmp;
for(int i=0; i<n; i++){
for(int j=0; j<n; j++){
tmp = 0-(c[i]+d[j]);
if(mp.find(tmp)!=mp.end()) ans+=mp[tmp];
}
}
return ans;
}
};
Leetcode 15. 三数之和
-
排序+双指针
算法思想:开一个循环,每次循环固定一个数a[i],移动双指针left和right寻找
a[i] + a[left] + a[right] = 0
情况。双指针规则移动的前提:数组排序,若三个数大于target,则right左移,三个数会变小;同理,若三个数小于0,left右移,三个数会变大,直到找到等于target或不符合left<right条件。注意找到等于target后不能马上跳出循环,不然会丢结果,因为还没遍历完 i 固定时所有可能的三元组。
这里的双指针与i的位置,一开始自己写把i写在中间,实在是不好写,去重不好弄,还会漏结果,看题解后才知道双指针都在i的右侧。
思路:
- 先排序
- 排特解:a[left]>i,结束寻找
- 初始化left, rig, i,
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& a) {
vector<vector<int>> ans;
int n = a.size(), i=0, rig = n-1, left=1;
sort(a.begin(), a.end()); // STL,使用sort对vector进行排序
for(; i<n; i++){
if(a[i]>0) break; // 剪枝:第一个数都大于0,后面不可能大于0
if(i>0 && a[i]==a[i-1]) continue;
left = i+1, rig = n-1;
while(left<rig){
if(a[i] + a[left] + a[rig] > 0){
rig--;
}
else if(a[i] + a[left] + a[rig] < 0){
left++;
}
else{ // 等于0
ans.push_back((vector<int>){a[i], a[left], a[rig]});
while(left<rig && a[rig]==a[rig-1]) rig--;
while(left<rig && a[left]==a[left+1]) left++;
rig--;
left++;
}
}
}
return ans;
}
};
-
一些测试用例
[-1,0,1,2,-1,-4] [[-1,-1,2],[-1,0,1]] [-2,0,0,2,2] [2,0,2]
-
其他
- 左右指针向中间逼近写法:法一:两个循环,参考快排中划分时的写法,法二:一个循环里放if语句。
Leetcode 18. 四数之和
有了三数之和为基础,四数之和就是每次固定住两个数进行寻找,再修改一下剪枝条件即可。
码完发现还有三个细节需注意,一是此题数组大小可能小于4,需要特判;二是j的去重与i的去重有些差别;三是四个数相加可能超过int范围,相加判断时应该先转换为long。
class Solution {
public:
vector<vector<int>> fourSum(vector<int>& a, int target) {
vector<vector<int>> ans;
if(a.size()<4) return ans; // 特解
int n=a.size(), left = 2, rig = n-1;
sort(a.begin(), a.end());
for(int i=0; i<n; i++){
if(a[i]>0 && a[i] > target) break; // 剪枝1
if(i>0 && a[i]==a[i-1]) continue; // i去重
for(int j=i+1; j<n; j++){
if(j>i+1 && a[j]==a[j-1]) continue; // j去重,注意是当j>i+1后开始判断而不是>0
left = j+1, rig=n-1;
if(a[i]+a[j]>0 && a[i]+a[j]>target) break; // 剪枝2:进入下一个i进行遍历
while(left<rig){
// 注意可能溢出
if((long)a[i] + a[j] + a[left] + a[rig] > target) rig--;
else if((long)a[i] + a[j] + a[left] + a[rig] < target) left++;
else{
ans.push_back((vector<int>){a[i], a[j], a[left], a[rig]});
while(left<rig && a[rig]==a[rig-1]) rig--;
while(left<rig && a[left]==a[left+1]) left++;
left++;
rig--;
}
}
}
}
return ans;
}
};