From 975e42a1578465c43c9d34e75a1488a23c7c5a2a Mon Sep 17 00:00:00 2001 From: youngyangyang04 <826123027@qq.com> Date: Tue, 27 Apr 2021 10:48:20 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E5=8F=8C=E6=8C=87=E9=92=88?= =?UTF-8?q?=EF=BC=8C=E5=AD=97=E7=AC=A6=E4=B8=B2Markdown=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 28 +-- ...44\346\225\260\344\271\213\345\222\214.md" | 87 +++++++ ...11\346\225\260\344\271\213\345\222\214.md" | 213 ++++++++++++++++++ ...33\346\225\260\344\271\213\345\222\214.md" | 123 ++++++++++ ...2.\345\277\253\344\271\220\346\225\260.md" | 86 +++++++ ...15\345\274\202\344\275\215\350\257\215.md" | 87 +++++++ ...04\347\232\204\344\272\244\351\233\206.md" | 78 +++++++ ...3.\350\265\216\351\207\221\344\277\241.md" | 113 ++++++++++ ...\346\225\260\347\233\270\345\212\240II.md" | 90 ++++++++ ...07\351\222\210\346\200\273\347\273\223.md" | 11 - ...14\350\241\250\346\200\273\347\273\223.md" | 17 +- ...06\350\256\272\345\237\272\347\241\200.md" | 17 +- 12 files changed, 910 insertions(+), 40 deletions(-) create mode 100644 "problems/0001.\344\270\244\346\225\260\344\271\213\345\222\214.md" create mode 100644 "problems/0015.\344\270\211\346\225\260\344\271\213\345\222\214.md" create mode 100644 "problems/0018.\345\233\233\346\225\260\344\271\213\345\222\214.md" create mode 100644 "problems/0202.\345\277\253\344\271\220\346\225\260.md" create mode 100644 "problems/0242.\346\234\211\346\225\210\347\232\204\345\255\227\346\257\215\345\274\202\344\275\215\350\257\215.md" create mode 100644 "problems/0349.\344\270\244\344\270\252\346\225\260\347\273\204\347\232\204\344\272\244\351\233\206.md" create mode 100644 "problems/0383.\350\265\216\351\207\221\344\277\241.md" create mode 100644 "problems/0454.\345\233\233\346\225\260\347\233\270\345\212\240II.md" diff --git a/README.md b/README.md index 56148ddc77..1a9bff13cd 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ > 1. **介绍**:本项目是一套完整的刷题计划,旨在帮助大家少走弯路,循序渐进学算法,[关注作者](#关于作者) > 2. **PDF版本** : [「代码随想录」算法精讲 PDF 版本](https://mp.weixin.qq.com/s/RsdcQ9umo09R6cfnwXZlrQ) 。 > 3. **知识星球** : 面试技巧/如何选择offer/大厂内推/职场规则/简历修改/技术分享/程序人生。欢迎加入[我的知识星球](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) 。 -> 4. **转载须知** :以下所有文章如非文首说明皆为我([程序员Carl](https://github.com/youngyangyang04))的原创。引用本项目文章请注明出处,发现恶意抄袭或搬运,会动用法律武器维护自己的权益。让我们一起维护一个良好的技术创作环境! +> 4. **转载须知** :以下所有文章皆为我([程序员Carl](https://github.com/youngyangyang04))的原创。引用本项目文章请注明出处,发现恶意抄袭或搬运,会动用法律武器维护自己的权益。让我们一起维护一个良好的技术创作环境!

@@ -120,16 +120,16 @@ ## 哈希表 -1. [关于哈希表,你该了解这些!](https://mp.weixin.qq.com/s/g8N6WmoQmsCUw3_BaWxHZA) -2. [哈希表:可以拿数组当哈希表来用,但哈希值不要太大](https://mp.weixin.qq.com/s/vM6OszkM6L1Mx2Ralm9Dig) -3. [哈希表:哈希值太大了,还是得用set](https://mp.weixin.qq.com/s/N9iqAchXreSVW7zXUS4BVA) -4. [哈希表:用set来判断快乐数](https://mp.weixin.qq.com/s/G4Q2Zfpfe706gLK7HpZHpA) -5. [哈希表:map等候多时了](https://mp.weixin.qq.com/s/uVAtjOHSeqymV8FeQbliJQ) -6. [哈希表:其实需要哈希的地方都能找到map的身影](https://mp.weixin.qq.com/s/Ue8pKKU5hw_m-jPgwlHcbA) -7. [哈希表:这道题目我做过?](https://mp.weixin.qq.com/s/sYZIR4dFBrw_lr3eJJnteQ) -8. [哈希表:解决了两数之和,那么能解决三数之和么?](https://mp.weixin.qq.com/s/r5cgZFu0tv4grBAexdcd8A) -9. [双指针法:一样的道理,能解决四数之和](https://mp.weixin.qq.com/s/nQrcco8AZJV1pAOVjeIU_g) -10. [哈希表:总结篇!(每逢总结必经典)](https://mp.weixin.qq.com/s/1s91yXtarL-PkX07BfnwLg) +1. [关于哈希表,你该了解这些!](./problems/哈希表理论基础.md) +2. [哈希表:可以拿数组当哈希表来用,但哈希值不要太大](./problems/0242.有效的字母异位词.md) +3. [哈希表:哈希值太大了,还是得用set](./problems/0349.两个数组的交集.md) +4. [哈希表:用set来判断快乐数](./problems/0202.快乐数.md) +5. [哈希表:map等候多时了](./problems/0001.两数之和.md) +6. [哈希表:其实需要哈希的地方都能找到map的身影](./problems/0454.四数相加II.md) +7. [哈希表:这道题目我做过?](./problems/0383.赎金信.md) +8. [哈希表:解决了两数之和,那么能解决三数之和么?](./problems/0015.三数之和.md) +9. [双指针法:一样的道理,能解决四数之和](./problems/0018.四数之和.md) +10. [哈希表:总结篇!(每逢总结必经典)](./problems/哈希表总结.md) ## 字符串 @@ -153,9 +153,9 @@ 4. [字符串:花式反转还不够!](./problems/0151.翻转字符串里的单词.md) 5. [链表:听说过两天反转链表又写不出来了?](./problems/0206.翻转链表.md) 6. [链表:环找到了,那入口呢?](./problems/0142.环形链表II.md) -7. [哈希表:解决了两数之和,那么能解决三数之和么?](https://mp.weixin.qq.com/s/r5cgZFu0tv4grBAexdcd8A) -8. [双指针法:一样的道理,能解决四数之和](https://mp.weixin.qq.com/s/nQrcco8AZJV1pAOVjeIU_g) -9. [双指针法:总结篇!](https://mp.weixin.qq.com/s/_p7grwjISfMh0U65uOyCjA) +7. [哈希表:解决了两数之和,那么能解决三数之和么?](./problems/0015.三数之和.md) +8. [双指针法:一样的道理,能解决四数之和](./problems/0018.四数之和.md) +9. [双指针法:总结篇!](./problems/双指针总结.md) ## 栈与队列 diff --git "a/problems/0001.\344\270\244\346\225\260\344\271\213\345\222\214.md" "b/problems/0001.\344\270\244\346\225\260\344\271\213\345\222\214.md" new file mode 100644 index 0000000000..832273ed97 --- /dev/null +++ "b/problems/0001.\344\270\244\346\225\260\344\271\213\345\222\214.md" @@ -0,0 +1,87 @@ + +

+ + + + +

+ +## 1. 两数之和 + +https://leetcode-cn.com/problems/two-sum/ + +给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。 + +你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。 + +**示例:** + +给定 nums = [2, 7, 11, 15], target = 9 + +因为 nums[0] + nums[1] = 2 + 7 = 9 + +所以返回 [0, 1] + + +## 思路 + +很明显暴力的解法是两层for循环查找,时间复杂度是O(n^2)。 + +建议大家做这道题目之前,先做一下这两道 +* [242. 有效的字母异位词](https://mp.weixin.qq.com/s/vM6OszkM6L1Mx2Ralm9Dig) +* [349. 两个数组的交集](https://mp.weixin.qq.com/s/N9iqAchXreSVW7zXUS4BVA) + +[242. 有效的字母异位词](https://mp.weixin.qq.com/s/vM6OszkM6L1Mx2Ralm9Dig) 这道题目是用数组作为哈希表来解决哈希问题,[349. 两个数组的交集](https://mp.weixin.qq.com/s/N9iqAchXreSVW7zXUS4BVA)这道题目是通过set作为哈希表来解决哈希问题。 + +本题呢,则要使用map,那么来看一下使用数组和set来做哈希法的局限。 + +* 数组的大小是受限制的,而且如果元素很少,而哈希值太大会造成内存空间的浪费。 +* set是一个集合,里面放的元素只能是一个key,而两数之和这道题目,不仅要判断y是否存在而且还要记录y的下表位置,因为要返回x 和 y的下表。所以set 也不能用。 + +此时就要选择另一种数据结构:map ,map是一种key value的存储结构,可以用key保存数值,用value在保存数值所在的下表。 + +C++中map,有三种类型: + +|映射 |底层实现 | 是否有序 |数值是否可以重复 | 能否更改数值|查询效率 |增删效率| +|---|---| --- |---| --- | --- | ---| +|std::map |红黑树 |key有序 |key不可重复 |key不可修改 | O(logn)|O(logn) | +|std::multimap | 红黑树|key有序 | key可重复 | key不可修改|O(logn) |O(logn) | +|std::unordered_map |哈希表 | key无序 |key不可重复 |key不可修改 |O(1) | O(1)| + +std::unordered_map 底层实现为哈希表,std::map 和std::multimap 的底层实现是红黑树。 + +同理,std::map 和std::multimap 的key也是有序的(这个问题也经常作为面试题,考察对语言容器底层的理解)。 更多哈希表的理论知识请看[关于哈希表,你该了解这些!](https://mp.weixin.qq.com/s/g8N6WmoQmsCUw3_BaWxHZA)。 + +**这道题目中并不需要key有序,选择std::unordered_map 效率更高!** + +解题思路动画如下: + + + +C++代码: + +```C++ +class Solution { +public: + vector twoSum(vector& nums, int target) { + std::unordered_map map; + for(int i = 0; i < nums.size(); i++) { + auto iter = map.find(target - nums[i]); + if(iter != map.end()) { + return {iter->second, i}; + } + map.insert(pair(nums[i], i)); + } + return {}; + } +}; +``` + + +------------------------ + +* 微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) +* B站:[代码随想录](https://space.bilibili.com/525438321) +* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) + +![](../pics/公众号.png) diff --git "a/problems/0015.\344\270\211\346\225\260\344\271\213\345\222\214.md" "b/problems/0015.\344\270\211\346\225\260\344\271\213\345\222\214.md" new file mode 100644 index 0000000000..5aa1f14398 --- /dev/null +++ "b/problems/0015.\344\270\211\346\225\260\344\271\213\345\222\214.md" @@ -0,0 +1,213 @@ + +

+ + + + +

+ + +> 用哈希表解决了[两数之和](https://mp.weixin.qq.com/s/uVAtjOHSeqymV8FeQbliJQ),那么三数之和呢? + +# 第15题. 三数之和 + +https://leetcode-cn.com/problems/3sum/ + +给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。 + +**注意:** 答案中不可以包含重复的三元组。 + +示例: + +给定数组 nums = [-1, 0, 1, 2, -1, -4], + +满足要求的三元组集合为: +[ + [-1, 0, 1], + [-1, -1, 2] +] + + +# 思路 + +**注意[0, 0, 0, 0] 这组数据** + +## 哈希解法 + +两层for循环就可以确定 a 和b 的数值了,可以使用哈希法来确定 0-(a+b) 是否在 数组里出现过,其实这个思路是正确的,但是我们有一个非常棘手的问题,就是题目中说的不可以包含重复的三元组。 + +把符合条件的三元组放进vector中,然后在去去重,这样是非常费时的,很容易超时,也是这道题目通过率如此之低的根源所在。 + +去重的过程不好处理,有很多小细节,如果在面试中很难想到位。 + +时间复杂度可以做到O(n^2),但还是比较费时的,因为不好做剪枝操作。 + +大家可以尝试使用哈希法写一写,就知道其困难的程度了。 + +哈希法C++代码: +```C++ +class Solution { +public: + vector> threeSum(vector& nums) { + vector> result; + sort(nums.begin(), nums.end()); + // 找出a + b + c = 0 + // a = nums[i], b = nums[j], c = -(a + b) + for (int i = 0; i < nums.size(); i++) { + // 排序之后如果第一个元素已经大于零,那么不可能凑成三元组 + if (nums[i] > 0) { + continue; + } + if (i > 0 && nums[i] == nums[i - 1]) { //三元组元素a去重 + continue; + } + unordered_set set; + for (int j = i + 1; j < nums.size(); j++) { + if (j > i + 2 + && nums[j] == nums[j-1] + && nums[j-1] == nums[j-2]) { // 三元组元素b去重 + continue; + } + int c = 0 - (nums[i] + nums[j]); + if (set.find(c) != set.end()) { + result.push_back({nums[i], nums[j], c}); + set.erase(c);// 三元组元素c去重 + } else { + set.insert(nums[j]); + } + } + } + return result; + } +}; +``` + +## 双指针 + +**其实这道题目使用哈希法并不十分合适**,因为在去重的操作中有很多细节需要注意,在面试中很难直接写出没有bug的代码。 + +而且使用哈希法 在使用两层for循环的时候,能做的剪枝操作很有限,虽然时间复杂度是O(n^2),也是可以在leetcode上通过,但是程序的执行时间依然比较长 。 + +接下来我来介绍另一个解法:双指针法,**这道题目使用双指针法 要比哈希法高效一些**,那么来讲解一下具体实现的思路。 + +动画效果如下: + +![15.三数之和](https://code-thinking.cdn.bcebos.com/gifs/15.%E4%B8%89%E6%95%B0%E4%B9%8B%E5%92%8C.gif) + +拿这个nums数组来举例,首先将数组排序,然后有一层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相遇为止。 + +时间复杂度:O(n^2)。 + + +## 双指针法C++代码 + +```C++ +class Solution { +public: + vector> threeSum(vector& nums) { + vector> 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; + } + // 错误去重方法,将会漏掉-1,-1,2 这种情况 + /* + if (nums[i] == nums[i + 1]) { + continue; + } + */ + // 正确去重方法 + 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{nums[i], nums[left], nums[right]}); + // 去重逻辑应该放在找到一个三元组之后 + while (right > left && nums[right] == nums[right - 1]) right--; + while (right > left && nums[left] == nums[left + 1]) left++; + + // 找到答案时,双指针同时收缩 + right--; + left++; + } + } + + } + return result; + } +}; +``` + +# 思考题 + +既然三数之和可以使用双指针法,我们之前讲过的[两数之和](https://mp.weixin.qq.com/s/uVAtjOHSeqymV8FeQbliJQ),可不可以使用双指针法呢? + +如果不能,题意如何更改就可以使用双指针法呢? **大家留言说出自己的想法吧!** + +两数之和 就不能使用双指针法,因为[两数之和](https://mp.weixin.qq.com/s/uVAtjOHSeqymV8FeQbliJQ)要求返回的是索引下表, 而双指针法一定要排序,一旦排序之后原数组的索引就被改变了。 + +如果[两数之和](https://mp.weixin.qq.com/s/uVAtjOHSeqymV8FeQbliJQ)要求返回的是数值的话,就可以使用双指针法了。 + +> 更过算法干货文章持续更新,可以微信搜索「代码随想录」第一时间围观,关注后,回复「Java」「C++」 「python」「简历模板」「数据结构与算法」等等,就可以获得我多年整理的学习资料。 + + + +## tmp + +```python +class Solution: + def threeSum(self, nums: List[int]) -> List[List[int]]: + res = [] + nums.sort() + for i in range(len(nums)): + if nums[i] > 0 : + return res + if nums[i] == nums[i-1] and i>0: + continue + left = i + 1 + right = len(nums) - 1 + while left < right: + if nums[i] + nums[left] + nums[right] > 0: + right -= 1 + elif nums[i] + nums[left] + nums[right] < 0: + left += 1 + else: + res.append([nums[i], nums[left], nums[right]]) + while left < right and nums[right] == nums[right-1]: + right -= 1 + while left < right and nums[left] == nums[left+1]: + left += 1 + left += 1 + right -= 1 + return res +``` + +------------------------ + +* 微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) +* B站:[代码随想录](https://space.bilibili.com/525438321) +* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) + +![](../pics/公众号.png) diff --git "a/problems/0018.\345\233\233\346\225\260\344\271\213\345\222\214.md" "b/problems/0018.\345\233\233\346\225\260\344\271\213\345\222\214.md" new file mode 100644 index 0000000000..ad7134d9cd --- /dev/null +++ "b/problems/0018.\345\233\233\346\225\260\344\271\213\345\222\214.md" @@ -0,0 +1,123 @@ + +

+ + + + +

+ +> 一样的道理,能解决四数之和 +> 那么五数之和、六数之和、N数之和呢? + +# 第18题. 四数之和 + +https://leetcode-cn.com/problems/4sum/ + +题意:给定一个包含 n 个整数的数组 nums 和一个目标值 target,判断 nums 中是否存在四个元素 a,b,c 和 d ,使得 a + b + c + d 的值与 target 相等?找出所有满足条件且不重复的四元组。 + +**注意:** + +答案中不可以包含重复的四元组。 + +示例: +给定数组 nums = [1, 0, -1, 0, -2, 2],和 target = 0。 +满足要求的四元组集合为: +[ + [-1, 0, 0, 1], + [-2, -1, 1, 2], + [-2, 0, 0, 2] +] + +# 思路 + +四数之和,和[三数之和](https://mp.weixin.qq.com/s/r5cgZFu0tv4grBAexdcd8A)是一个思路,都是使用双指针法, 基本解法就是在[三数之和](https://mp.weixin.qq.com/s/r5cgZFu0tv4grBAexdcd8A) 的基础上再套一层for循环。 + +但是有一些细节需要注意,例如: 不要判断`nums[k] > target` 就返回了,三数之和 可以通过 `nums[i] > 0` 就返回了,因为 0 已经是确定的数了,四数之和这道题目 target是任意值。(大家亲自写代码就能感受出来) + +[三数之和](https://mp.weixin.qq.com/s/r5cgZFu0tv4grBAexdcd8A)的双指针解法是一层for循环num[i]为确定值,然后循环内有left和right下表作为双指针,找到nums[i] + nums[left] + nums[right] == 0。 + +四数之和的双指针解法是两层for循环nums[k] + nums[i]为确定值,依然是循环内有left和right下表作为双指针,找出nums[k] + nums[i] + nums[left] + nums[right] == target的情况,三数之和的时间复杂度是O(n^2),四数之和的时间复杂度是O(n^3) 。 + +那么一样的道理,五数之和、六数之和等等都采用这种解法。 + +对于[三数之和](https://mp.weixin.qq.com/s/r5cgZFu0tv4grBAexdcd8A)双指针法就是将原本暴力O(n^3)的解法,降为O(n^2)的解法,四数之和的双指针解法就是将原本暴力O(n^4)的解法,降为O(n^3)的解法。 + +之前我们讲过哈希表的经典题目:[四数相加II](https://mp.weixin.qq.com/s/Ue8pKKU5hw_m-jPgwlHcbA),相对于本题简单很多,因为本题是要求在一个集合中找出四个数相加等于target,同时四元组不能重复。 + +而[四数相加II](https://mp.weixin.qq.com/s/Ue8pKKU5hw_m-jPgwlHcbA)是四个独立的数组,只要找到A[i] + B[j] + C[k] + D[l] = 0就可以,不用考虑有重复的四个元素相加等于0的情况,所以相对于本题还是简单了不少! + +我们来回顾一下,几道题目使用了双指针法。 + +双指针法将时间复杂度O(n^2)的解法优化为 O(n)的解法。也就是降一个数量级,题目如下: +* [0027.移除元素](https://mp.weixin.qq.com/s/wj0T-Xs88_FHJFwayElQlA) +* [15.三数之和](https://mp.weixin.qq.com/s/r5cgZFu0tv4grBAexdcd8A) +* [18.四数之和](https://mp.weixin.qq.com/s/nQrcco8AZJV1pAOVjeIU_g) + +双指针来记录前后指针实现链表反转: + +* [206.反转链表](https://mp.weixin.qq.com/s/pnvVP-0ZM7epB8y3w_Njwg) + +使用双指针来确定有环: + +* [142题.环形链表II](https://mp.weixin.qq.com/s/_QVP3IkRZWx9zIpQRgajzA) + +双指针法在数组和链表中还有很多应用,后面还会介绍到。 + +C++代码 + +```C++ +class Solution { +public: + vector> fourSum(vector& nums, int target) { + vector> result; + sort(nums.begin(), nums.end()); + for (int k = 0; k < nums.size(); k++) { + // 这种剪枝是错误的,这道题目target 是任意值 + // if (nums[k] > target) { + // return result; + // } + // 去重 + if (k > 0 && nums[k] == nums[k - 1]) { + continue; + } + for (int i = k + 1; i < nums.size(); i++) { + // 正确去重方法 + if (i > k + 1 && nums[i] == nums[i - 1]) { + continue; + } + int left = i + 1; + int right = nums.size() - 1; + while (right > left) { + if (nums[k] + nums[i] + nums[left] + nums[right] > target) { + right--; + } else if (nums[k] + nums[i] + nums[left] + nums[right] < target) { + left++; + } else { + result.push_back(vector{nums[k], nums[i], nums[left], nums[right]}); + // 去重逻辑应该放在找到一个四元组之后 + while (right > left && nums[right] == nums[right - 1]) right--; + while (right > left && nums[left] == nums[left + 1]) left++; + + // 找到答案时,双指针同时收缩 + right--; + left++; + } + } + + } + } + return result; + } + +}; +``` + + +------------------------ + +* 微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) +* B站:[代码随想录](https://space.bilibili.com/525438321) +* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) + +![](../pics/公众号.png) + diff --git "a/problems/0202.\345\277\253\344\271\220\346\225\260.md" "b/problems/0202.\345\277\253\344\271\220\346\225\260.md" new file mode 100644 index 0000000000..4e4eb4734e --- /dev/null +++ "b/problems/0202.\345\277\253\344\271\220\346\225\260.md" @@ -0,0 +1,86 @@ + +

+ + + + +

+ + +> 该用set的时候,还是得用set + +# 第202题. 快乐数 + +https://leetcode-cn.com/problems/happy-number/ + +编写一个算法来判断一个数 n 是不是快乐数。 + +「快乐数」定义为:对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和,然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。如果 可以变为  1,那么这个数就是快乐数。 + +如果 n 是快乐数就返回 True ;不是,则返回 False 。 + +**示例:** + +输入:19 +输出:true +解释: +1^2 + 9^2 = 82 +8^2 + 2^2 = 68 +6^2 + 8^2 = 100 +1^2 + 0^2 + 0^2 = 1 + +# 思路 + +这道题目看上去貌似一道数学问题,其实并不是! + +题目中说了会 **无限循环**,那么也就是说**求和的过程中,sum会重复出现,这对解题很重要!** + +正如:[关于哈希表,你该了解这些!](https://mp.weixin.qq.com/s/g8N6WmoQmsCUw3_BaWxHZA)中所说,**当我们遇到了要快速判断一个元素是否出现集合里的时候,就要考虑哈希法了。** + +所以这道题目使用哈希法,来判断这个sum是否重复出现,如果重复了就是return false, 否则一直找到sum为1为止。 + +判断sum是否重复出现就可以使用unordered_set。 + +**还有一个难点就是求和的过程,如果对取数值各个位上的单数操作不熟悉的话,做这道题也会比较艰难。** + +C++代码如下: + +```C++ +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 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; + } + } +}; +``` + + +------------------------ + +* 微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) +* B站:[代码随想录](https://space.bilibili.com/525438321) +* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) + +![](../pics/公众号.png) diff --git "a/problems/0242.\346\234\211\346\225\210\347\232\204\345\255\227\346\257\215\345\274\202\344\275\215\350\257\215.md" "b/problems/0242.\346\234\211\346\225\210\347\232\204\345\255\227\346\257\215\345\274\202\344\275\215\350\257\215.md" new file mode 100644 index 0000000000..091391fb80 --- /dev/null +++ "b/problems/0242.\346\234\211\346\225\210\347\232\204\345\255\227\346\257\215\345\274\202\344\275\215\350\257\215.md" @@ -0,0 +1,87 @@ + +

+ + + + +

+ +> 数组就是简单的哈希表,但是数组的大小可不是无限开辟的 + +# 242.有效的字母异位词 + +https://leetcode-cn.com/problems/valid-anagram/ + +给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。 + +![](https://img-blog.csdnimg.cn/202008171902298.png) + +**说明:** +你可以假设字符串只包含小写字母。 + +# 思路 + +先看暴力的解法,两层for循环,同时还要记录字符是否重复出现,很明显时间复杂度是 O(n^2)。 + +暴力的方法这里就不做介绍了,直接看一下有没有更优的方式。 + +**数组其实就是一个简单哈希表**,而且这道题目中字符串只有小写字符,那么就可以定义一个数组,来记录字符串s里字符出现的次数。 + +如果对哈希表的理论基础关于数组,set,map不了解的话可以看这篇:[关于哈希表,你该了解这些!](https://mp.weixin.qq.com/s/g8N6WmoQmsCUw3_BaWxHZA) + +需要定义一个多大的数组呢,定一个数组叫做record,大小为26 就可以了,初始化为0,因为字符a到字符z的ASCII也是26个连续的数值。 + +为了方便举例,判断一下字符串s= "aee", t = "eae"。 + +操作动画如下: + +![242.有效的字母异位词](https://tva1.sinaimg.cn/large/008eGmZEly1govxyg83bng30ds09ob29.gif) + +定义一个数组叫做record用来上记录字符串s里字符出现的次数。 + +需要把字符映射到数组也就是哈希表的索引下表上,**因为字符a到字符z的ASCII是26个连续的数值,所以字符a映射为下表0,相应的字符z映射为下表25。** + +再遍历 字符串s的时候,**只需要将 s[i] - ‘a’ 所在的元素做+1 操作即可,并不需要记住字符a的ASCII,只要求出一个相对数值就可以了。** 这样就将字符串s中字符出现的次数,统计出来了。 + +那看一下如何检查字符串t中是否出现了这些字符,同样在遍历字符串t的时候,对t中出现的字符映射哈希表索引上的数值再做-1的操作。 + +那么最后检查一下,**record数组如果有的元素不为零0,说明字符串s和t一定是谁多了字符或者谁少了字符,return false。** + +最后如果record数组所有元素都为零0,说明字符串s和t是字母异位词,return true。 + +时间复杂度为O(n),空间上因为定义是的一个常量大小的辅助数组,所以空间复杂度为O(1)。 + +看完这篇哈希表总结:[哈希表:总结篇!(每逢总结必经典)](https://mp.weixin.qq.com/s/1s91yXtarL-PkX07BfnwLg),详细就可以哈希表的各种用法非常清晰了。 + +C++ 代码如下: +```C++ +class Solution { +public: + bool isAnagram(string s, string t) { + int record[26] = {0}; + for (int i = 0; i < s.size(); i++) { + // 并不需要记住字符a的ASCII,只要求出一个相对数值就可以了 + 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) { + // record数组如果有的元素不为零0,说明字符串s和t 一定是谁多了字符或者谁少了字符。 + return false; + } + } + // record数组所有元素都为零0,说明字符串s和t是字母异位词 + return true; + } +}; +``` + +------------------------ + +* 微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) +* B站:[代码随想录](https://space.bilibili.com/525438321) +* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) + +![](../pics/公众号.png) diff --git "a/problems/0349.\344\270\244\344\270\252\346\225\260\347\273\204\347\232\204\344\272\244\351\233\206.md" "b/problems/0349.\344\270\244\344\270\252\346\225\260\347\273\204\347\232\204\344\272\244\351\233\206.md" new file mode 100644 index 0000000000..df4e5307c8 --- /dev/null +++ "b/problems/0349.\344\270\244\344\270\252\346\225\260\347\273\204\347\232\204\344\272\244\351\233\206.md" @@ -0,0 +1,78 @@ + +

+ + + + +

+ +---------------------- + + +> 如果哈希值比较少、特别分散、跨度非常大,使用数组就造成空间的极大浪费! + +# 349. 两个数组的交集 + +https://leetcode-cn.com/problems/intersection-of-two-arrays/ + +题意:给定两个数组,编写一个函数来计算它们的交集。 + +![349. 两个数组的交集](https://img-blog.csdnimg.cn/20200818193523911.png) + +**说明:** +输出结果中的每个元素一定是唯一的。 +我们可以不考虑输出结果的顺序。 + +# 思路 + +这道题目,主要要学会使用一种哈希数据结构:unordered_set,这个数据结构可以解决很多类似的问题。 + +注意题目特意说明:**输出结果中的每个元素一定是唯一的,也就是说输出的结果的去重的, 同时可以不考虑输出结果的顺序** + +这道题用暴力的解法时间复杂度是O(n^2),那来看看使用哈希法进一步优化。 + +那么用数组来做哈希表也是不错的选择,例如[242. 有效的字母异位词](https://mp.weixin.qq.com/s/vM6OszkM6L1Mx2Ralm9Dig),[0383.赎金信](https://mp.weixin.qq.com/s/sYZIR4dFBrw_lr3eJJnteQ) + +但是要注意,**使用数组来做哈希的题目,是因为题目都限制了数值的大小。** + +而这道题目没有限制数值的大小,就无法使用数组来做哈希表了。 + +**而且如果哈希值比较少、特别分散、跨度非常大,使用数组就造成空间的极大浪费。** + +此时就要使用另一种结构体了,set ,关于set,C++ 给提供了如下三种可用的数据结构: + +* std::set +* std::multiset +* std::unordered_set + +std::set和std::multiset底层实现都是红黑树,std::unordered_set的底层实现是哈希表, 使用unordered_set 读写效率是最高的,并不需要对数据进行排序,而且还不要让数据重复,所以选择unordered_set。 + +思路如图所示: + +![set哈希法](https://img-blog.csdnimg.cn/2020080918570417.png) + +C++代码如下: +```C++ +class Solution { +public: + vector intersection(vector& nums1, vector& nums2) { + unordered_set result_set; // 存放结果 + unordered_set nums_set(nums1.begin(), nums1.end()); + for (int num : nums2) { + // 发现nums2的元素 在nums_set里又出现过 + if (nums_set.find(num) != nums_set.end()) { + result_set.insert(num); + } + } + return vector(result_set.begin(), result_set.end()); + } +}; +``` + +------------------------ + +* 微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) +* B站:[代码随想录](https://space.bilibili.com/525438321) +* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) + +![](../pics/公众号.png) diff --git "a/problems/0383.\350\265\216\351\207\221\344\277\241.md" "b/problems/0383.\350\265\216\351\207\221\344\277\241.md" new file mode 100644 index 0000000000..66f272319f --- /dev/null +++ "b/problems/0383.\350\265\216\351\207\221\344\277\241.md" @@ -0,0 +1,113 @@ + +

+ + + + +

+ + +> 在哈希法中有一些场景就是为数组量身定做的。 + +# 383. 赎金信 + +https://leetcode-cn.com/problems/ransom-note/ + +给定一个赎金信 (ransom) 字符串和一个杂志(magazine)字符串,判断第一个字符串 ransom 能不能由第二个字符串 magazines 里面的字符构成。如果可以构成,返回 true ;否则返回 false。 + +(题目说明:为了不暴露赎金信字迹,要从杂志上搜索各个需要的字母,组成单词来表达意思。杂志字符串中的每个字符只能在赎金信字符串中使用一次。) + +**注意:** + +你可以假设两个字符串均只含有小写字母。 + +canConstruct("a", "b") -> false +canConstruct("aa", "ab") -> false +canConstruct("aa", "aab") -> true + +# 思路 + +这道题目和[242.有效的字母异位词](https://mp.weixin.qq.com/s/vM6OszkM6L1Mx2Ralm9Dig)很像,[242.有效的字母异位词](https://mp.weixin.qq.com/s/vM6OszkM6L1Mx2Ralm9Dig)相当于求 字符串a 和 字符串b 是否可以相互组成 ,而这道题目是求 字符串a能否组成字符串b,而不用管字符串b 能不能组成字符串a。 + +本题判断第一个字符串ransom能不能由第二个字符串magazines里面的字符构成,但是这里需要注意两点。 + +*  第一点“为了不暴露赎金信字迹,要从杂志上搜索各个需要的字母,组成单词来表达意思”  这里*说明杂志里面的字母不可重复使用。* + +* 第二点 “你可以假设两个字符串均只含有小写字母。” *说明只有小写字母*,这一点很重要 + +# 暴力解法 + +那么第一个思路其实就是暴力枚举了,两层for循环,不断去寻找,代码如下: + +```C++ +// 时间复杂度: O(n^2) +// 空间复杂度:O(1) +class Solution { +public: + bool canConstruct(string ransomNote, string magazine) { + for (int i = 0; i < magazine.length(); i++) { + for (int j = 0; j < ransomNote.length(); j++) { + // 在ransomNote中找到和magazine相同的字符 + if (magazine[i] == ransomNote[j]) { + ransomNote.erase(ransomNote.begin() + j); // ransomNote删除这个字符 + break; + } + } + } + // 如果ransomNote为空,则说明magazine的字符可以组成ransomNote + if (ransomNote.length() == 0) { + return true; + } + return false; + } +}; +``` + +这里时间复杂度是比较高的,而且里面还有一个字符串删除也就是erase的操作,也是费时的,当然这段代码也可以过这道题。 + + +# 哈希解法 + +因为题目所只有小写字母,那可以采用空间换取时间的哈希策略, 用一个长度为26的数组还记录magazine里字母出现的次数。 + +然后再用ransomNote去验证这个数组是否包含了ransomNote所需要的所有字母。 + +依然是数组在哈希法中的应用。 + +一些同学可能想,用数组干啥,都用map完事了,**其实在本题的情况下,使用map的空间消耗要比数组大一些的,因为map要维护红黑树或者哈希表,而且还要做哈希函数。 所以数组更加简单直接有效!** + +代码如下: + +```C++ +// 时间复杂度: O(n) +// 空间复杂度:O(1) +class Solution { +public: + bool canConstruct(string ransomNote, string magazine) { + int record[26] = {0}; + for (int i = 0; i < magazine.length(); i++) { + // 通过recode数据记录 magazine里各个字符出现次数 + record[magazine[i]-'a'] ++; + } + for (int j = 0; j < ransomNote.length(); j++) { + // 遍历ransomNote,在record里对应的字符个数做--操作 + record[ransomNote[j]-'a']--; + // 如果小于零说明ransomNote里出现的字符,magazine没有 + if(record[ransomNote[j]-'a'] < 0) { + return false; + } + } + return true; + } +}; +``` + + + +------------------------ + +* 微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) +* B站:[代码随想录](https://space.bilibili.com/525438321) +* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) + +![](../pics/公众号.png) diff --git "a/problems/0454.\345\233\233\346\225\260\347\233\270\345\212\240II.md" "b/problems/0454.\345\233\233\346\225\260\347\233\270\345\212\240II.md" new file mode 100644 index 0000000000..85eb1409ef --- /dev/null +++ "b/problems/0454.\345\233\233\346\225\260\347\233\270\345\212\240II.md" @@ -0,0 +1,90 @@ + +

+ + + + +

+ +> 需要哈希的地方都能找到map的身影 + +# 第454题.四数相加II + +https://leetcode-cn.com/problems/4sum-ii/ + +给定四个包含整数的数组列表 A , B , C , D ,计算有多少个元组 (i, j, k, l) ,使得 A[i] + B[j] + C[k] + D[l] = 0。 + +为了使问题简单化,所有的 A, B, C, D 具有相同的长度 N,且 0 ≤ N ≤ 500 。所有整数的范围在 -2^28 到 2^28 - 1 之间,最终结果不会超过 2^31 - 1 。 + +**例如:** + +输入: +A = [ 1, 2] +B = [-2,-1] +C = [-1, 2] +D = [ 0, 2] + +输出: +2 + +**解释:** +两个元组如下: +1. (0, 0, 0, 1) -> A[0] + B[0] + C[0] + D[1] = 1 + (-2) + (-1) + 2 = 0 +2. (1, 1, 0, 0) -> A[1] + B[1] + C[0] + D[0] = 2 + (-1) + (-1) + 0 = 0 + + +# 思路 + +本题咋眼一看好像和[0015.三数之和](https://mp.weixin.qq.com/s/r5cgZFu0tv4grBAexdcd8A),[0018.四数之和](https://mp.weixin.qq.com/s/nQrcco8AZJV1pAOVjeIU_g)差不多,其实差很多。 + +**本题是使用哈希法的经典题目,而[0015.三数之和](https://mp.weixin.qq.com/s/r5cgZFu0tv4grBAexdcd8A),[0018.四数之和](https://mp.weixin.qq.com/s/nQrcco8AZJV1pAOVjeIU_g)并不合适使用哈希法**,因为三数之和和四数之和这两道题目使用哈希法在不超时的情况下做到对结果去重是很困难的,很有多细节需要处理。 + +**而这道题目是四个独立的数组,只要找到A[i] + B[j] + C[k] + D[l] = 0就可以,不用考虑有重复的四个元素相加等于0的情况,所以相对于题目18. 四数之和,题目15.三数之和,还是简单了不少!** + +如果本题想难度升级:就是给出一个数组(而不是四个数组),在这里找出四个元素相加等于0,答案中不可以包含重复的四元组,大家可以思考一下,后续的文章我也会讲到的。 + +本题解题步骤: + +1. 首先定义 一个unordered_map,key放a和b两数之和,value 放a和b两数之和出现的次数。 +2. 遍历大A和大B数组,统计两个数组元素之和,和出现的次数,放到map中。 +3. 定义int变量count,用来统计a+b+c+d = 0 出现的次数。 +4. 在遍历大C和大D数组,找到如果 0-(c+d) 在map中出现过的话,就用count把map中key对应的value也就是出现次数统计出来。 +5. 最后返回统计值 count 就可以了 + +C++代码: + +```C++ +class Solution { +public: + int fourSumCount(vector& A, vector& B, vector& C, vector& D) { + unordered_map umap; //key:a+b的数值,value:a+b数值出现的次数 + // 遍历大A和大B数组,统计两个数组元素之和,和出现的次数,放到map中 + for (int a : A) { + for (int b : B) { + umap[a + b]++; + } + } + int count = 0; // 统计a+b+c+d = 0 出现的次数 + // 在遍历大C和大D数组,找到如果 0-(c+d) 在map中出现过的话,就把map中key对应的value也就是出现次数统计出来。 + for (int c : C) { + for (int d : D) { + if (umap.find(0 - (c + d)) != umap.end()) { + count += umap[0 - (c + d)]; + } + } + } + return count; + } +}; + +``` + + + +------------------------ + +* 微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) +* B站:[代码随想录](https://space.bilibili.com/525438321) +* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) + +![](../pics/公众号.png) diff --git "a/problems/\345\217\214\346\214\207\351\222\210\346\200\273\347\273\223.md" "b/problems/\345\217\214\346\214\207\351\222\210\346\200\273\347\273\223.md" index c98eefb3af..b4d3382fcc 100644 --- "a/problems/\345\217\214\346\214\207\351\222\210\346\200\273\347\273\223.md" +++ "b/problems/\345\217\214\346\214\207\351\222\210\346\200\273\347\273\223.md" @@ -1,14 +1,3 @@ -

- -

-

- - - - - - -

> 又是一波总结 diff --git "a/problems/\345\223\210\345\270\214\350\241\250\346\200\273\347\273\223.md" "b/problems/\345\223\210\345\270\214\350\241\250\346\200\273\347\273\223.md" index 597e28308b..3dd9bf8c25 100644 --- "a/problems/\345\223\210\345\270\214\350\241\250\346\200\273\347\273\223.md" +++ "b/problems/\345\223\210\345\270\214\350\241\250\346\200\273\347\273\223.md" @@ -1,14 +1,9 @@ -

- -

- - + + - -

> 哈希表总结篇如约而至 @@ -127,6 +122,12 @@ std::unordered_map 底层实现为哈希,std::map 和std::multimap 的底层 相信通过这个总结篇,大家可以对哈希表有一个全面的了解。 -**就酱,如果关注「代码随想录」之后收获满满,就转发给身边的同学朋友吧!** +------------------------ + +* 微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) +* B站:[代码随想录](https://space.bilibili.com/525438321) +* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) + +![](../pics/公众号.png) diff --git "a/problems/\345\223\210\345\270\214\350\241\250\347\220\206\350\256\272\345\237\272\347\241\200.md" "b/problems/\345\223\210\345\270\214\350\241\250\347\220\206\350\256\272\345\237\272\347\241\200.md" index 4c8ea1b2ee..e22b8a4dd4 100644 --- "a/problems/\345\223\210\345\270\214\350\241\250\347\220\206\350\256\272\345\237\272\347\241\200.md" +++ "b/problems/\345\223\210\345\270\214\350\241\250\347\220\206\350\256\272\345\237\272\347\241\200.md" @@ -1,13 +1,9 @@ -

- -

+

- - + + - -

# 哈希表 @@ -132,3 +128,10 @@ std::unordered_map 底层实现为哈希表,std::map 和std::multimap 的底 都看到这了,还有sei!sei没读懂单独找我! +------------------------ + +* 微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) +* B站:[代码随想录](https://space.bilibili.com/525438321) +* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) + +![](../pics/公众号.png)