算法刷题 Leetcode哈希表-0116

文章详细介绍了使用哈希表解决LeetCode中的一些算法问题,包括两数之和、三数之和、四数之和、有效字母异位词、赎金信和交集问题,强调了不同类型的哈希表(数组、set和map)在不同场景下的应用,以及unordered_set的效率优势。同时,提供了各种问题的优化解法和代码实现。
摘要由CSDN通过智能技术生成

算法刷题 | 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的右侧。

思路:

  1. 先排序
  2. 排特解:a[left]>i,结束寻找
  3. 初始化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;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值