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

训练营第七天打卡继续,今天直面天命了一小会,并穿插干了些dirty work,然后做了后天出差准备,晚上刷题不能停!今天头两题用哈希表很好解决,后两题用哈希表的去重逻辑很复杂,不如用双指针!后两题不会做对着随想录又做了一遍后加深了印象,还得多练!


454.四数相加Ⅱ

题目链接

解题过程

  • 采用哈希表存数组1和数组2的和为key,相同和的次数为value。

知识点

  • 时间复杂度: O(n^2)
  • 空间复杂度: O(n^2),最坏情况下A和B的值各不相同,相加产生的数字个数为 n^2

哈希表

class Solution {
public:
    int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
        int result = 0;
        unordered_map<int, int>sum12;
        for (int i = 0; i < nums1.size(); i++) {
            for (int j = 0; j < nums2.size(); j++) {
                sum12[nums1[i] + nums2[j]]++;
            }
        }
        for (int i = 0; i < nums3.size(); i++) {
            for (int j = 0; j < nums4.size(); j++) {
                if (sum12.find(-nums3[i] - nums4[j]) != sum12.end()) {
                    result += sum12[-nums3[i] - nums4[j]];
                }
            }
        }
        return result;
    }
};

383.赎金信

题目链接

解题过程

  • 使用一个长度为26的数组记录小写字母的个数
  • magazine里的字母在数组中个数加1
  • ransomNote里的字母在数组中个数减1

知识点

  • 时间复杂度: O(n)
  • 空间复杂度: O(1)

数组哈希表

class Solution {
public:
    bool canConstruct(string ransomNote, string magazine) {
        vector<int>count(26);
        for (char c : magazine) count[c - 'a']++;
        for (char c : ransomNote) count[c - 'a']--;
        for (int num : count) if (num < 0) return false;
        return true;
    }
};

15.三数之和

题目链接

解题过程

  • 见到这题首先往哈希法的思路去想了,然而用哈希的一个必要条件就是结果能够有重复数组,此题不适合用哈希。

  • 没思路后,看随想录的思路才写出来。本题使用双指针的思路,用一个i指针遍历数组,leftright遍历除i外的元素,注意要对i left right三个指针都有去重操作。

  • 首先将数组排序,然后有一层for循环,i从下标0的地方开始,同时定一个下标left 定义在i+1的位置上,定义下标right 在数组结尾的位置上。

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

    如果 nums[i] + nums[left] + nums[right] < 0 说明 此时 三数之和小了,left 就向右移动,才能让三数之和大一些,直到left与right相遇为止。

知识点

  • 时间复杂度: O(n^2)

  • 空间复杂度: O(1)

  • 如果我们的写法是 这样:

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

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

  • 两数之和 就不能使用双指针法,因为1.两数之和 要求返回的是索引下标, 而双指针法一定要排序,一旦排序之后原数组的索引就被改变了。

    如果1.两数之和 要求返回的是数值的话,就可以使用双指针法了。

双指针法

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        sort(nums.begin(), nums.end());
        int left, right;
        vector<vector<int>>result;
        for (int i = 0; i < nums.size() - 2; i++) {
            if (nums[i] > 0) return result;
            if (i != 0 && nums[i] == nums[i - 1]) continue;
            left = i + 1;
            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({ nums[i], nums[left], nums[right] });
                    while (left < right && nums[left] == nums[left + 1]) left++;
                    while (left < right && nums[right] == nums[right - 1]) right--;
                    left++;
                    right--;
                }
            }
        }
        return result;
    }
};

18.四数之和

题目链接

解题过程

  • 运用三数之和的双指针思路做,大部分测试案例都能过,但有几个案例一直debug不过。
  • 四个数相加可能超过int的最大值,所以需要处理数据溢出问题

知识点

  • 时间复杂度: O(n^3)
  • 空间复杂度: O(1)
  • 本题剪枝不能和三数之和一样,因为是求和大于target,而不是0

双指针法

class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
        if (nums.size() < 4) return {};
        vector<vector<int>>result;
        sort(nums.begin(), nums.end());
        int left, right;
        for (int i = 0; i < nums.size() - 3; i++) {
            if (nums[i] >= 0 && nums[i] > target) break;
            if (i != 0 && nums[i] == nums[i - 1]) continue;
            for (int j = i + 1; j < nums.size() - 2; j++) {
                if (nums[i] + nums[j] >= 0 && nums[i] + nums[j] > target) break;
                if (j != i + 1 && nums[j] == nums[j - 1]) continue;
                left = j + 1;
                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({ nums[i], nums[j], nums[left], nums[right] });
                        while (left < right && nums[left] == nums[left + 1]) left++;
                        while (left < right && nums[right] == nums[right - 1]) right--;
                        left++;
                        right--;
                    }
                }
            }
        }
        return result;
    }
};

哈希表总结篇

哈希表的理论基础

  • 一般来说哈希表都是用来快速判断一个元素是否出现集合里

    对于哈希表,要知道哈希函数哈希碰撞在哈希表中的作用。

    哈希函数是把传入的key映射到符号表的索引上。

    哈希碰撞处理有多个key映射到相同索引上时的情景,处理碰撞的普遍方式是拉链法和线性探测法。

    接下来是常见的三种哈希结构:

    • 数组
    • set(集合)
    • map(映射)

    在C++语言中,setmap 都分别提供了三种数据结构,每种数据结构的底层实现和用途都有所不同

    什么时候用std::set,什么时候用std::multiset,什么时候用std::unordered_set,都是很有考究的。

    只有对这些数据结构的底层实现很熟悉,才能灵活使用,否则很容易写出效率低下的程序


哈希表经典题目

  • 数组作为哈希表

    • 242.有效的字母异位词

      • 提到了数组就是简单的哈希表,但是数组的大小是受限的!
      • 这道题目包含小写字母,那么使用数组来做哈希最合适不过。
    • 383.赎金信

      • 同样要求只有小写字母,给我们暗示数组的大小是受限的,用数组!
    • 上面两道题目用map确实可以,但使用map的空间消耗要比数组大一些,因为map要维护红黑树或者符号表,而且还要做哈希函数的运算。所以数组更加简单直接有效!


  • set作为哈希表

    • 349. 两个数组的交集

    • 用数组就不行了,需要用set。这道题目没有限制数值的大小,就无法使用数组来做哈希表了。

    • 主要因为如下两点:

      • 数组的大小是有限的,受到系统栈空间(不是数据结构的栈)的限制。
      • 如果数组空间够大,但哈希值比较少、特别分散、跨度非常大,使用数组就造成空间的极大浪费。
    • 关于set,C++ 给提供了如下三种可用的数据结构:

      • std::set
      • std::multiset
      • std::unordered_set

      std::setstd::multiset底层实现都是红黑树,std::unordered_set的底层实现是哈希, 使用unordered_set 读写效率是最高的,本题并不需要对数据进行排序,而且还不要让数据重复,所以选择unordered_set

    • 202.快乐数

    • 使用了unordered_set来判断一个数是否重复出现过。


  • map作为哈希表

    • 1.两数之和

    • 使用数组和set来做哈希法的局限。

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

      C++提供如下三种map:(详情请看关于哈希表,你该了解这些! (opens new window)

      • std::map
      • std::multimap
      • std::unordered_map
    • std::unordered_map 底层实现为哈希,std::map 和std::multimap 的底层实现是红黑树。

      同理,std::mapstd::multimap 的key也是有序的。两数之和中并不需要key有序,选择std::unordered_map 效率更高!

    • 454.四数相加

    • 需要哈希的地方都能找到map的身影。

    • 18. 四数之和15.三数之和关键差别是本题为四个独立的数组,只要找到A[i] + B[j] + C[k] + D[l] = 0就可以,不用考虑重复问题,而是一个数组(集合)里找到和为0的组合,可就难很多了!

    • 18. 四数之和15.三数之和都推荐使用双指针法,方便去重

  • 29
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 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、付费专栏及课程。

余额充值