[日记]LeetCode算法·十二——哈希

1 哈希Hash

哈希算法就是一种空间换时间的思想,利用红黑树(一种类似AVL树的平衡树)、哈希表等数据结构,实现增、取的快速实现。

哈希相关的数据结构unordered_set、unordered_map、map都适用于快速判断一个元素是否在集合中的问题。

2 有效的字母异位词

LeetCode:有效的字母异位词

做了二叉树和回溯之后,再用数组这种线性结构解决这种问题,真有一种难以严明的轻快感。

class Solution {
public:
    bool isAnagram(string s, string t) {
        int count[26]={0};
        if(s.size()!=t.size())
            return false;
        for(int i=0;i<t.size();i++)
        {
            ++count[s[i]-'a'];
            --count[t[i]-'a'];
        }
        for(int i=0;i<26;i++)
        {
            if(count[i]!=0)
                return false;
        }
        return true;
    }
};

3 两个数组的交集

LeetCode:两个数组的交集

利用字典nums_set去重+快速比较,利用result_set的去重,天然满足集合不重复条件。

class Solution {
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
        //不使用vector是为了进行天然的去重
        unordered_set<int> result_set;
        unordered_set<int> num_set(nums1.begin(),nums1.end());
        for(int num : nums2)
        {
            //该语句用于判断是否在集合内
            if(num_set.find(num)!=num_set.end())
            {
                result_set.insert(num);
            }
        }
        return vector<int>(result_set.begin(),result_set.end());
    }
};

4 快乐数

LeetCode:快乐数

利用Hash的Set保存已有的结果,结果不在集合内就加入集合,在意味着无限循环,直接break跳出循环。

class Solution {
public:
    bool isHappy(int n) {
        int sum=0;
        int res;
        unordered_set<int> sum_set;
        sum_set.insert(n);
        while(1)
        {
            sum=0;
            //计算各个位置的平方和
            while(n)
            {
                res=n % 10;
                sum+=res*res;
                n/=10;
            }
            n=sum;
            //快乐数
            if(sum==1)
                return true;
            //出现已出现过的数字意味着无限循环
            if(sum_set.find(sum)!=sum_set.end())
                return false;
            //不确定就进行下一轮循环
            else
                sum_set.insert(sum);
        }
    }
};

5 两数之和

LeetCode:两数之和

唯一的槽点在于输出的是指针而不是值,导致排序+双指针的方法没法直接用,于是直接用set{之前遍历元素}+当前元素进行比较。

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) 
    {
        //一次遍历的办法
        //利用哈希保存自己之前已经遍历过的元素,并且进行比较
        //因为目前的元素还没有进入map,因此保留了两个相同元素相加的可能,如2+2=4
        //另外因为只存在一个有效答案,我们可以认定,在已经遍历过的结果中,不存在满足有效答案的重复元素
        unordered_map<int,int> vk;
        for(int i=0;i<nums.size();++i)
        {
            auto iter=vk.find(target-nums[i]);
            if(iter!=vk.end())
            {
                return {iter->second,i};
            }
            else
            {
                vk[nums[i]]=i;
            }
        }
        return {};
    }
};

6 四数相加II

LeetCode:四数相加II

相比三数/四数相加,没有去重要求的四数相加II反而更加简单。

这一提体现了哈希空间换时间的思想,利用Hash的O(1)取,将原来的O(n4)转化为了2个O(n2)的循环,

class Solution {
public:
    int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
        unordered_map<int,int> umap;
        //记录a+b的情况
        for(int a:nums1)
        {
            for(int b:nums2)
            {
                ++umap[a+b];
            }
        }
        int count=0;
        //c+d=-a-b的情况下,计数
        for(int c:nums3)
        {
            for(int d:nums4)
            {
                if(umap.find(-(c+d))!=umap.end())
                    count+=umap[-(c+d)];
            }
        }
        return count;
    }
};

7 赎金信

LeetCode:赎金信

和2没有本质区别。

class Solution {
public:
    bool canConstruct(string ransomNote, string magazine) {
        int count[26]={0};
        for(int i=0;i<ransomNote.size();i++)
        {
            --count[ransomNote[i]-'a'];
        }
        for(int i=0;i<magazine.size();i++)
        {
            ++count[magazine[i]-'a'];
        }
        for(int i=0;i<26;++i)
        {
            if(count[i]<0)
                return false;
        }
        return true;
    }
};

8 三数之和

LeetCode:三数之和

虽然放在了Hash来讲,但事实上双指针反而更好使的一道题目,利用Hash需要多多思考去重情况的一道题,给出了2种Hash和1种双指针答案。

Hash1·循环进行两数之和
class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>> result;
        sort(nums.begin(),nums.end());
        unordered_set<int> set;

        //三数之和可以看成是一个循环内的二数之和
        //两数之和的目标是-nums[i]
        //按照这个思路进行即可
        for(int i=0;i<nums.size()-2;++i)
        {
            //剪枝
            if(nums[i]>0)
                break;
            //a去重
            if(i>0 && nums[i]==nums[i-1])
                continue;
            set.clear();
            for(int j=i+1;j<nums.size();j++)
            {
                //允许一次重复,因为这一轮循环需要求b和c,b/c重复是允许的
                //但是如果有三个数,就需要去重了
                if(j>i+2 && nums[j-1]==nums[j] && nums[j-2]==nums[j])
                    continue;
                int complement=-(nums[i]+nums[j]);
                //这一轮从i+1走到目前有某个b与目前c=nums[j]满足条件
                if(set.find(complement)!=set.end())
                {
                    result.push_back({nums[i],nums[j],complement});
                    //用过一次就不能再用了,避免重复
                    set.erase(complement);
                }
                //加入set,自动去重
                else
                {
                    set.insert(nums[j]);
                }
            }
        }
        return result;
    }
};
Hash2·利用后向覆盖+向后遍历确定有序性,避免重复
利用哈希保存靠后的序号,遍历从前往后找,于是就意味着三数的排列必然为a<b<c,方便进行剪枝与确定数值。
class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        //利用双指针
        int i,left,right;
        vector<vector<int>> result;
        sort(nums.begin(),nums.end());

        for(i=0;i<nums.size()-2;++i)
        {
            //剪枝
            if(nums[i]>0)break;
            //去重
            if(i>0 && nums[i]==nums[i-1])continue;
            //双指针
            left=i+1;
            right=nums.size()-1;
            while(left<right)
            {
                //去重
                if(right<nums.size()-1 && nums[right]==nums[right+1])
                {
                    --right;
                    continue;
                }
                if(left>i+1 && nums[left]==nums[left-1])
                {
                    ++left;
                    continue;
                }
                int sum=nums[i]+nums[left]+nums[right];
                //sum>0,需要减小,i是固定的,left只能增大,所以只有++right了
                if(sum>0)--right;
                //同理,--left
                else if(sum<0)++left;
                //满足条件,继续搜索
                else if(sum==0)
                {
                    result.push_back({nums[i],nums[left],nums[right]});
                    --right;
                    ++left;
                }
            }
        }
        return result;
    }
};
双指针(增大只能++left,减小只能–right)
class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        //利用双指针
        int i,left,right;
        vector<vector<int>> result;
        sort(nums.begin(),nums.end());

        for(i=0;i<nums.size()-2;++i)
        {
            //剪枝
            if(nums[i]>0)break;
            //去重
            if(i>0 && nums[i]==nums[i-1])continue;
            //双指针
            left=i+1;
            right=nums.size()-1;
            while(left<right)
            {
                //去重
                if(right<nums.size()-1 && nums[right]==nums[right+1])
                {
                    --right;
                    continue;
                }
                if(left>i+1 && nums[left]==nums[left-1])
                {
                    ++left;
                    continue;
                }
                int sum=nums[i]+nums[left]+nums[right];
                //sum>0,需要减小,i是固定的,left只能增大,所以只有++right了
                if(sum>0)--right;
                //同理,--left
                else if(sum<0)++left;
                //满足条件,继续搜索
                else if(sum==0)
                {
                    result.push_back({nums[i],nums[left],nums[right]});
                    --right;
                    ++left;
                }
            }
        }
        return result;
    }
};

9 四数之和

LeetCode:四数之和

解决了三数之和,就可以用一样的套路(双指针)解决四数之和了,多加一层循环罢了。

class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
        vector<vector<int>> result;
        long Target=target;
        sort(nums.begin(),nums.end());

        if(nums.back()<Target && nums.back()<=0)return result;
        if(nums.front()>Target && nums.front()>=0)return result;

        int i,j,left,right;
        int n=nums.size();

        //双指针
        for(i =0 ;i< n-3; ++i)
        {
            if(i>0 && nums[i]==nums[i-1])continue;
            for(j=i+1; j < n-2; ++j)
            {
                if(j>i+1 && nums[j]==nums[j-1])continue;

                left=j+1;
                right=n-1;

                while(left<right)
                {
                    if(left>j+1 && nums[left]==nums[left-1])
                    {
                        ++left;
                        continue;
                    }
                    if(right<n-1 && nums[right]==nums[right+1])
                    {
                        --right;
                        continue;
                    }

                    long sum=long(nums[i])+long(nums[j])+long(nums[left])+long(nums[right]);
                    if(sum>Target)--right;
                    if(sum<Target)++left;
                    if(sum==Target)
                    {
                        result.push_back({nums[i],nums[j],nums[left],nums[right]});
                        --right;
                        ++left;
                    }
                }
            }
        }
        return result;
    }
};

10 总结

回归哈希,虽然暂时还没有去深究C++底层容器的红黑树啊、哈希编码的原理,但好用就是好用,而且题目难度也降低了很多。

另外就是感觉一直没睡够,到了下午做题都没精神了,忘得也快。

——2023.2.25

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值