代码随想录(day7)——哈希表

Leetcode.454 四数相加Ⅱ:

454. 四数相加 II - 力扣(LeetCode)

       对于本题,虽然使用四层for循环嵌套可以解决,但是效率过慢,为O(n^{4}),因此,可以将给定的四个数组,分成两组,即:nums1,nums2为一组,nums3,nums4为一组,由于题目的要求是找到满足nums1[i]+nums2[j]+nums3[k]+nums4[l]==0的元组的数量,对于这个问题,可以抽象的看成,查找在nums3,nums4中,是否有满足-(nums1[i]+nums[j])的元素。

     因此,可以利用map定义一个哈希表,通过两次for循环嵌套,存放nums1[i]+nums2[j]所有元素组合出现的次数。

     再去利用两层for循环在nums3,nums4进行查找相应的元素。例如,在定义好的哈希表中,nums1[i]+nums2[j]==5的元组出现了4次,因此,需要在nums3,nums4中进行逐次遍历,查找0-(nums3[i]+nums4[j])是否在哈希表中出现过,若出现过,则说明存在符合条件nums1[i]+nums2[j]+nums3[k]+nums4[l]==0的元组,此时再定义一个变量count,令count+=map[0-(nums3[i]+nums4[j])];即可。具体代码对应如下:

class Solution {
public:
    int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {

        
        unordered_map<int,int> umap;
        int count = 0;
        for(int i = 0; i < nums1.size(); i++)
        {
            for(int j = 0; j < nums2.size(); j++)
            {
                umap[nums1[i]+nums2[j]]++;
            }
        }

        for(int i = 0; i < nums3.size(); i++)
        {
            for(int j = 0; j < nums4.size(); j++)
            {
                if(umap.find(0-(nums3[i]+nums4[j])) != umap.end())
                {
                    count += umap[0-(nums3[i]+nums4[j])];
                }
            }
        }

        return count;

    }
};

运行结果如下:

Leetcode.382 赎金信:

383. 赎金信 - 力扣(LeetCode)

       本题与上篇文章代码随想录(day6)——哈希表-CSDN博客中的题目. - 力扣(LeetCode)相似。都是利用哈希表来统计一个字符串中各个字母出现的次数,再拿去另一个字符串中进行比对。不过对于本题还是需要注意一些细节:

       由于题目中说明,给定的两个字符串全都由小写英文字母构成,因此,直接定义一个大小为26的整型数组即可。

      题目中提到了,需要判断ransomNote能不能由magazine中出现过的字符组成,并且,magazine中出现n次的字符在ransomNote只能使用n次,例如,ransomNoteaaamagazineaa,由于ransomNote中出现a的次数为3magazine中出现a的次数为2,因此判定false。所以,在创建数组进行判断之前,首先判定一次两个字符串的长短关系,如果ransomNote.size() > magazine.size();则直接判定为false即可。

    并且由题干中给出了aa,aab的样例来看,可以得出一个结论:magazine中出现的字符的种类可以>=ransomNote中出现的字符的种类,magazine中单个字符出现的次数必须>=ransomNote中单个字符出现的次数

    介于上面的原理,可以先遍历一次magazine,统计其出现的字符的种类以及单个字符出现的次数即:hash[magazine[i]-'a']++,然后再去遍历ransomNote,令hash[ransomNote[i]-'a']--;如果hash任意位置出现了hash[i]<0,则说明ransomNote出现了magazine中没有的字符或者ransomNote出现某个字符的次数>magazine出现这个字符的次数,返回false。对应代码如下:

class Solution {
public:
    bool canConstruct(string ransomNote, string magazine) {

        if(ransomNote.size() > magazine.size())
        {
            return false;
        }

        int hash[26] = {0};

        for(int i = 0; i < magazine.size(); i++)
        {
            hash[magazine[i]-'a']++;
        }

        for(int j = 0; j < ransomNote.size(); j++)
        {
            hash[ransomNote[j]-'a']--;

            if( hash[ransomNote[j]-'a'] < 0)
            {
                return false;
            }
        }

        return true;

    }
};

运行结果如下:

从入门到入土——Leetcode.15 三数之和:

15. 三数之和 - 力扣(LeetCode)

       对于三数之和这道题,相对于两数之和的难度有明显的上升,如果说两数之和作为梦开始的地方,那么三数之和可以说是梦结束的地方。这是因为本题相对于两数之和只需要控制两个变量进行判断变成了需要控制三个变量,并且,题目中要求了答案中不可以包含重复的三元组,例如[-1,1,0],[-1,0,1]就属于两个重复的三元组。

       虽然文章的标题为哈希表,但是对于三数之和,以及后面的四数之和,使用双指针的方法能更加容易的解决问题。具体方法如下:

首先利用vector创建一个二维数组result,用于接收合适的三元组。
       

例如对于题目中给定的数组,首先进行一次排序,即:

       再排序完成后,由于题目要求需要找到三个数的和==0,因此,要么数组中的元素全为0,要么数组中同时存在正数,负数,所以,如果数组的第一个数已经>0,则不可能存在三个数相加等于0的情况,因此直接返回result即可。

      对于本题所使用的双指针法,大致思路如下:

     首先利用循环对每一个元素都进行一次双指针的判定,例如对于数组中的第一个元素-4,令其作为三元组的第一个元素,其下一个元素用left表示,数组的最后一个元素用right表示。

    如果出现了nums[i]+nums[left]+nums[right]>0的情况,由于数组在开始进行了排序,只需要令right--即可。

   如果出现了nums[i]+nums[left]+nums[right]<0的情况,则令left++

   如果出现了nums[i]+nums[left]+nums[right]==0,则说明找到和符合条件的三元组,随后将这个三元组利用result进行保存。

   题目的大致解题思路如上,但是,由于题目要求不能出现的重复的三元组,因此,需要对每个元素进行去重判定,具体操作如下:

    首先是针对每次循环中,头个元素的去重判定:

   首先需要明确,如果确定了三元组中的两个数重复,则三元组一定重复,所以,对于第一个数据而言,需要判定其前一个数是否与自己相等,例如:

如图所示,其前一个数据与自身相等,则一定会造成重复。在这里,或许读者会有一位,为什么要求判定与前一个元素是否相同,即nums[i]==nums[i-1]而不是去判定nums[i]==nums[i+1]。对于这个问题,上图可以给出解释,如果去后一个数相等,则对于下面的情况:

      如果按照nums[i]==nums[i+1]进行判定重复,则在返回的正确的三元组中,会丢失一组数据,即[-1,-1,2]。因此,在判定是否重复时,需要按照nums[i]==nums[i-1]进行判定。如果满足重复的条件,则直接利用continue判定下一个元素即可。

     而对于其余的两个元素,即left,right所代表的元素,则也需要进行去重操作,例如对于下面的操作:

        例如,对于图中的三元组[-4,0,4],在找到后由于left后面的元素和right前面的元素都造成了重复,因此,如果left[i]==left[i+1],则令left++,如果right[i]==right[i-1],则令right--。具体效果如下:

       此时,left,right相邻的元素不造成重复,因此在去重完成后,令left++,right--,来寻找下一组满足条件的三元组。

具体代码如下:

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        //对数组整体排序
        sort(nums.begin(),nums.end());
        vector<vector<int>> result;

        for(int i = 0; i < nums.size();i++)
        {
            //检测首个元素是否>0
            if(nums[i] > 0)
            {
                return result;
            }
            //对于首个元素进行去重
            if(i > 0 && nums[i] == nums[i-1])
            {
                continue;
            }
            //定义双指针
            int left = i+1;
            int right = nums.size()-1;

            while(left < right)
            {
                if(nums[i]+nums[left]+nums[right] > 0)
                {
                    right--;
                }

                else if(nums[i]+nums[left]+nums[right] < 0)
                {
                    left++;
                }
                else
                {
                    result.push_back(vector<int>{nums[i],nums[left],nums[right]});
                    //对于双指针进行去重
                    while(left < right && nums[right] == nums[right-1])
                    {
                        right--;
                    }

                    while(left < right && nums[left] == nums[left+1])
                    {
                        left++;
                    }

                    right--;
                    left++;
                }

            
            }

        }

        return result;
        
    }
};

Leetcode.18 四数之和:

18. 四数之和 - 力扣(LeetCode)

对于本题,主题思路与三数之和大致相同。具体如下:

        在三数之和中,如果第一个元素>0,则说明不存在三元组使得三元组中元素的和==0,但是需要注意,在本题中,是给定一个targettarget可以<0。因此,在进行判断时,需要进行两次判断,即:(nums[i]>0&&target>0)如果满足,则说明,在数组中不存在合适的四元组使得其中四个数之和等于target,随后需要对这个元素进行去重,去重的方法相同,即nums[i]==nums[i-1]

       随后,令k=i+1,即:

将这两个数加和进行一次判断,即nums[i]+nums[k] > 0&&target>=0。随后,再对k位置的数据进行去重操作。

此时,前面的两个数据可以看作一个整体,最后再使用三数之和中的双指针法,即:

最后进行类似于三数之和一样的操作。本处不再展开说明,只给出代码:
 

class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {

        vector<vector<int>> result;
        sort(nums.begin(),nums.end());

        for(int k = 0; k < nums.size(); k++)
        {
            if(nums[k] > target && nums[k] >= 0)
            {
                break;
            }

            if(k > 0 && nums[k] == nums[k-1])
            {
                continue;
            }

            for(int i = k+1; i < nums.size(); i++)
            {
                if(nums[i] + nums[k] > target && nums[i]+nums[k] >= 0)
                {
                    break;
                }

                if(i > k+1 && nums[i] == nums[i-1])
                {
                    continue;
                }

                int left = i+1;
                int right = nums.size()-1;

                while(left < right)
                {
                    if((long)nums[i] + nums[k] +nums[left] +nums[right] > target)
                    {
                        right--;
                    }
                    else if((long)nums[i] + nums[k] +nums[left] +nums[right] < target)
                    {
                        left++;
                    }
                    else
                    {
                        result.push_back(vector<int>{nums[i],nums[k],nums[left],nums[right]});

                        while(left < right && nums[right] == nums[right-1])
                        {
                            right--;
                        }

                        while(left < right && nums[left] == nums[left+1])
                        {
                            left++;
                        }

                        right--;
                        left++;
                    }
                }
            }
        }
        return result;

    }
};

运行结果如下:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

起床写代码啦!

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值