代码随想录算法训练营:6/60

非科班学习算法day6 | LeetCode454: 四数相加||,Leetcode383:赎金信 ,Leetcode15:三数之和,LeetCode18:四数之和 

目录

介绍

一、基础概念补充:

二、LeetCode题目

1.LeetCode454:四数相加|| 

题目解析

 2.Leetcode383:赎金信 

题目解析

3.Leetcode15:三数之和

题目解析

4.Leetcode18:四数之和

题目解析

总结


介绍

包含LC的两道题目,还有相应概念的补充。

相关图解和更多版本:

代码随想录 (programmercarl.com)https://programmercarl.com/#%E6%9C%AC%E7%AB%99%E8%83%8C%E6%99%AF


一、基础概念补充:

                和day5的补充一样!

二、LeetCode题目

1.LeetCode454:四数相加|| 

题目链接:454. 四数相加 II - 力扣(LeetCode)

题目解析

       昨天理解了两数之和的思路是:先建立哈希表,然后在其中寻找target-当前元素的目标元素。那么四个数的和是不是也能用类似的思路?答案是肯定的。

        首先也是利用map存储前两个数组中元素相加的结果统计:用键存储两数之和,用键值存储两数出现的次数;然后在另外两个数组元素加和的结果中统计出现次数。

 c++代码如下:

class Solution {
public:
    int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) 
    {
        //定义map1存储前两个的遍历结果
        unordered_map<int, int> m;

        //对数组1和2的和进行存储
        for(auto n1:nums1)
        {
            for(auto n2:nums2)
            {
                m[n1 + n2]++;
                //if(m.find(n1+n2)!=m.end())
                //{
                //    m[a+b] = m[a+b]+1;
                //}
                //else
                //{
                //    m[a+b] = 1
                //}
            }
        }

        //初始化记录次数
        int count = 0;
        for(auto n3 : nums3)
        {
            for(auto n4 : nums4)
            {
                if(m.find(0-n3-n4) != m.end())
                {
                    count += m[0-n3-n4];
                }
            }
        }
        return count;
    }
};

注意点1:这里的写法m[n1 + n2]++;我一开始不太懂,表示的是备注中的写法。因为用加和的元素作为键,所以在map中有这个键就把键值加一,没有则创建,把键值赋予1.

                //if(m.find(n1+n2)!=m.end())
                //{
                //    m[a+b] = m[a+b]+1;
                //}
                //else
                //{
                //    m[a+b] = 1
                //}

注意点2: 这里用键存储和,用键值存储次数,这样操作起来更加简单合理,因为和可能多次出现,可以根据键索引到键值存储的次数直接修改,如果反过来:

class Solution {
public:
    int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) 
    {
        // 定义map1,但这次键是次数,值是和的集合(使用vector存储可能重复的和)
        unordered_map<int, vector<int>> m;

        // 对数组1和2的和进行存储,并初始化或增加对应和值的计数
        for(auto n1:nums1)
        {
            for(auto n2:nums2)
            {
                int sum = n1 + n2;
                auto it = m.find(sum);
                if (it != m.end()) {
                    it->second.push_back(sum); // 相同和值,增加一个实例到vector中
                } else {
                    m[1].push_back(sum); // 首次遇到此和值,设置次数为1
                }
            }
        }

        // 初始化记录次数
        int count = 0;
        for(auto n3 : nums3)
        {
            for(auto n4 : nums4)
            {
                int target = -(n3 + n4);
                if(m.find(target) != m.end())
                {
                    count += m[target].size(); // 累加该和值出现的次数
                }
            }
        }
        return count;
    }
};

 

 2.Leetcode383:赎金信 

题目链接:383. 赎金信 - 力扣(LeetCode)

题目解析

       很有电影画面的一道题,我认为这道题和349. 两个数组的交集 - 力扣(LeetCode)很相似,只不过我们需要注意的是:这里对母字符串中的元素放到子字符串中的这个操作的过程中,要求母串该元素不得少于字串该元素数量。

        选用的容器还是:数组。

 C++代码如下: 

class Solution {
public:
    bool canConstruct(string ransomNote, string magazine) 
    {
        //如果短,那么直接判定为假
        if(ransomNote.size() > magazine.size())
        {
            return false;
        }

        //设置字符串的哈希表
        int count[26] = {0};

        //遍历字符串2记录
        for(auto mag : magazine)
        {
            count[mag-'a']++;
        }

        //遍历字符串1检验
        for(auto ran : ransomNote)
        {
            count[ran-'a']--;
            if(count[ran -'a'] < 0)
            {
                return false;
            }
        }
        return true;
    }
};

 注意点1:检验的条件是遍历子串,如果相同元素的数量大于母串,那么就返回false

3.Leetcode15:三数之和

题目链接:15. 三数之和 - 力扣(LeetCode)

题目解析

       这道题我是后看的,我建议是先看18. 四数之和 - 力扣(LeetCode),其实思路是一样的。

C++代码如下:

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>> result;
        sort(nums.begin(), nums.end());
        // 找出a + b + c = 0
        // a = nums[i], b = nums[left], c = nums[right]
        for (int i = 0; i < nums.size(); i++) {
            // 排序之后如果第一个元素已经大于零,那么无论如何组合都不可能凑成三元组,直接返回结果就可以了
            if (nums[i] > 0) {
                return result;
            }
            // 错误去重a方法,将会漏掉-1,-1,2 这种情况
            /*
            if (nums[i] == nums[i + 1]) {
                continue;
            }
            */
            // 正确去重a方法
            if (i > 0 && nums[i] == nums[i - 1]) {
                continue;
            }
            int left = i + 1;
            int right = nums.size() - 1;
            while (right > left) {
                // 去重复逻辑如果放在这里,0,0,0 的情况,可能直接导致 right<=left 了,从而漏掉了 0,0,0 这种三元组
                /*
                while (right > left && nums[right] == nums[right - 1]) right--;
                while (right > left && nums[left] == nums[left + 1]) left++;
                */
                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]});
                    // 去重逻辑应该放在找到一个三元组之后,对b 和 c去重
                    while (right > left && nums[right] == nums[right - 1]) right--;
                    while (right > left && nums[left] == nums[left + 1]) left++;

                    // 找到答案时,双指针同时收缩
                    right--;
                    left++;
                }
            }

        }
        return result;
    }
};

注意点1:我觉得我的下面一道题写的不如代码随想录里去重讲的好,参考一下吧

代码随想录 (programmercarl.com)icon-default.png?t=N7T8https://programmercarl.com/0015.%E4%B8%89%E6%95%B0%E4%B9%8B%E5%92%8C.html#%E6%80%9D%E8%B7%AF

4.Leetcode18:四数之和

题目链接:18. 四数之和 - 力扣(LeetCode)

题目解析

       这题和前面四树之和的区别在:同一个数组检索,所以要求不能重复同一下标元素,也不能在结果中返回内容完全一样的数组,那么去重就成了最大的难题。

        这里虽然我也跟着写过哈希表的方法,判定非常多,而且很复杂,最后也是使用双指针的方法求解。

C++代码如下:

class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) 
    {
        //参考三数之和的做法,那么需要先循环遍历a和b

        //建立存储结果的v
        vector<vector<int>>result;
        
        //对数组排序
        sort(nums.begin(), nums.end());

        //循环!
        for(int i = 0; i < nums.size(); i++)
        {
            //a去重检查
            if(i > 0 && nums[i] == nums[i - 1]) continue;
            for(int j = i + 1; j < nums.size(); j++)
            {
                //b去重检查
                if(j > i + 1 && nums[j] == nums[j - 1])continue;

                //接下来设置双指针
                int left = j + 1;
                int right = nums.size() - 1;

                //检查条件调整指针
                while(left < right)
                {
                    if((long)nums[i] + nums[j] + nums[left] + nums[right] > target) right--;
                    else if((long)nums[i] + nums[j] + nums[left] + nums[right] < target) left++;
                    else
                    {
                        result.push_back(vector<int>{nums[i], nums[j], nums[left], nums[right]});

                        //检查去重
                        while(right > left && nums[right] == nums[right - 1])
                        {
                            right--;   
                        }
                        while(right > left && nums[left] == nums[left + 1])
                        {
                            left++;
                        }

                        //完成一轮检索,收缩指针
                        left++;
                        right--;
                    }
                }



            }
        }
        return result;
    }
    
};

注意点1:比较重要的一点,直接更改原数组,对其排序,这样子的好处,就可以在后面选择元素的时候,不用把当前元素和全部元素都比较一遍,而是就比对附近的元素。也是后面移动双指针的前提!

注意点2:基本的做法还有点类似于哈希表,不过有本质区别,哈希是寻找目标值,直接可以锁定;双指针是逐步逼近调整。

        将四个数先分成两组,第一组两个和用两层循环遍历,这样可以避免掉一个元素用了两次,那么同时还要有对相同元素的去重作用,因为[nums[a], nums[b], nums[c], nums[d]]按照我们排序的条件下返回必定是有序的,所以在一层循环里面就只需把相同元素用一个,

        这里插入举个例子就是:排序之后的数组可能是[-2,-2,.........],那对于外层循环i来说,位置0的-2循环之后的结果,不和位置1的-2循环的结果可能有相同的么(想象两个-2换位置),所以插入了去重操作。

注意点3:双指针的逻辑并不难,难的也是去重的操作能不能想好,我一开始试了先收缩指针,再去重,这样是会报错或者有重复解。

注意点4:这里用了一个类型转换(long),因为题目的要求已经超出了int的32位的范围,所以要做强制类型转换。

总结


打卡第6天,很难吖!明天之后会有很讨厌的字符串还有KMP算法,坚持!!!

  • 38
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
代码随想录算法训练营是一个优质的学习和讨论平台,提供了丰富的算法训练内容和讨论交流机会。在训练营中,学员们可以通过观看视频讲解来学习算法知识,并根据讲解内容进行刷题练习。此外,训练营还提供了刷题建议,例如先看视频、了解自己所使用的编程语言、使用日志等方法来提高刷题效果和语言掌握程度。 训练营中的讨论内容非常丰富,涵盖了各种算法知识点和解题方法。例如,在第14天的训练营中,讲解了二叉树的理论基础、递归遍历、迭代遍历和统一遍历的内容。此外,在讨论中还分享了相关的博客文章和配图,帮助学员更好地理解和掌握二叉树的遍历方法。 训练营还提供了每日的讨论知识点,例如在第15天的讨论中,介绍了层序遍历的方法和使用队列来模拟一层一层遍历的效果。在第16天的讨论中,重点讨论了如何进行调试(debug)的方法,认为掌握调试技巧可以帮助学员更好地解决问题和写出正确的算法代码。 总之,代码随想录算法训练营是一个提供优质学习和讨论环境的平台,可以帮助学员系统地学习算法知识,并提供了丰富的讨论内容和刷题建议来提高算法编程能力。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [代码随想录算法训练营每日精华](https://blog.csdn.net/weixin_38556197/article/details/128462133)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值