代码随想录算法训练营第七天|454.四数相加II、383. 赎金信、15. 三数之和、18. 四数之和

今日学习的文章和视频链接

454文章链接: link
454视频讲解链接: link
383文章链接: link
383视频暂无讲解
15文章链接: link
15视频讲解链接: link
18文章链接: link
18视频讲解链接: link

第454题.四数相加II

看到题目第一想法

题目描述:给你四个整数数组 nums1、nums2、nums3 和 nums4 ,数组长度都是 n ,请你计算有多少个元组 (i, j, k, l) 能满足:

0 <= i, j, k, l < n
nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0

解题思路:四数相加不用考虑去重,所以想到使用哈希表来处理本题。
我们不仅要记录a + b在集合里出现,还要统计a + b出现过多少次,之后在可以和c + d做一个映射。所以我们不仅要统计是否出现过,还要统计出现过的次数。所以我们选择使用map,用key存是否出现过,value存出现过的次数。

看完代码随想录后的想法

解题步骤:

  1. 首先定义 一个unordered_map,key放a和b两数之和,value 放a和b两数之和出现的次数。
  2. 遍历nums1和nums2数组,统计两个数组元素之和,和出现的次数,放到map中。
  3. 定义int变量count,用来统计 a+b+c+d = 0 出现的次数。
  4. 再遍历nums3和nums4数组,找到如果 0-(c+d) 在map中出现过的话,就用count把map中key对应的value也就是出现次数统计出来。
  5. 最后返回统计值 count 。

实现过程中遇到的困难

最开始的时候想到了先遍历一个数组,然后再遍历另外三个数组是否在对应集合中出现过。这样再遍历后面三个数组时,时间复杂度为O( n 3 n^3 n3),而先遍历前两个数组,再遍历后两个数组的时间复杂度为 O ( n 2 ) O(n^2) On2

代码

class Solution {
    
    
public:
    int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
    
    
        unordered_map<int, int> umap; //key:a+b的数值,value:a+b数值出现的次数
        // 遍历nums1和nums2数组,统计两个数组元素之和,和出现的次数,放到map中
        for (int a : nums1) {
    
    
            for (int b : nums2) {
    
    
                umap[a + b]++;
            }
        }
        int count = 0; // 统计a+b+c+d = 0 出现的次数
        // 在遍历nums3和nums4数组,找到如果 0-(c+d) 在map中出现过的话,就把map中key对应的value也就是出现次数统计出来。
        for (int c : nums3) {
    
    
            for (int d : nums4) {
    
    
                if (umap.find(0 - (c + d)) != umap.end()) {
    
    
                    count += umap[0 - (c + d)];
                }
            }
        }
        return count;
    }
};

383. 赎金信

看到题目第一想法

题目描述:给你两个字符串:ransomNote 和 magazine ,判断 ransomNote 能不能由 magazine 里面的字符构成。

如果可以,返回 true ;否则返回 false 。

magazine 中的每个字符只能在 ransomNote 中使用一次。

解题思路:发现本题与之前的242.有效的字母异位词相似,只是本题是求 字符串a能否组成字符串b,而不用管字符串b 能不能组成字符串a

看完代码随想录后的想法

判断第一个字符串ransom能不能由第二个字符串magazines里面的字符构成时需注意两点:

  • 第一点“magazine 中的每个字符只能在 ransomNote 中使用一次”
  • 第二点ransomNote 和 magazine 由小写英文字母组成

因为本题目只有小写字母,所以采用一个长度为26的数组来记录magazine里字母出现的次数。

再用ransomNote去验证这个数组是否包含ransomNote所需的所有字母。

使用哈希法来解决本题。

实现过程中遇到的困难

本想着使用map,但是感觉有点大材小用,看了代码随想录了解到在本题的情况下,使用map的空间消耗要比数组大一些的,因为map要维护红黑树或者哈希表,而且还要做哈希函数,是费时的!数据量大的话就能体现出来差别了。 所以数组更加简单直接有效!

代码

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

第15题. 三数之和

看到题目第一想法

题目描述
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。

注意: 答案中不可以包含重复的三元组。

解题思路:依然想着使用哈希法去解决本问题,但题目中要求我们去重,但是对去重的部分思路不是很清晰。

看完代码随想录后的想法

随想录中给了哈希法双指针法两种解法。

哈希法

先使用两层for循环确定a + b,然后使用哈希法来确定 0-(a+b) 是否在 数组里出现过。

但要把符合条件的三元组放进vector中,然后再去重,这样是非常费时的,很容易超时。

时间复杂度可以做到O(n^2),但还是比较费时的,因为不好做剪枝操作

双指针法

双指针法要比哈希法高效,我们看如下动画:
在这里插入图片描述
首先将数组排序,然后有一层for循环,i从下标0的地方开始,同时将一个下标left 定义在i+1的位置上,定义下标right 在数组结尾的位置上。

依然还是在数组中找到 abc 使得a + b +c =0,我们这里相当于 a = nums[i],b = nums[left],c = nums[right]。

移动left 和right:

  • 如果nums[i] + nums[left] + nums[right] > 0 就说明 此时三数之和大了,因为数组是排序后了,所以right下标就应该向左移动,这样才能让三数之和小一些。
  • 如果 nums[i] + nums[left] + nums[right] < 0 说明 此时 三数之和小了,left 就向右移动,才能让三数之和大一些。

直到left与right相遇为止。

实现过程中遇到的困难

去重的操作还不是很熟悉

去重逻辑思考

其实主要考虑三个数的去重。 a, b ,c, 对应的就是 nums[i],nums[left],nums[right]

a的去重

a 如果重复了怎么办,a是nums里遍历的元素,那么应该直接跳过去。

但是是判断 nums[i] 与 nums[i + 1]是否相同,还是判断 nums[i] 与 nums[i-1] 是否相同?都是和 nums[i]进行比较,是比较它的前一个,还是比较他的后一个?

如果我们比较它的后一个:

if (nums[i] == nums[i + 1]) {
    
     // 去重操作
    continue;
}

那就我们就把 三元组中出现重复元素的情况直接pass掉了。 例如{-1, -1 ,2} 这组数据,当遍历到第一个-1 的时候,判断 下一个也是-1,那这组数据就pass了。

但要记住我们是不能有重复的三元组,但三元组内的元素是可以重复的!

此时这里是有两个重复的维度。

所以应该比较它的前一个:

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

这么写就是当前使用 nums[i],我们判断前一位是不是一样的元素,在看 {-1, -1 ,2} 这组数据,当遍历到 第一个 -1 的时候,只要前一位没有-1,那么 {-1, -1 ,2} 这组数据一样可以收录到 结果集里。

b与c的去重

当去重的逻辑中加入

代码

class Solution {
    
    
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
    
    
        unordered_set<int> result_set;
        unordered_set<int> nums_set(nums1.begin(),nums1.end());
        for(int num : nums2){
    
    
            if(nums_set.find(num) != nums_set.end()){
    
    
                result_set.insert(num);
            }
        }
        return vector<int>(result_set.begin(),result_set.end());
    }
};

第202题. 快乐数

看到题目第一想法

题目描述:

编写一个算法来判断一个数 n 是不是快乐数。

「快乐数」定义为:对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和,然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。如果 可以变为 1,那么这个数就是快乐数。

如果 n 是快乐数就返回 True ;不是,则返回 False 。

有如下想法:

关键:** 若无限循环,那么求和的过程中,sum会重复出现**

所以采用哈希法来判断这个sum是否重复出现,如果重复了就是return false, 否则一直找到sum为1为止。

判断sum是否重复出现就可以使用unordered_set

看完代码随想录后的想法

思路相同

实现过程中遇到的困难

对取数值各个位上的单数操作不是很熟悉

代码

class Solution {
    
    
public:
    // 取数值各个位上的单数之和
    int getSum(int n) {
    
    
        int sum = 0;
        while (n) {
    
    
            sum += (n % 10) * (n % 10);
            n /= 10;
        }
        return sum;
    }
    bool isHappy(int n) {
    
    
        unordered_set<int> set;
        while(1) {
    
    
            int sum = getSum(n);
            if (sum == 1) {
    
    
                return true;
            }
            // 如果这个sum曾经出现过,说明已经陷入了无限循环了,立刻return false
            if (set.find(sum) != set.end()) {
    
    
                return false;
            } else {
    
    
                set.insert(sum);
            }
            n = sum;
        }
    }
};

1. 两数之和

看到题目第一想法

题目描述:

给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。

本题解决有困难,对map不是很了解。

看完代码随想录后的想法

本题需要一个集合来存放我们遍历过的元素,然后在遍历数组的时候去询问这个集合,某元素是否遍历过,也就是 是否出现在这个集合。

本题我们不仅要知道元素有没有遍历过,还要知道这个元素对应的下标,需要使用 key value结构来存放,key来存元素value来存下标,那么使用map正合适。

为何使用之前用过的set呢?

  • 数组的大小是受限制的,而且如果元素很少,而哈希值太大会造成内存空间的浪费。
  • set是一个集合,里面放的元素只能是一个key,而两数之和这道题目,不仅要判断y是否存在而且还要记录y的下标位置,因为要返回x 和 y的下标。所以set 也不能用。

而map是一种key value的存储结构,可以用key保存数值,用value在保存数值所在的下标。

在map在C++的3种类型中,选择std::unordered_map,因为这道题目中并不需要key有序,选择std::unordered_map 效率更高

使用map需注意如下两点:

  • map用来做什么
  • map中key和value分别表示什么

关于第一点,map目的用来存放我们访问过的元素,因为遍历数组的时候,需要记录我们之前遍历过哪些元素和对应的下标,这样才能找到与当前元素相匹配的(也就是相加等于target)

关于第二点,这道题我们需要给出一个元素,判断这个元素是否出现过,如果出现过,返回这个元素的下标。

判断元素是否出现,这个元素就要作为key,所以数组中的元素作为key,有key对应的就是value,value用来存下标。

所以 map中的存储结构为 {key:数据元素,value:数组元素对应的下标}。

在遍历数组的时候,只需要向map去查询是否有和目前遍历元素比配的数值,如果有,就是找到的匹配对,如果没有,就把目前遍历的元素放进map中,因为map存放的就是我们访问过的元素。

实现过程如下图:
在这里插入图片描述

实现过程中遇到的困难

对取数值各个位上的单数操作不是很熟悉

代码


今日收获

1.对哈希表的基础理论有所了解

2.知道了set和map的应用场景

3.使用模板库还是不太熟练,后续需要加强

今日学习时长3h

该文章图片均来自Carl哥的代码随想录,在此特别感谢

猜你喜欢

转载自blog.csdn.net/m0_46555669/article/details/127079009
今日推荐