diff --git a/README.md b/README.md index c006fd6b..96f7bb9c 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ -## 一些闲话: +👉 推荐 [在线阅读](http://programmercarl.com/) (Github在国内访问经常不稳定) +👉 推荐 [Gitee同步](https://gitee.com/programmercarl/leetcode-master) > 1. **介绍**:本项目是一套完整的刷题计划,旨在帮助大家少走弯路,循序渐进学算法,[关注作者](#关于作者) > 2. **PDF版本** : [「代码随想录」算法精讲 PDF 版本](https://mp.weixin.qq.com/s/RsdcQ9umo09R6cfnwXZlrQ) 。 @@ -87,6 +88,7 @@ * 编程语言 * [C++面试&C++学习指南知识点整理](https://github.com/youngyangyang04/TechCPP) + * 项目 * [基于跳表的轻量级KV存储引擎](https://github.com/youngyangyang04/Skiplist-CPP) * [Nosql数据库注入攻击系统](https://github.com/youngyangyang04/NoSQLAttack) @@ -95,6 +97,7 @@ * [看了这么多代码,谈一谈代码风格!](./problems/前序/代码风格.md) * [力扣上的代码想在本地编译运行?](./problems/前序/力扣上的代码想在本地编译运行?.md) * [什么是核心代码模式,什么又是ACM模式?](./problems/前序/什么是核心代码模式,什么又是ACM模式?.md) + * 工具 * [一站式vim配置](https://github.com/youngyangyang04/PowerVim) * [保姆级Git入门教程,万字详解](https://mp.weixin.qq.com/s/Q_O0ey4C9tryPZaZeJocbA) @@ -119,27 +122,33 @@ * [递归算法的时间与空间复杂度分析!](./problems/前序/递归算法的时间与空间复杂度分析.md) * [刷了这么多题,你了解自己代码的内存消耗么?](./problems/前序/刷了这么多题,你了解自己代码的内存消耗么?.md) -(持续更新中.....) - ## 知识星球精选 -1. [选择方向的时候,我也迷茫了](https://mp.weixin.qq.com/s/ZCzFiAHZHLqHPLJQXNm75g) -2. [刷题就用库函数了,怎么了?](https://mp.weixin.qq.com/s/6K3_OSaudnHGq2Ey8vqYfg) -3. [关于实习,大家可能有点迷茫!](https://mp.weixin.qq.com/s/xcxzi7c78kQGjvZ8hh7taA) -4. [马上秋招了,慌得很!](https://mp.weixin.qq.com/s/7q7W8Cb2-a5U5atZdOnOFA) -5. [Carl看了上百份简历,总结了这些!](https://mp.weixin.qq.com/s/sJa87MZD28piCOVMFkIbwQ) -6. [面试中遇到了发散性问题.....](https://mp.weixin.qq.com/s/SSonDxi2pjkSVwHNzZswng) -7. [英语到底重不重要!](https://mp.weixin.qq.com/s/1PRZiyF_-TVA-ipwDNjdKw) -8. [计算机专业要不要读研!](https://mp.weixin.qq.com/s/c9v1L3IjqiXtkNH7sOMAdg) -9. [秋招和提前批都越来越提前了....](https://mp.weixin.qq.com/s/SNFiRDx8CKyjhTPlys6ywQ) -10. [你的简历里「专业技能」写的够专业么?](https://mp.weixin.qq.com/s/bp6y-e5FVN28H9qc8J9zrg) -11. [对于秋招,实习生也有烦恼....](https://mp.weixin.qq.com/s/ka07IPryFnfmIjByFFcXDg) -12. [华为提前批已经开始了.....](https://mp.weixin.qq.com/s/OC35QDG8pn5OwLpCxieStw) -13. [大厂新人培养体系应该是什么样的?](https://mp.weixin.qq.com/s/WBaPCosOljB5NEkFL2GhOQ) +* [为什么都说客户端会消失](./problems/知识星球精选/客三消.md) +* [博士转计算机如何找工作](./problems/知识星球精选/博士转行计算机.md) +* [不一样的七夕](./problems/知识星球精选/不一样的七夕.md) +* [HR面注意事项](./problems/知识星球精选/HR面注意事项.md) +* [刷题攻略要刷两遍!](./problems/知识星球精选/刷题攻略要刷两遍.md) +* [秋招进行中的迷茫与焦虑......](./problems/知识星球精选/秋招进行中的迷茫与焦虑.md) +* [大厂新人培养体系应该是什么样的?](./problems/知识星球精选/大厂新人培养体系.md) +* [你的简历里「专业技能」写的够专业么?](./problems/知识星球精选/专业技能可以这么写.md) +* [Carl看了上百份简历,总结了这些!](./problems/知识星球精选/写简历的一些问题.md) +* [备战2022届秋招](./problems/知识星球精选/备战2022届秋招.md) +* [技术不太好,如果选择方向](./problems/知识星球精选/技术不好如何选择技术方向.md) +* [刷题要不要使用库函数](./problems/知识星球精选/刷力扣用不用库函数.md) +* [关于实习的几点问题](./problems/知识星球精选/关于实习大家的疑问.md) +* [面试中遇到了发散性问题,怎么办?](./problems/知识星球精选/面试中发散性问题.md) +* [英语到底重不重要!](./problems/知识星球精选/英语到底重不重要.md) +* [计算机专业要不要读研!](./problems/知识星球精选/要不要考研.md) +* [关于提前批的一些建议](./problems/知识星球精选/关于提前批的一些建议.md) +* [已经在实习的录友要如何准备秋招](./problems/知识星球精选/如何权衡实习与秋招复习.md) +* [华为提前批已经开始了](./problems/知识星球精选/提前批已经开始了.md) ## 杂谈 +* [「代码随想录」刷题网站上线](https://mp.weixin.qq.com/s/-6rd_g7LrVD1fuKBYk2tXQ)。 * [LeetCode-Master上榜了](https://mp.weixin.qq.com/s/wZRTrA9Rbvgq1yEkSw4vfQ) +* [上榜之后,都有哪些变化?](https://mp.weixin.qq.com/s/VJBV0qSBthjnbbmW-lctLA) * [大半年过去了......](https://mp.weixin.qq.com/s/lubfeistPxBLSQIe5XYg5g) * [一万录友在B站学算法!](https://mp.weixin.qq.com/s/Vzq4zkMZY7erKeu0fqGLgw) @@ -241,7 +250,7 @@ 15. [二叉树:以为使用了递归,其实还隐藏着回溯](./problems/二叉树中递归带着回溯.md) 16. [二叉树:做了这么多题目了,我的左叶子之和是多少?](./problems/0404.左叶子之和.md) 17. [二叉树:我的左下角的值是多少?](./problems/0513.找树左下角的值.md) -18. [二叉树:递归函数究竟什么时候需要返回值,什么时候不要返回值?](./problems/0112.路径总和.md) +18. [二叉树:路径总和](./problems/0112.路径总和.md) 19. [二叉树:构造二叉树登场!](./problems/0106.从中序与后序遍历序列构造二叉树.md) 20. [二叉树:构造一棵最大的二叉树](./problems/0654.最大二叉树.md) 21. [本周小结!(二叉树系列三)](./problems/周总结/20201010二叉树周末总结.md) @@ -409,6 +418,7 @@ 2. [单调栈:下一个更大元素I](./problems/0496.下一个更大元素I.md) 3. [单调栈:下一个更大元素II](./problems/0503.下一个更大元素II.md) 4. [单调栈:接雨水](./problems/0042.接雨水.md) +5. [单调栈:柱状图中最大的矩形](./problems/0084.柱状图中最大的矩形.md) (持续更新中....) @@ -451,7 +461,6 @@ * [24.两两交换链表中的节点](./problems/0024.两两交换链表中的节点.md) * [234.回文链表](./problems/0234.回文链表.md) * [143.重排链表](./problems/0143.重排链表.md)【数组】【双向队列】【直接操作链表】 -* [234.回文链表](./problems/0234.回文链表.md) * [141.环形链表](./problems/0141.环形链表.md) ## 哈希表 @@ -467,8 +476,14 @@ * [100.相同的树](./problems/0100.相同的树.md) 同101.对称二叉树 一个思路 * [116.填充每个节点的下一个右侧节点指针](./problems/0116.填充每个节点的下一个右侧节点指针.md) +## 回溯算法 + +* [52.N皇后II](./problems/0052.N皇后II.md) + + ## 贪心 * [649.Dota2参议院](./problems/0649.Dota2参议院.md) 有难度 +* [1221.分割平衡字符](./problems/1221.分割平衡字符串.md) 简单贪心 ## 动态规划 * [5.最长回文子串](./problems/0005.最长回文子串.md) 和[647.回文子串](https://mp.weixin.qq.com/s/2WetyP6IYQ6VotegepVpEw) 差不多是一样的 @@ -478,6 +493,7 @@ ## 图论 * [463.岛屿的周长](./problems/0463.岛屿的周长.md) (模拟) * [841.钥匙和房间](./problems/0841.钥匙和房间.md) 【有向图】dfs,bfs都可以 +* [127.单词接龙](./problems/0127.单词接龙.md) 广搜 ## 并查集 * [684.冗余连接](./problems/0684.冗余连接.md) 【并查集基础题目】 diff --git a/problems/0001.两数之和.md b/problems/0001.两数之和.md index 5be94996..a6381eff 100644 --- a/problems/0001.两数之和.md +++ b/problems/0001.两数之和.md @@ -9,7 +9,7 @@ ## 1. 两数之和 -https://leetcode-cn.com/problems/two-sum/ +[力扣题目链接](https://leetcode-cn.com/problems/two-sum/) 给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。 @@ -29,10 +29,10 @@ https://leetcode-cn.com/problems/two-sum/ 很明显暴力的解法是两层for循环查找,时间复杂度是O(n^2)。 建议大家做这道题目之前,先做一下这两道 -* [242. 有效的字母异位词](https://mp.weixin.qq.com/s/ffS8jaVFNUWyfn_8T31IdA) -* [349. 两个数组的交集](https://mp.weixin.qq.com/s/aMSA5zrp3jJcLjuSB0Es2Q) +* [242. 有效的字母异位词](https://www.programmercarl.com/0242.有效的字母异位词.html) +* [349. 两个数组的交集](https://www.programmercarl.com/0349.两个数组的交集.html) -[242. 有效的字母异位词](https://mp.weixin.qq.com/s/ffS8jaVFNUWyfn_8T31IdA) 这道题目是用数组作为哈希表来解决哈希问题,[349. 两个数组的交集](https://mp.weixin.qq.com/s/aMSA5zrp3jJcLjuSB0Es2Q)这道题目是通过set作为哈希表来解决哈希问题。 +[242. 有效的字母异位词](https://www.programmercarl.com/0242.有效的字母异位词.html) 这道题目是用数组作为哈希表来解决哈希问题,[349. 两个数组的交集](https://www.programmercarl.com/0349.两个数组的交集.html)这道题目是通过set作为哈希表来解决哈希问题。 本题呢,则要使用map,那么来看一下使用数组和set来做哈希法的局限。 @@ -51,7 +51,7 @@ C++中map,有三种类型: std::unordered_map 底层实现为哈希表,std::map 和std::multimap 的底层实现是红黑树。 -同理,std::map 和std::multimap 的key也是有序的(这个问题也经常作为面试题,考察对语言容器底层的理解)。 更多哈希表的理论知识请看[关于哈希表,你该了解这些!](https://mp.weixin.qq.com/s/RSUANESA_tkhKhYe3ZR8Jg)。 +同理,std::map 和std::multimap 的key也是有序的(这个问题也经常作为面试题,考察对语言容器底层的理解)。 更多哈希表的理论知识请看[关于哈希表,你该了解这些!](https://www.programmercarl.com/哈希表理论基础.html)。 **这道题目中并不需要key有序,选择std::unordered_map 效率更高!** @@ -62,7 +62,7 @@ std::unordered_map 底层实现为哈希表,std::map 和std::multimap 的底 C++代码: -```C++ +```CPP class Solution { public: vector twoSum(vector& nums, int target) { @@ -110,13 +110,14 @@ Python: ```python class Solution: def twoSum(self, nums: List[int], target: int) -> List[int]: - hashmap={} - for ind,num in enumerate(nums): - hashmap[num] = ind - for i,num in enumerate(nums): - j = hashmap.get(target - num) - if j is not None and i!=j: - return [i,j] + records = dict() + + # 用枚举更方便,就不需要通过索引再去取当前位置的值 + for idx, val in enumerate(nums): + if target - val not in records: + records[val] = idx + else: + return [records[target - val], idx] # 如果存在就返回字典记录索引和当前索引 ``` @@ -205,9 +206,51 @@ function twoSum(array $nums, int $target): array } ``` +Swift: +```swift +func twoSum(_ nums: [Int], _ target: Int) -> [Int] { + var res = [Int]() + var dict = [Int : Int]() + for i in 0 ..< nums.count { + let other = target - nums[i] + if dict.keys.contains(other) { + res.append(i) + res.append(dict[other]!) + return res + } + dict[nums[i]] = i + } + return res +} +``` + +PHP: +```php +class Solution { + /** + * @param Integer[] $nums + * @param Integer $target + * @return Integer[] + */ + function twoSum($nums, $target) { + if (count($nums) == 0) { + return []; + } + $table = []; + for ($i = 0; $i < count($nums); $i++) { + $temp = $target - $nums[$i]; + if (isset($table[$temp])) { + return [$table[$temp], $i]; + } + $table[$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) -
+
diff --git a/problems/0005.最长回文子串.md b/problems/0005.最长回文子串.md index 0063b358..c78b827c 100644 --- a/problems/0005.最长回文子串.md +++ b/problems/0005.最长回文子串.md @@ -10,7 +10,7 @@ # 5.最长回文子串 -题目链接:https://leetcode-cn.com/problems/longest-palindromic-substring/ +[力扣题目链接](https://leetcode-cn.com/problems/longest-palindromic-substring/) 给你一个字符串 s,找到 s 中最长的回文子串。 @@ -30,11 +30,11 @@ 示例 4: * 输入:s = "ac" * 输出:"a" -  + # 思路 -本题和[647.回文子串](https://mp.weixin.qq.com/s/2WetyP6IYQ6VotegepVpEw) 差不多是一样的,但647.回文子串更基本一点,建议可以先做647.回文子串 +本题和[647.回文子串](https://programmercarl.com/0647.回文子串.html) 差不多是一样的,但647.回文子串更基本一点,建议可以先做647.回文子串 ## 暴力解法 @@ -67,7 +67,7 @@ 以上三种情况分析完了,那么递归公式如下: -```C++ +```CPP if (s[i] == s[j]) { if (j - i <= 1) { // 情况一 和 情况二 dp[i][j] = true; @@ -81,7 +81,7 @@ if (s[i] == s[j]) { 在得到[i,j]区间是否是回文子串的时候,直接保存最长回文子串的左边界和右边界,代码如下: -```C++ +```CPP if (s[i] == s[j]) { if (j - i <= 1) { // 情况一 和 情况二 dp[i][j] = true; @@ -120,7 +120,7 @@ dp[i + 1][j - 1] 在 dp[i][j]的左下角,如图: 代码如下: -```C++ +```CPP for (int i = s.size() - 1; i >= 0; i--) { // 注意遍历顺序 for (int j = i; j < s.size(); j++) { if (s[i] == s[j]) { @@ -150,7 +150,7 @@ for (int i = s.size() - 1; i >= 0; i--) { // 注意遍历顺序 以上分析完毕,C++代码如下: -```C++ +```CPP class Solution { public: string longestPalindrome(string s) { @@ -181,7 +181,7 @@ public: ``` 以上代码是为了凸显情况一二三,当然是可以简洁一下的,如下: -```C++ +```CPP class Solution { public: string longestPalindrome(string s) { @@ -226,7 +226,7 @@ public: **这两种情况可以放在一起计算,但分别计算思路更清晰,我倾向于分别计算**,代码如下: -```C++ +```CPP class Solution { public: int left = 0; @@ -270,6 +270,23 @@ public: ## Python ```python +class Solution: + def longestPalindrome(self, s: str) -> str: + dp = [[False] * len(s) for _ in range(len(s))] + maxlenth = 0 + left = 0 + right = 0 + for i in range(len(s) - 1, -1, -1): + for j in range(i, len(s)): + if s[j] == s[i]: + if j - i <= 1 or dp[i + 1][j - 1]: + dp[i][j] = True + if dp[i][j] and j - i + 1 > maxlenth: + maxlenth = j - i + 1 + left = i + right = j + return s[left:right + 1] + ``` ## Go @@ -286,6 +303,6 @@ public: * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0015.三数之和.md b/problems/0015.三数之和.md index 36abb58c..6b5311ae 100644 --- a/problems/0015.三数之和.md +++ b/problems/0015.三数之和.md @@ -8,11 +8,11 @@ -> 用哈希表解决了[两数之和](https://mp.weixin.qq.com/s/uVAtjOHSeqymV8FeQbliJQ),那么三数之和呢? +> 用哈希表解决了[两数之和](https://programmercarl.com/0001.两数之和.html),那么三数之和呢? # 第15题. 三数之和 -https://leetcode-cn.com/problems/3sum/ +[力扣题目链接](https://leetcode-cn.com/problems/3sum/) 给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。 @@ -37,7 +37,7 @@ https://leetcode-cn.com/problems/3sum/ 两层for循环就可以确定 a 和b 的数值了,可以使用哈希法来确定 0-(a+b) 是否在 数组里出现过,其实这个思路是正确的,但是我们有一个非常棘手的问题,就是题目中说的不可以包含重复的三元组。 -把符合条件的三元组放进vector中,然后在去去重,这样是非常费时的,很容易超时,也是这道题目通过率如此之低的根源所在。 +把符合条件的三元组放进vector中,然后再去重,这样是非常费时的,很容易超时,也是这道题目通过率如此之低的根源所在。 去重的过程不好处理,有很多小细节,如果在面试中很难想到位。 @@ -46,7 +46,7 @@ https://leetcode-cn.com/problems/3sum/ 大家可以尝试使用哈希法写一写,就知道其困难的程度了。 哈希法C++代码: -```C++ +```CPP class Solution { public: vector> threeSum(vector& nums) { @@ -95,11 +95,11 @@ public: ![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 在数组结尾的位置上。 +拿这个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下表就应该向左移动,这样才能让三数之和小一些。 +接下来如何移动left 和right呢, 如果nums[i] + nums[left] + nums[right] > 0 就说明 此时三数之和大了,因为数组是排序后了,所以right下标就应该向左移动,这样才能让三数之和小一些。 如果 nums[i] + nums[left] + nums[right] < 0 说明 此时 三数之和小了,left 就向右移动,才能让三数之和大一些,直到left与right相遇为止。 @@ -107,7 +107,7 @@ public: C++代码代码如下: -```C++ +```CPP class Solution { public: vector> threeSum(vector& nums) { @@ -163,13 +163,13 @@ public: # 思考题 -既然三数之和可以使用双指针法,我们之前讲过的[1.两数之和](https://mp.weixin.qq.com/s/vaMsLnH-f7_9nEK4Cuu3KQ),可不可以使用双指针法呢? +既然三数之和可以使用双指针法,我们之前讲过的[1.两数之和](https://programmercarl.com/0001.两数之和.html),可不可以使用双指针法呢? 如果不能,题意如何更改就可以使用双指针法呢? **大家留言说出自己的想法吧!** -两数之和 就不能使用双指针法,因为[1.两数之和](https://mp.weixin.qq.com/s/vaMsLnH-f7_9nEK4Cuu3KQ)要求返回的是索引下表, 而双指针法一定要排序,一旦排序之后原数组的索引就被改变了。 +两数之和 就不能使用双指针法,因为[1.两数之和](https://programmercarl.com/0001.两数之和.html)要求返回的是索引下标, 而双指针法一定要排序,一旦排序之后原数组的索引就被改变了。 -如果[1.两数之和](https://mp.weixin.qq.com/s/vaMsLnH-f7_9nEK4Cuu3KQ)要求返回的是数值的话,就可以使用双指针法了。 +如果[1.两数之和](https://programmercarl.com/0001.两数之和.html)要求返回的是数值的话,就可以使用双指针法了。 @@ -393,10 +393,90 @@ function threeSum(array $nums): array } ``` +PHP: +```php +class Solution { + /** + * @param Integer[] $nums + * @return Integer[][] + */ + function threeSum($nums) { + $res = []; + sort($nums); + for ($i = 0; $i < count($nums); $i++) { + if ($nums[$i] > 0) { + return $res; + } + if ($i > 0 && $nums[$i] == $nums[$i - 1]) { + continue; + } + $left = $i + 1; + $right = count($nums) - 1; + while ($left < $right) { + $sum = $nums[$i] + $nums[$left] + $nums[$right]; + if ($sum < 0) { + $left++; + } + else if ($sum > 0) { + $right--; + } + else { + $res[] = [$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 $res; + } +} +``` +Swift: +```swift +// 双指针法 +func threeSum(_ nums: [Int]) -> [[Int]] { + var res = [[Int]]() + var sorted = nums + sorted.sort() + for i in 0 ..< sorted.count { + if sorted[i] > 0 { + return res + } + if i > 0 && sorted[i] == sorted[i - 1] { + continue + } + var left = i + 1 + var right = sorted.count - 1 + while left < right { + let sum = sorted[i] + sorted[left] + sorted[right] + if sum < 0 { + left += 1 + } else if sum > 0 { + right -= 1 + } else { + res.append([sorted[i], sorted[left], sorted[right]]) + + while left < right && sorted[left] == sorted[left + 1] { + left += 1 + } + while left < right && sorted[right] == sorted[right - 1] { + right -= 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) -
+
diff --git a/problems/0017.电话号码的字母组合.md b/problems/0017.电话号码的字母组合.md index 1562052c..1221115c 100644 --- a/problems/0017.电话号码的字母组合.md +++ b/problems/0017.电话号码的字母组合.md @@ -9,7 +9,7 @@ # 17.电话号码的字母组合 -题目链接:https://leetcode-cn.com/problems/letter-combinations-of-a-phone-number/ +[力扣题目链接](https://leetcode-cn.com/problems/letter-combinations-of-a-phone-number/) 给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。 @@ -29,7 +29,7 @@ 如果输入"233"呢,那么就三层for循环,如果"2333"呢,就四层for循环....... -大家应该感觉出和[回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)遇到的一样的问题,就是这for循环的层数如何写出来,此时又是回溯法登场的时候了。 +大家应该感觉出和[回溯算法:求组合问题!](https://programmercarl.com/0077.组合.html)遇到的一样的问题,就是这for循环的层数如何写出来,此时又是回溯法登场的时候了。 理解本题后,要解决如下三个问题: @@ -58,7 +58,7 @@ const string letterMap[10] = { ## 回溯法来解决n个for循环的问题 -对于回溯法还不了解的同学看这篇:[关于回溯算法,你该了解这些!](https://mp.weixin.qq.com/s/gjSgJbNbd1eAA5WkA-HeWw) +对于回溯法还不了解的同学看这篇:[关于回溯算法,你该了解这些!](https://programmercarl.com/回溯算法理论基础.html) 例如:输入:"23",抽象为树形结构,如图所示: @@ -75,7 +75,7 @@ const string letterMap[10] = { 再来看参数,参数指定是有题目中给的string digits,然后还要有一个参数就是int型的index。 -注意这个index可不是 [回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)和[回溯算法:求组合总和!](https://mp.weixin.qq.com/s/HX7WW6ixbFZJASkRnCTC3w)中的startIndex了。 +注意这个index可不是 [回溯算法:求组合问题!](https://programmercarl.com/0077.组合.html)和[回溯算法:求组合总和!](https://programmercarl.com/0216.组合总和III.html)中的startIndex了。 这个index是记录遍历第几个数字了,就是用来遍历digits的(题目中给出数字字符串),同时index也表示树的深度。 @@ -120,9 +120,9 @@ for (int i = 0; i < letters.size(); i++) { } ``` -**注意这里for循环,可不像是在[回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)和[回溯算法:求组合总和!](https://mp.weixin.qq.com/s/HX7WW6ixbFZJASkRnCTC3w)中从startIndex开始遍历的**。 +**注意这里for循环,可不像是在[回溯算法:求组合问题!](https://programmercarl.com/0077.组合.html)和[回溯算法:求组合总和!](https://programmercarl.com/0216.组合总和III.html)中从startIndex开始遍历的**。 -**因为本题每一个数字代表的是不同集合,也就是求不同集合之间的组合,而[77. 组合](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)和[216.组合总和III](https://mp.weixin.qq.com/s/HX7WW6ixbFZJASkRnCTC3w)都是是求同一个集合中的组合!** +**因为本题每一个数字代表的是不同集合,也就是求不同集合之间的组合,而[77. 组合](https://programmercarl.com/0077.组合.html)和[216.组合总和III](https://programmercarl.com/0216.组合总和III.html)都是是求同一个集合中的组合!** 注意:输入1 * #按键等等异常情况 @@ -134,7 +134,7 @@ for (int i = 0; i < letters.size(); i++) { ## C++代码 -关键地方都讲完了,按照[关于回溯算法,你该了解这些!](https://mp.weixin.qq.com/s/gjSgJbNbd1eAA5WkA-HeWw)中的回溯法模板,不难写出如下C++代码: +关键地方都讲完了,按照[关于回溯算法,你该了解这些!](https://programmercarl.com/回溯算法理论基础.html)中的回溯法模板,不难写出如下C++代码: ```c++ @@ -224,13 +224,13 @@ public: }; ``` -我不建议把回溯藏在递归的参数里这种写法,很不直观,我在[二叉树:以为使用了递归,其实还隐藏着回溯](https://mp.weixin.qq.com/s/ivLkHzWdhjQQD1rQWe6zWA)这篇文章中也深度分析了,回溯隐藏在了哪里。 +我不建议把回溯藏在递归的参数里这种写法,很不直观,我在[二叉树:以为使用了递归,其实还隐藏着回溯](https://programmercarl.com/二叉树中递归带着回溯.html)这篇文章中也深度分析了,回溯隐藏在了哪里。 所以大家可以按照版本一来写就可以了。 # 总结 -本篇将题目的三个要点一一列出,并重点强调了和前面讲解过的[77. 组合](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)和[216.组合总和III](https://mp.weixin.qq.com/s/HX7WW6ixbFZJASkRnCTC3w)的区别,本题是多个集合求组合,所以在回溯的搜索过程中,都有一些细节需要注意的。 +本篇将题目的三个要点一一列出,并重点强调了和前面讲解过的[77. 组合](https://programmercarl.com/0077.组合.html)和[216.组合总和III](https://programmercarl.com/0216.组合总和III.html)的区别,本题是多个集合求组合,所以在回溯的搜索过程中,都有一些细节需要注意的。 其实本题不算难,但也处处是细节,大家还要自己亲自动手写一写。 @@ -322,20 +322,20 @@ python3: ```py class Solution: def letterCombinations(self, digits: str) -> List[str]: - self.s = "" res = [] + s = "" letterMap = ["","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"] - if len(digits) == 0: return res - def backtrack(digits,index): + if not len(digits): return res + def backtrack(digits,index, s): if index == len(digits): - return res.append(self.s) + return res.append(s) digit = int(digits[index]) #将index指向的数字转为int letters = letterMap[digit] #取数字对应的字符集 for i in range(len(letters)): - self.s += letters[i] - backtrack(digits,index + 1) #递归,注意index+1,一下层要处理下一个数字 - self.s = self.s[:-1] #回溯 - backtrack(digits,0) + s += letters[i] + backtrack(digits, index+1, s) #递归,注意index+1,一下层要处理下一个数字 + s = s[:-1] #回溯 + backtrack(digits, 0, s) return res ``` @@ -410,10 +410,70 @@ var letterCombinations = function(digits) { }; ``` +C: +```c +char* path; +int pathTop; +char** result; +int resultTop; +char* letterMap[10] = {"", //0 + "", //1 + "abc", //2 + "def", //3 + "ghi", //4 + "jkl", //5 + "mno", //6 + "pqrs", //7 + "tuv", //8 + "wxyz", //9 +}; +void backTracking(char* digits, int index) { + //若当前下标等于digits数组长度 + if(index == strlen(digits)) { + //复制digits数组,因为最后要多存储一个0,所以数组长度要+1 + char* tempString = (char*)malloc(sizeof(char) * strlen(digits) + 1); + int j; + for(j = 0; j < strlen(digits); j++) { + tempString[j] = path[j]; + } + //char数组最后要以0结尾 + tempString[strlen(digits)] = 0; + result[resultTop++] = tempString; + return ; + } + //将字符数字转换为真的数字 + int digit = digits[index] - '0'; + //找到letterMap中对应的字符串 + char* letters = letterMap[digit]; + int i; + for(i = 0; i < strlen(letters); i++) { + path[pathTop++] = letters[i]; + //递归,处理下一层数字 + backTracking(digits, index+1); + pathTop--; + } +} + +char ** letterCombinations(char * digits, int* returnSize){ + //初始化path和result + path = (char*)malloc(sizeof(char) * strlen(digits)); + result = (char**)malloc(sizeof(char*) * 300); + + *returnSize = 0; + //若digits数组中元素个数为0,返回空集 + if(strlen(digits) == 0) + return result; + pathTop = resultTop = 0; + backTracking(digits, 0); + *returnSize = resultTop; + + return result; +} +``` ----------------------- * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0018.四数之和.md b/problems/0018.四数之和.md index 0caf12be..9891f0fe 100644 --- a/problems/0018.四数之和.md +++ b/problems/0018.四数之和.md @@ -12,7 +12,7 @@ # 第18题. 四数之和 -https://leetcode-cn.com/problems/4sum/ +[力扣题目链接](https://leetcode-cn.com/problems/4sum/) 题意:给定一个包含 n 个整数的数组 nums 和一个目标值 target,判断 nums 中是否存在四个元素 a,b,c 和 d ,使得 a + b + c + d 的值与 target 相等?找出所有满足条件且不重复的四元组。 @@ -31,43 +31,43 @@ https://leetcode-cn.com/problems/4sum/ # 思路 -四数之和,和[15.三数之和](https://mp.weixin.qq.com/s/QfTNEByq1YlNSXRKEumwHg)是一个思路,都是使用双指针法, 基本解法就是在[15.三数之和](https://mp.weixin.qq.com/s/QfTNEByq1YlNSXRKEumwHg) 的基础上再套一层for循环。 +四数之和,和[15.三数之和](https://programmercarl.com/0015.三数之和.html)是一个思路,都是使用双指针法, 基本解法就是在[15.三数之和](https://programmercarl.com/0015.三数之和.html) 的基础上再套一层for循环。 但是有一些细节需要注意,例如: 不要判断`nums[k] > target` 就返回了,三数之和 可以通过 `nums[i] > 0` 就返回了,因为 0 已经是确定的数了,四数之和这道题目 target是任意值。(大家亲自写代码就能感受出来) -[15.三数之和](https://mp.weixin.qq.com/s/QfTNEByq1YlNSXRKEumwHg)的双指针解法是一层for循环num[i]为确定值,然后循环内有left和right下表作为双指针,找到nums[i] + nums[left] + nums[right] == 0。 +[15.三数之和](https://programmercarl.com/0015.三数之和.html)的双指针解法是一层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) 。 那么一样的道理,五数之和、六数之和等等都采用这种解法。 -对于[15.三数之和](https://mp.weixin.qq.com/s/QfTNEByq1YlNSXRKEumwHg)双指针法就是将原本暴力O(n^3)的解法,降为O(n^2)的解法,四数之和的双指针解法就是将原本暴力O(n^4)的解法,降为O(n^3)的解法。 +对于[15.三数之和](https://programmercarl.com/0015.三数之和.html)双指针法就是将原本暴力O(n^3)的解法,降为O(n^2)的解法,四数之和的双指针解法就是将原本暴力O(n^4)的解法,降为O(n^3)的解法。 -之前我们讲过哈希表的经典题目:[454.四数相加II](https://mp.weixin.qq.com/s/12g_w6RzHuEpFts1pT6BWw),相对于本题简单很多,因为本题是要求在一个集合中找出四个数相加等于target,同时四元组不能重复。 +之前我们讲过哈希表的经典题目:[454.四数相加II](https://programmercarl.com/0454.四数相加II.html),相对于本题简单很多,因为本题是要求在一个集合中找出四个数相加等于target,同时四元组不能重复。 -而[454.四数相加II](https://mp.weixin.qq.com/s/12g_w6RzHuEpFts1pT6BWw)是四个独立的数组,只要找到A[i] + B[j] + C[k] + D[l] = 0就可以,不用考虑有重复的四个元素相加等于0的情况,所以相对于本题还是简单了不少! +而[454.四数相加II](https://programmercarl.com/0454.四数相加II.html)是四个独立的数组,只要找到A[i] + B[j] + C[k] + D[l] = 0就可以,不用考虑有重复的四个元素相加等于0的情况,所以相对于本题还是简单了不少! 我们来回顾一下,几道题目使用了双指针法。 双指针法将时间复杂度O(n^2)的解法优化为 O(n)的解法。也就是降一个数量级,题目如下: -* [27.移除元素](https://mp.weixin.qq.com/s/RMkulE4NIb6XsSX83ra-Ww) -* [15.三数之和](https://mp.weixin.qq.com/s/QfTNEByq1YlNSXRKEumwHg) -* [18.四数之和](https://mp.weixin.qq.com/s/nQrcco8AZJV1pAOVjeIU_g) +* [27.移除元素](https://programmercarl.com/0027.移除元素.html) +* [15.三数之和](https://programmercarl.com/0015.三数之和.html) +* [18.四数之和](https://programmercarl.com/0018.四数之和.html) 操作链表: -* [206.反转链表](https://mp.weixin.qq.com/s/ckEvIVGcNLfrz6OLOMoT0A) -* [19.删除链表的倒数第N个节点](https://mp.weixin.qq.com/s/gxu65X1343xW_sBrkTz0Eg) -* [面试题 02.07. 链表相交](https://mp.weixin.qq.com/s/BhfFfaGvt9Zs7UmH4YehZw) -* [142题.环形链表II](https://mp.weixin.qq.com/s/gt_VH3hQTqNxyWcl1ECSbQ) +* [206.反转链表](https://programmercarl.com/0206.翻转链表.html) +* [19.删除链表的倒数第N个节点](https://programmercarl.com/0019.删除链表的倒数第N个节点.html) +* [面试题 02.07. 链表相交](https://programmercarl.com/面试题02.07.链表相交.html) +* [142题.环形链表II](https://programmercarl.com/0142.环形链表II.html) 双指针法在字符串题目中还有很多应用,后面还会介绍到。 C++代码 -```C++ +```CPP class Solution { public: vector> fourSum(vector& nums, int target) { @@ -167,7 +167,33 @@ class Solution { Python: ```python +# 双指针法 +class Solution: + def fourSum(self, nums: List[int], target: int) -> List[List[int]]: + + nums.sort() + n = len(nums) + res = [] + for i in range(n): + if i > 0 and nums[i] == nums[i - 1]: continue + for k in range(i+1, n): + if k > i + 1 and nums[k] == nums[k-1]: continue + p = k + 1 + q = n - 1 + while p < q: + if nums[i] + nums[k] + nums[p] + nums[q] > target: q -= 1 + elif nums[i] + nums[k] + nums[p] + nums[q] < target: p += 1 + else: + res.append([nums[i], nums[k], nums[p], nums[q]]) + while p < q and nums[p] == nums[p + 1]: p += 1 + while p < q and nums[q] == nums[q - 1]: q -= 1 + p += 1 + q -= 1 + return res +``` +```python +# 哈希表法 class Solution(object): def fourSum(self, nums, target): """ @@ -201,6 +227,54 @@ class Solution(object): ``` Go: +```go +func fourSum(nums []int, target int) [][]int { + if len(nums) < 4 { + return nil + } + sort.Ints(nums) + var res [][]int + for i := 0; i < len(nums)-3; i++ { + n1 := nums[i] + // if n1 > target { // 不能这样写,因为可能是负数 + // break + // } + if i > 0 && n1 == nums[i-1] { + continue + } + for j := i + 1; j < len(nums)-2; j++ { + n2 := nums[j] + if j > i+1 && n2 == nums[j-1] { + continue + } + l := j + 1 + r := len(nums) - 1 + for l < r { + n3 := nums[l] + n4 := nums[r] + sum := n1 + n2 + n3 + n4 + if sum < target { + l++ + } else if sum > target { + r-- + } else { + res = append(res, []int{n1, n2, n3, n4}) + for l < r && n3 == nums[l+1] { // 去重 + l++ + } + for l < r && n4 == nums[r-1] { // 去重 + r-- + } + // 找到答案时,双指针同时靠近 + r-- + l++ + } + } + } + } + return res +} +``` javaScript: @@ -236,9 +310,100 @@ var fourSum = function(nums, target) { }; ``` +PHP: +```php +class Solution { + /** + * @param Integer[] $nums + * @param Integer $target + * @return Integer[][] + */ + function fourSum($nums, $target) { + $res = []; + sort($nums); + for ($i = 0; $i < count($nums); $i++) { + if ($i > 0 && $nums[$i] == $nums[$i - 1]) { + continue; + } + for ($j = $i + 1; $j < count($nums); $j++) { + if ($j > $i + 1 && $nums[$j] == $nums[$j - 1]) { + continue; + } + $left = $j + 1; + $right = count($nums) - 1; + while ($left < $right) { + $sum = $nums[$i] + $nums[$j] + $nums[$left] + $nums[$right]; + if ($sum < $target) { + $left++; + } + else if ($sum > $target) { + $right--; + } + else { + $res[] = [$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 $res; + } +} +``` + +Swift: +```swift +func fourSum(_ nums: [Int], _ target: Int) -> [[Int]] { + var res = [[Int]]() + var sorted = nums + sorted.sort() + for k in 0 ..< sorted.count { + // 这种剪枝不行,target可能是负数 +// if sorted[k] > target { +// return res +// } + // 去重 + if k > 0 && sorted[k] == sorted[k - 1] { + continue + } + + let target2 = target - sorted[k] + for i in (k + 1) ..< sorted.count { + if i > (k + 1) && sorted[i] == sorted[i - 1] { + continue + } + var left = i + 1 + var right = sorted.count - 1 + while left < right { + let sum = sorted[i] + sorted[left] + sorted[right] + if sum < target2 { + left += 1 + } else if sum > target2 { + right -= 1 + } else { + res.append([sorted[k], sorted[i], sorted[left], sorted[right]]) + while left < right && sorted[left] == sorted[left + 1] { + left += 1 + } + while left < right && sorted[right] == sorted[right - 1] { + right -= 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) -
+
diff --git a/problems/0019.删除链表的倒数第N个节点.md b/problems/0019.删除链表的倒数第N个节点.md index 52735794..85e3eb48 100644 --- a/problems/0019.删除链表的倒数第N个节点.md +++ b/problems/0019.删除链表的倒数第N个节点.md @@ -11,7 +11,7 @@ ## 19.删除链表的倒数第N个节点 -题目链接:https://leetcode-cn.com/problems/remove-nth-node-from-end-of-list/ +[力扣题目链接](https://leetcode-cn.com/problems/remove-nth-node-from-end-of-list/) 给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。 @@ -41,7 +41,7 @@ 分为如下几步: -* 首先这里我推荐大家使用虚拟头结点,这样方面处理删除实际头结点的逻辑,如果虚拟头结点不清楚,可以看这篇: [链表:听说用虚拟头节点会方便很多?](https://mp.weixin.qq.com/s/L5aanfALdLEwVWGvyXPDqA) +* 首先这里我推荐大家使用虚拟头结点,这样方面处理删除实际头结点的逻辑,如果虚拟头结点不清楚,可以看这篇: [链表:听说用虚拟头节点会方便很多?](https://programmercarl.com/0203.移除链表元素.html) * 定义fast指针和slow指针,初始值为虚拟头结点,如图: @@ -58,7 +58,7 @@ 此时不难写出如下C++代码: -```C++ +```CPP class Solution { public: ListNode* removeNthFromEnd(ListNode* head, int n) { @@ -184,9 +184,53 @@ var removeNthFromEnd = function(head, n) { return ret.next; }; ``` +Kotlin: +```Kotlin +fun removeNthFromEnd(head: ListNode?, n: Int): ListNode? { + val pre = ListNode(0).apply { + this.next = head + } + var fastNode: ListNode? = pre + var slowNode: ListNode? = pre + for (i in 0..n) { + fastNode = fastNode?.next + } + while (fastNode != null) { + slowNode = slowNode?.next + fastNode = fastNode.next + } + slowNode?.next = slowNode?.next?.next + return pre.next +} +``` + +Swift: +```swift +func removeNthFromEnd(_ head: ListNode?, _ n: Int) -> ListNode? { + if head == nil { + return nil + } + if n == 0 { + return head + } + let dummyHead = ListNode(-1, head) + var fast: ListNode? = dummyHead + var slow: ListNode? = dummyHead + // fast 前移 n + for _ in 0 ..< n { + fast = fast?.next + } + while fast?.next != nil { + fast = fast?.next + slow = slow?.next + } + slow?.next = slow?.next?.next + return dummyHead.next +} +``` ----------------------- * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0020.有效的括号.md b/problems/0020.有效的括号.md index 178bc6f8..5b02f9ee 100644 --- a/problems/0020.有效的括号.md +++ b/problems/0020.有效的括号.md @@ -12,7 +12,7 @@ # 20. 有效的括号 -https://leetcode-cn.com/problems/valid-parentheses/ +[力扣题目链接](https://leetcode-cn.com/problems/valid-parentheses/) 给定一个只包括 '(',')','{','}','[',']' 的字符串,判断字符串是否有效。 @@ -108,7 +108,7 @@ cd a/b/c/../../ 实现C++代码如下: -```C++ +```CPP class Solution { public: bool isValid(string s) { @@ -162,18 +162,44 @@ class Solution { Python: ```python3 +# 方法一,仅使用栈,更省空间 class Solution: def isValid(self, s: str) -> bool: - stack = [] # 保存还未匹配的左括号 - mapping = {")": "(", "]": "[", "}": "{"} - for i in s: - if i in "([{": # 当前是左括号,则入栈 - stack.append(i) - elif stack and stack[-1] == mapping[i]: # 当前是配对的右括号则出栈 - stack.pop() - else: # 不是匹配的右括号或者没有左括号与之匹配,则返回false + stack = [] + + for item in s: + if item == '(': + stack.append(')') + elif item == '[': + stack.append(']') + elif item == '{': + stack.append('}') + elif not stack or stack[-1] != item: return False - return stack == [] # 最后必须正好把左括号匹配完 + else: + stack.pop() + + return True if not stack else False +``` + +```python3 +# 方法二,使用字典 +class Solution: + def isValid(self, s: str) -> bool: + stack = [] + mapping = { + '(': ')', + '[': ']', + '{': '}' + } + for item in s: + if item in mapping.keys(): + stack.append(mapping[item]) + elif not stack or stack[-1] != item: + return False + else: + stack.pop() + return True if not stack else False ``` Go: @@ -264,4 +290,4 @@ var isValid = function(s) { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0024.两两交换链表中的节点.md b/problems/0024.两两交换链表中的节点.md index 66e149e6..75a5f739 100644 --- a/problems/0024.两两交换链表中的节点.md +++ b/problems/0024.两两交换链表中的节点.md @@ -9,7 +9,7 @@ ## 24. 两两交换链表中的节点 -https://leetcode-cn.com/problems/swap-nodes-in-pairs/ +[力扣题目链接](https://leetcode-cn.com/problems/swap-nodes-in-pairs/) 给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。 @@ -24,7 +24,7 @@ https://leetcode-cn.com/problems/swap-nodes-in-pairs/ 建议使用虚拟头结点,这样会方便很多,要不然每次针对头结点(没有前一个指针指向头结点),还要单独处理。 -对虚拟头结点的操作,还不熟悉的话,可以看这篇[链表:听说用虚拟头节点会方便很多?](https://mp.weixin.qq.com/s/L5aanfALdLEwVWGvyXPDqA)。 +对虚拟头结点的操作,还不熟悉的话,可以看这篇[链表:听说用虚拟头节点会方便很多?](https://programmercarl.com/0203.移除链表元素.html)。 接下来就是交换相邻两个元素了,**此时一定要画图,不画图,操作多个指针很容易乱,而且要操作的先后顺序** @@ -43,7 +43,7 @@ https://leetcode-cn.com/problems/swap-nodes-in-pairs/ 对应的C++代码实现如下: (注释中详细和如上图中的三步做对应) -```C++ +```CPP class Solution { public: ListNode* swapPairs(ListNode* head) { @@ -86,6 +86,34 @@ public: ## 其他语言版本 +C: +``` +/** + * Definition for singly-linked list. + * struct ListNode { + * int val; + * struct ListNode *next; + * }; + */ + + +struct ListNode* swapPairs(struct ListNode* head){ + //使用双指针避免使用中间变量 + typedef struct ListNode ListNode; + ListNode *fakehead = (ListNode *)malloc(sizeof(ListNode)); + fakehead->next = head; + ListNode* right = fakehead->next; + ListNode* left = fakehead; + while(left && right && right->next ){ + left->next = right->next; + right->next = left->next->next; + left->next->next = right; + left = right; + right = left->next; + } + return fakehead->next; +} +``` Java: @@ -132,21 +160,29 @@ class Solution { Python: ```python +# Definition for singly-linked list. +# class ListNode: +# def __init__(self, val=0, next=None): +# self.val = val +# self.next = next + class Solution: def swapPairs(self, head: ListNode) -> ListNode: - dummy = ListNode(0) #设置一个虚拟头结点 - dummy.next = head - cur = dummy - while cur.next and cur.next.next: - tmp = cur.next #记录临时节点 - tmp1 = cur.next.next.next #记录临时节点 + res = ListNode(next=head) + pre = res + + # 必须有pre的下一个和下下个才能交换,否则说明已经交换结束了 + while pre.next and pre.next.next: + cur = pre.next + post = pre.next.next - cur.next = cur.next.next #步骤一 - cur.next.next = tmp #步骤二 - cur.next.next.next = tmp1 #步骤三 - - cur = cur.next.next #cur移动两位,准备下一轮交换 - return dummy.next + # pre,cur,post对应最左,中间的,最右边的节点 + cur.next = post.next + post.next = cur + pre.next = post + + pre = pre.next.next + return res.next ``` Go: @@ -200,9 +236,51 @@ var swapPairs = function (head) { }; ``` +Kotlin: + +```kotlin +fun swapPairs(head: ListNode?): ListNode? { + val dummyNode = ListNode(0).apply { + this.next = head + } + var cur: ListNode? = dummyNode + while (cur?.next != null && cur.next?.next != null) { + val temp = cur.next + val temp2 = cur.next?.next?.next + cur.next = cur.next?.next + cur.next?.next = temp + cur.next?.next?.next = temp2 + cur = cur.next?.next + } + return dummyNode.next +} +``` + +Swift: +```swift +func swapPairs(_ head: ListNode?) -> ListNode? { + if head == nil || head?.next == nil { + return head + } + let dummyHead: ListNode = ListNode(-1, head) + var current: ListNode? = dummyHead + while current?.next != nil && current?.next?.next != nil { + let temp1 = current?.next + let temp2 = current?.next?.next?.next + + current?.next = current?.next?.next + current?.next?.next = temp1 + current?.next?.next?.next = temp2 + + current = current?.next?.next + } + return dummyHead.next +} +``` + ----------------------- * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0027.移除元素.md b/problems/0027.移除元素.md index f1187db7..78f9afb7 100644 --- a/problems/0027.移除元素.md +++ b/problems/0027.移除元素.md @@ -9,7 +9,7 @@ ## 27. 移除元素 -题目地址:https://leetcode-cn.com/problems/remove-element/ +[力扣题目链接](https://leetcode-cn.com/problems/remove-element/) 给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。 @@ -34,7 +34,7 @@ **要知道数组的元素在内存地址中是连续的,不能单独删除数组中的某个元素,只能覆盖。** -数组的基础知识可以看这里[程序员算法面试中,必须掌握的数组理论知识](https://mp.weixin.qq.com/s/c2KABb-Qgg66HrGf8z-8Og)。 +数组的基础知识可以看这里[程序员算法面试中,必须掌握的数组理论知识](https://programmercarl.com/数组理论基础.html)。 ### 暴力解法 @@ -48,7 +48,7 @@ 代码如下: -```C++ +```CPP // 时间复杂度:O(n^2) // 空间复杂度:O(1) class Solution { @@ -85,7 +85,7 @@ public: 后序都会一一介绍到,本题代码如下: -```C++ +```CPP // 时间复杂度:O(n) // 空间复杂度:O(1) class Solution { @@ -106,7 +106,7 @@ public: * 时间复杂度:$O(n)$ * 空间复杂度:$O(1)$ -旧文链接:[数组:就移除个元素很难么?](https://mp.weixin.qq.com/s/wj0T-Xs88_FHJFwayElQlA) +旧文链接:[数组:就移除个元素很难么?](https://programmercarl.com/0027.移除元素.html) ## 相关题目推荐 @@ -144,15 +144,28 @@ class Solution { Python: -```python +```python3 class Solution: - def removeElement(self, nums: List[int], val: int) -> int: - i,n = 0,len(nums) - for j in range(n): - if nums[j] != val: - nums[i] = nums[j] - i += 1 - return i + """双指针法 + 时间复杂度:O(n) + 空间复杂度:O(1) + """ + + @classmethod + def removeElement(cls, nums: List[int], val: int) -> int: + fast = slow = 0 + + while fast < len(nums): + + if nums[fast] != val: + nums[slow] = nums[fast] + slow += 1 + + # 当 fast 指针遇到要删除的元素时停止赋值 + # slow 指针停止移动, fast 指针继续前进 + fast += 1 + + return slow ``` @@ -201,23 +214,80 @@ end ``` Rust: ```rust -pub fn remove_element(nums: &mut Vec, val: i32) -> &mut Vec { - let mut start: usize = 0; - while start < nums.len() { - if nums[start] == val { - nums.remove(start); +impl Solution { + pub fn remove_element(nums: &mut Vec, val: i32) -> i32 { + let mut slowIdx = 0; + for pos in (0..nums.len()) { + if nums[pos]!=val { + nums[slowIdx] = nums[pos]; + slowIdx += 1; + } } - start += 1; + return (slowIdx) as i32; } - nums -} -fn main() { - let mut nums = vec![5,1,3,5,2,3,4,1]; - println!("{:?}",remove_element(&mut nums, 5)); } ``` + +Swift: + +```swift +func removeElement(_ nums: inout [Int], _ val: Int) -> Int { + var slowIndex = 0 + + for fastIndex in 0.. +
diff --git a/problems/0028.实现strStr.md b/problems/0028.实现strStr.md index fc14a34a..481be2ed 100644 --- a/problems/0028.实现strStr.md +++ b/problems/0028.实现strStr.md @@ -11,7 +11,7 @@ # 28. 实现 strStr() -https://leetcode-cn.com/problems/implement-strstr/ +[力扣题目链接](https://leetcode-cn.com/problems/implement-strstr/) 实现 strStr() 函数。 @@ -315,7 +315,7 @@ next[i] = j; 最后整体构建next数组的函数代码如下: -```C++ +```CPP void getNext(int* next, const string& s){     int j = -1;     next[0] = j; @@ -386,7 +386,7 @@ if (j == (t.size() - 1) ) { 那么使用next数组,用模式串匹配文本串的整体代码如下: -```C++ +```CPP int j = -1; // 因为next数组里记录的起始位置为-1 for (int i = 0; i < s.size(); i++) { // 注意i就从0开始     while(j >= 0 && s[i] != t[j + 1]) { // 不匹配 @@ -405,7 +405,7 @@ for (int i = 0; i < s.size(); i++) { // 注意i就从0开始 # 前缀表统一减一 C++代码实现 -```C++ +```CPP class Solution { public:     void getNext(int* next, const string& s) { @@ -457,7 +457,7 @@ public: 我给出的getNext的实现为:(前缀表统一减一) -```C++ +```CPP void getNext(int* next, const string& s) {     int j = -1;     next[0] = j; @@ -479,7 +479,7 @@ void getNext(int* next, const string& s) { 那么前缀表不减一来构建next数组,代码如下: -```C++ +```CPP void getNext(int* next, const string& s) { int j = 0; next[0] = 0; @@ -502,7 +502,7 @@ void getNext(int* next, const string& s) { 实现代码如下: -```C++ +```CPP class Solution { public: void getNext(int* next, const string& s) { @@ -902,4 +902,4 @@ var strStr = function (haystack, needle) { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0031.下一个排列.md b/problems/0031.下一个排列.md index fbe31eb3..05321a9a 100644 --- a/problems/0031.下一个排列.md +++ b/problems/0031.下一个排列.md @@ -11,7 +11,7 @@ # 31.下一个排列 -链接:https://leetcode-cn.com/problems/next-permutation/ +[力扣题目链接](https://leetcode-cn.com/problems/next-permutation/) 实现获取 下一个排列 的函数,算法需要将给定数字序列重新排列成字典序中下一个更大的排列。 @@ -75,7 +75,7 @@ 对应的C++代码如下: -```C++ +```CPP class Solution { public: void nextPermutation(vector& nums) { @@ -99,6 +99,24 @@ public: ## Java ```java +class Solution { + public void nextPermutation(int[] nums) { + for (int i = nums.length - 1; i >= 0; i--) { + for (int j = nums.length - 1; j > i; j--) { + if (nums[j] > nums[i]) { + // 交换 + int temp = nums[i]; + nums[i] = nums[j]; + nums[j] = temp; + // [i + 1, nums.length) 内元素升序排序 + Arrays.sort(nums, i + 1, nums.length); + return; + } + } + } + Arrays.sort(nums); // 不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列)。 + } +} ``` ## Python @@ -120,5 +138,5 @@ public: * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0034.在排序数组中查找元素的第一个和最后一个位置.md b/problems/0034.在排序数组中查找元素的第一个和最后一个位置.md index 97b9a9b6..68dd797d 100644 --- a/problems/0034.在排序数组中查找元素的第一个和最后一个位置.md +++ b/problems/0034.在排序数组中查找元素的第一个和最后一个位置.md @@ -35,8 +35,8 @@ 对二分还不了解的同学先做这两题: -* [704.二分查找](https://mp.weixin.qq.com/s/4X-8VRgnYRGd5LYGZ33m4w) -* [35.搜索插入位置](https://mp.weixin.qq.com/s/fCf5QbPDtE6SSlZ1yh_q8Q) +* [704.二分查找](https://programmercarl.com/0704.二分查找.html) +* [35.搜索插入位置](https://programmercarl.com/0035.搜索插入位置.html) 下面我来把所有情况都讨论一下。 @@ -50,21 +50,21 @@ 接下来,在去寻找左边界,和右边界了。 -采用二分法来取寻找左右边界,为了让代码清晰,我分别写两个二分来寻找左边界和右边界。 +采用二分法来去寻找左右边界,为了让代码清晰,我分别写两个二分来寻找左边界和右边界。 **刚刚接触二分搜索的同学不建议上来就像如果用一个二分来查找左右边界,很容易把自己绕进去,建议扎扎实实的写两个二分分别找左边界和右边界** ## 寻找右边界 -先来寻找右边界,至于二分查找,如果看过[为什么每次遇到二分法,都是一看就会,一写就废](https://mp.weixin.qq.com/s/fCf5QbPDtE6SSlZ1yh_q8Q)就会知道,二分查找中什么时候用while (left <= right),有什么时候用while (left < right),其实只要清楚**循环不变量**,很容易区分两种写法。 +先来寻找右边界,至于二分查找,如果看过[704.二分查找](https://programmercarl.com/0704.二分查找.html)就会知道,二分查找中什么时候用while (left <= right),有什么时候用while (left < right),其实只要清楚**循环不变量**,很容易区分两种写法。 -那么这里我采用while (left <= right)的写法,区间定义为[left, right],即左闭又闭的区间(如果这里有点看不懂了,强烈建议把[为什么每次遇到二分法,都是一看就会,一写就废](https://mp.weixin.qq.com/s/fCf5QbPDtE6SSlZ1yh_q8Q)这篇文章先看了,在把「leetcode:35.搜索插入位置」做了之后在做这道题目就好很多了) +那么这里我采用while (left <= right)的写法,区间定义为[left, right],即左闭又闭的区间(如果这里有点看不懂了,强烈建议把[704.二分查找](https://programmercarl.com/0704.二分查找.html)这篇文章先看了,704题目做了之后再做这道题目就好很多了) 确定好:计算出来的右边界是不包好target的右边界,左边界同理。 可以写出如下代码 -```C++ +```CPP // 二分查找,寻找target的右边界(不包括target) // 如果rightBorder为没有被赋值(即target在数组范围的左边,例如数组[3,3],target为2),为了处理情况一 int getRightBorder(vector& nums, int target) { @@ -86,7 +86,7 @@ int getRightBorder(vector& nums, int target) { ## 寻找左边界 -```C++ +```CPP // 二分查找,寻找target的左边界leftBorder(不包括target) // 如果leftBorder没有被赋值(即target在数组范围的右边,例如数组[3,3],target为4),为了处理情况一 int getLeftBorder(vector& nums, int target) { @@ -110,7 +110,7 @@ int getLeftBorder(vector& nums, int target) { 左右边界计算完之后,看一下主体代码,这里把上面讨论的三种情况,都覆盖了 -```C++ +```CPP class Solution { public: vector searchRange(vector& nums, int target) { @@ -223,7 +223,7 @@ class Solution { // 解法2 // 1、首先,在 nums 数组中二分查找 target; // 2、如果二分查找失败,则 binarySearch 返回 -1,表明 nums 中没有 target。此时,searchRange 直接返回 {-1, -1}; -// 3、如果二分查找失败,则 binarySearch 返回 nums 中 为 target 的一个下标。然后,通过左右滑动指针,来找到符合题意的区间 +// 3、如果二分查找成功,则 binarySearch 返回 nums 中值为 target 的一个下标。然后,通过左右滑动指针,来找到符合题意的区间 class Solution { public int[] searchRange(int[] nums, int target) { @@ -275,6 +275,117 @@ class Solution { ## Python ```python +class Solution: + def searchRange(self, nums: List[int], target: int) -> List[int]: + def getRightBorder(nums:List[int], target:int) -> int: + left, right = 0, len(nums)-1 + rightBoder = -2 # 记录一下rightBorder没有被赋值的情况 + while left <= right: + middle = left + (right-left) // 2 + if nums[middle] > target: + right = middle - 1 + else: # 寻找右边界,nums[middle] == target的时候更新left + left = middle + 1 + rightBoder = left + + return rightBoder + + def getLeftBorder(nums:List[int], target:int) -> int: + left, right = 0, len(nums)-1 + leftBoder = -2 # 记录一下leftBorder没有被赋值的情况 + while left <= right: + middle = left + (right-left) // 2 + if nums[middle] >= target: # 寻找左边界,nums[middle] == target的时候更新right + right = middle - 1; + leftBoder = right; + else: + left = middle + 1 + return leftBoder + leftBoder = getLeftBorder(nums, target) + rightBoder = getRightBorder(nums, target) + # 情况一 + if leftBoder == -2 or rightBoder == -2: return [-1, -1] + # 情况三 + if rightBoder -leftBoder >1: return [leftBoder + 1, rightBoder - 1] + # 情况二 + return [-1, -1] +``` +```python +# 解法2 +# 1、首先,在 nums 数组中二分查找 target; +# 2、如果二分查找失败,则 binarySearch 返回 -1,表明 nums 中没有 target。此时,searchRange 直接返回 {-1, -1}; +# 3、如果二分查找成功,则 binarySearch 返回 nums 中值为 target 的一个下标。然后,通过左右滑动指针,来找到符合题意的区间 +class Solution: + def searchRange(self, nums: List[int], target: int) -> List[int]: + def binarySearch(nums:List[int], target:int) -> int: + left, right = 0, len(nums)-1 + while left<=right: # 不变量:左闭右闭区间 + middle = left + (right-left) // 2 + if nums[middle] > target: + right = middle - 1 + elif nums[middle] < target: + left = middle + 1 + else: + return middle + return -1 + index = binarySearch(nums, target) + if index == -1:return [-1, -1] # nums 中不存在 target,直接返回 {-1, -1} + # nums 中存在 targe,则左右滑动指针,来找到符合题意的区间 + left, right = index, index + # 向左滑动,找左边界 + while left -1 >=0 and nums[left - 1] == target: left -=1 + # 向右滑动,找右边界 + while right+1 < len(nums) and nums[right + 1] == target: right +=1 + return [left, right] +``` +```python +# 解法3 +# 1、首先,在 nums 数组中二分查找得到第一个大于等于 target的下标(左边界)与第一个大于target的下标(右边界); +# 2、如果左边界<= 右边界,则返回 [左边界, 右边界]。否则返回[-1, -1] +class Solution: + def searchRange(self, nums: List[int], target: int) -> List[int]: + def binarySearch(nums:List[int], target:int, lower:bool) -> int: + left, right = 0, len(nums)-1 + ans = len(nums) + while left<=right: # 不变量:左闭右闭区间 + middle = left + (right-left) //2 + # lower为True,执行前半部分,找到第一个大于等于 target的下标 ,否则找到第一个大于target的下标 + if nums[middle] > target or (lower and nums[middle] >= target): + right = middle - 1 + ans = middle + else: + left = middle + 1 + return ans + + leftBorder = binarySearch(nums, target, True) # 搜索左边界 + rightBorder = binarySearch(nums, target, False) -1 # 搜索右边界 + if leftBorder<= rightBorder and rightBorder< len(nums) and nums[leftBorder] == target and nums[rightBorder] == target: + return [leftBorder, rightBorder] + return [-1, -1] +``` + +```python +# 解法4 +# 1、首先,在 nums 数组中二分查找得到第一个大于等于 target的下标leftBorder; +# 2、在 nums 数组中二分查找得到第一个大于等于 target+1的下标, 减1则得到rightBorder; +# 3、如果开始位置在数组的右边或者不存在target,则返回[-1, -1] 。否则返回[leftBorder, rightBorder] +class Solution: + def searchRange(self, nums: List[int], target: int) -> List[int]: + def binarySearch(nums:List[int], target:int) -> int: + left, right = 0, len(nums)-1 + while left<=right: # 不变量:左闭右闭区间 + middle = left + (right-left) //2 + if nums[middle] >= target: + right = middle - 1 + else: + left = middle + 1 + return left # 若存在target,则返回第一个等于target的值 + + leftBorder = binarySearch(nums, target) # 搜索左边界 + rightBorder = binarySearch(nums, target+1) -1 # 搜索右边界 + if leftBorder == len(nums) or nums[leftBorder]!= target: # 情况一和情况二 + return [-1, -1] + return [leftBorder, rightBorder] ``` ## Go @@ -291,5 +402,5 @@ class Solution { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0035.搜索插入位置.md b/problems/0035.搜索插入位置.md index 1c5ad2c8..a536f0ec 100644 --- a/problems/0035.搜索插入位置.md +++ b/problems/0035.搜索插入位置.md @@ -11,7 +11,7 @@ # 35.搜索插入位置 -题目地址:https://leetcode-cn.com/problems/search-insert-position/ +[力扣题目链接](https://leetcode-cn.com/problems/search-insert-position/) 给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。 @@ -76,7 +76,7 @@ public: ``` 时间复杂度:O(n) -时间复杂度:O(1) +空间复杂度:O(1) 效率如下: @@ -116,7 +116,7 @@ public: **大家要仔细看注释,思考为什么要写while(left <= right), 为什么要写right = middle - 1**。 -```C++ +```CPP class Solution { public: int searchInsert(vector& nums, int target) { @@ -158,7 +158,7 @@ public: **大家要仔细看注释,思考为什么要写while (left < right), 为什么要写right = middle**。 -```C++ +```CPP class Solution { public: int searchInsert(vector& nums, int target) { @@ -234,29 +234,24 @@ class Solution { ``` - - Python: ```python3 class Solution: def searchInsert(self, nums: List[int], target: int) -> int: - left, right = 0, len(nums) - 1 - + left, right = 0, len(nums) - 1 + while left <= right: middle = (left + right) // 2 - + if nums[middle] < target: left = middle + 1 elif nums[middle] > target: right = middle - 1 - else: + else: return middle return right + 1 ``` - -Go: - JavaScript: ```js var searchInsert = function (nums, target) { @@ -277,9 +272,45 @@ var searchInsert = function (nums, target) { }; ``` +Swift: + +```swift +// 暴力法 +func searchInsert(_ nums: [Int], _ target: Int) -> Int { + for i in 0..= target { + return i + } + } + return nums.count +} + +// 二分法 +func searchInsert(_ nums: [Int], _ target: Int) -> Int { + var left = 0 + var right = nums.count - 1 + + while left <= right { + let middle = left + ((right - left) >> 1) + + if nums[middle] > target { + right = middle - 1 + }else if nums[middle] < target { + left = middle + 1 + }else if nums[middle] == target { + return middle + } + } + + return right + 1 +} +``` + + + ----------------------- * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0037.解数独.md b/problems/0037.解数独.md index e43708b8..b6fa0d6e 100644 --- a/problems/0037.解数独.md +++ b/problems/0037.解数独.md @@ -11,7 +11,7 @@ ## 37. 解数独 -题目地址:https://leetcode-cn.com/problems/sudoku-solver/ +[力扣题目链接](https://leetcode-cn.com/problems/sudoku-solver/) 编写一个程序,通过填充空格来解决数独问题。 @@ -40,11 +40,11 @@ 怎么做二维递归呢? -大家已经跟着「代码随想录」刷过了如下回溯法题目,例如:[77.组合(组合问题)](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ),[131.分割回文串(分割问题)](https://mp.weixin.qq.com/s/Pb1epUTbU8fHIht-g_MS5Q),[78.子集(子集问题)](https://mp.weixin.qq.com/s/NNRzX-vJ_pjK4qxohd_LtA),[46.全排列(排列问题)](https://mp.weixin.qq.com/s/SCOjeMX1t41wcvJq49GhMw),以及[51.N皇后(N皇后问题)](https://mp.weixin.qq.com/s/lU_QwCMj6g60nh8m98GAWg),其实这些题目都是一维递归。 +大家已经跟着「代码随想录」刷过了如下回溯法题目,例如:[77.组合(组合问题)](https://programmercarl.com/0077.组合.html),[131.分割回文串(分割问题)](https://programmercarl.com/0131.分割回文串.html),[78.子集(子集问题)](https://programmercarl.com/0078.子集.html),[46.全排列(排列问题)](https://programmercarl.com/0046.全排列.html),以及[51.N皇后(N皇后问题)](https://programmercarl.com/0051.N皇后.html),其实这些题目都是一维递归。 **如果以上这几道题目没有做过的话,不建议上来就做这道题哈!** -[N皇后问题](https://mp.weixin.qq.com/s/lU_QwCMj6g60nh8m98GAWg)是因为每一行每一列只放一个皇后,只需要一层for循环遍历一行,递归来来遍历列,然后一行一列确定皇后的唯一位置。 +[N皇后问题](https://programmercarl.com/0051.N皇后.html)是因为每一行每一列只放一个皇后,只需要一层for循环遍历一行,递归来来遍历列,然后一行一列确定皇后的唯一位置。 本题就不一样了,**本题中棋盘的每一个位置都要放一个数字,并检查数字是否合法,解数独的树形结构要比N皇后更宽更深**。 @@ -59,7 +59,7 @@ **递归函数的返回值需要是bool类型,为什么呢?** -因为解数独找到一个符合的条件(就在树的叶子节点上)立刻就返回,相当于找从根节点到叶子节点一条唯一路径,所以需要使用bool返回值,这一点在[回溯算法:N皇后问题](https://mp.weixin.qq.com/s/lU_QwCMj6g60nh8m98GAWg)中已经介绍过了,一样的道理。 +因为解数独找到一个符合的条件(就在树的叶子节点上)立刻就返回,相当于找从根节点到叶子节点一条唯一路径,所以需要使用bool返回值,这一点在[回溯算法:N皇后问题](https://programmercarl.com/0051.N皇后.html)中已经介绍过了,一样的道理。 代码如下: @@ -90,7 +90,7 @@ bool backtracking(vector>& board) 代码如下:(**详细看注释**) -```C++ +```CPP bool backtracking(vector>& board) { for (int i = 0; i < board.size(); i++) { // 遍历行 for (int j = 0; j < board[0].size(); j++) { // 遍历列 @@ -125,7 +125,7 @@ bool backtracking(vector>& board) { 代码如下: -```C++ +```CPP bool isValid(int row, int col, char val, vector>& board) { for (int i = 0; i < 9; i++) { // 判断行里是否重复 if (board[row][i] == val) { @@ -154,7 +154,7 @@ bool isValid(int row, int col, char val, vector>& board) { ## C++代码 -```C++ +```CPP class Solution { private: bool backtracking(vector>& board) { @@ -437,4 +437,4 @@ var solveSudoku = function(board) { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0039.组合总和.md b/problems/0039.组合总和.md index ba8128b5..97987e6d 100644 --- a/problems/0039.组合总和.md +++ b/problems/0039.组合总和.md @@ -9,7 +9,7 @@ ## 39. 组合总和 -题目链接:https://leetcode-cn.com/problems/combination-sum/ +[力扣题目链接](https://leetcode-cn.com/problems/combination-sum/) 给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。 @@ -44,14 +44,14 @@ candidates 中的数字可以无限制重复被选取。 题目中的**无限制重复被选取,吓得我赶紧想想 出现0 可咋办**,然后看到下面提示:1 <= candidates[i] <= 200,我就放心了。 -本题和[回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ),[回溯算法:求组合总和!](https://mp.weixin.qq.com/s/HX7WW6ixbFZJASkRnCTC3w)和区别是:本题没有数量要求,可以无限重复,但是有总和的限制,所以间接的也是有个数的限制。 +本题和[回溯算法:求组合问题!](https://programmercarl.com/0077.组合.html),[回溯算法:求组合总和!](https://programmercarl.com/0216.组合总和III.html)和区别是:本题没有数量要求,可以无限重复,但是有总和的限制,所以间接的也是有个数的限制。 本题搜索的过程抽象成树形结构如下: ![39.组合总和](https://img-blog.csdnimg.cn/20201223170730367.png) 注意图中叶子节点的返回条件,因为本题没有组合数量要求,仅仅是总和的限制,所以递归没有层数的限制,只要选取的元素总和超过target,就返回! -而在[回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)和[回溯算法:求组合总和!](https://mp.weixin.qq.com/s/HX7WW6ixbFZJASkRnCTC3w) 中都可以知道要递归K层,因为要取k个元素的组合。 +而在[回溯算法:求组合问题!](https://programmercarl.com/0077.组合.html)和[回溯算法:求组合总和!](https://programmercarl.com/0216.组合总和III.html) 中都可以知道要递归K层,因为要取k个元素的组合。 ## 回溯三部曲 @@ -65,15 +65,15 @@ candidates 中的数字可以无限制重复被选取。 **本题还需要startIndex来控制for循环的起始位置,对于组合问题,什么时候需要startIndex呢?** -我举过例子,如果是一个集合来求组合的话,就需要startIndex,例如:[回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ),[回溯算法:求组合总和!](https://mp.weixin.qq.com/s/HX7WW6ixbFZJASkRnCTC3w)。 +我举过例子,如果是一个集合来求组合的话,就需要startIndex,例如:[回溯算法:求组合问题!](https://programmercarl.com/0077.组合.html),[回溯算法:求组合总和!](https://programmercarl.com/0216.组合总和III.html)。 -如果是多个集合取组合,各个集合之间相互不影响,那么就不用startIndex,例如:[回溯算法:电话号码的字母组合](https://mp.weixin.qq.com/s/e2ua2cmkE_vpYjM3j6HY0A) +如果是多个集合取组合,各个集合之间相互不影响,那么就不用startIndex,例如:[回溯算法:电话号码的字母组合](https://programmercarl.com/0017.电话号码的字母组合.html) **注意以上我只是说求组合的情况,如果是排列问题,又是另一套分析的套路,后面我再讲解排列的时候就重点介绍**。 代码如下: -```C++ +```CPP vector> result; vector path; void backtracking(vector& candidates, int target, int sum, int startIndex) @@ -89,7 +89,7 @@ void backtracking(vector& candidates, int target, int sum, int startIndex) sum等于target的时候,需要收集结果,代码如下: -```C++ +```CPP if (sum > target) { return; } @@ -103,11 +103,11 @@ if (sum == target) { 单层for循环依然是从startIndex开始,搜索candidates集合。 -**注意本题和[回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)、[回溯算法:求组合总和!](https://mp.weixin.qq.com/s/HX7WW6ixbFZJASkRnCTC3w)的一个区别是:本题元素为可重复选取的**。 +**注意本题和[回溯算法:求组合问题!](https://programmercarl.com/0077.组合.html)、[回溯算法:求组合总和!](https://programmercarl.com/0216.组合总和III.html)的一个区别是:本题元素为可重复选取的**。 如何重复选取呢,看代码,注释部分: -```C++ +```CPP for (int i = startIndex; i < candidates.size(); i++) { sum += candidates[i]; path.push_back(candidates[i]); @@ -117,9 +117,9 @@ for (int i = startIndex; i < candidates.size(); i++) { } ``` -按照[关于回溯算法,你该了解这些!](https://mp.weixin.qq.com/s/gjSgJbNbd1eAA5WkA-HeWw)中给出的模板,不难写出如下C++完整代码: +按照[关于回溯算法,你该了解这些!](https://programmercarl.com/回溯算法理论基础.html)中给出的模板,不难写出如下C++完整代码: -```C++ +```CPP // 版本一 class Solution { private: @@ -179,7 +179,7 @@ for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; 整体代码如下:(注意注释的部分) -```C++ +```CPP class Solution { private: vector> result; @@ -213,14 +213,14 @@ public: ## 总结 -本题和我们之前讲过的[回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)、[回溯算法:求组合总和!](https://mp.weixin.qq.com/s/HX7WW6ixbFZJASkRnCTC3w)有两点不同: +本题和我们之前讲过的[回溯算法:求组合问题!](https://programmercarl.com/0077.组合.html)、[回溯算法:求组合总和!](https://programmercarl.com/0216.组合总和III.html)有两点不同: * 组合没有数量要求 * 元素可无限重复选取 针对这两个问题,我都做了详细的分析。 -并且给出了对于组合问题,什么时候用startIndex,什么时候不用,并用[回溯算法:电话号码的字母组合](https://mp.weixin.qq.com/s/e2ua2cmkE_vpYjM3j6HY0A)做了对比。 +并且给出了对于组合问题,什么时候用startIndex,什么时候不用,并用[回溯算法:电话号码的字母组合](https://programmercarl.com/0017.电话号码的字母组合.html)做了对比。 最后还给出了本题的剪枝优化,这个优化如果是初学者的话并不容易想到。 @@ -346,9 +346,63 @@ var combinationSum = function(candidates, target) { }; ``` +C: +```c +int* path; +int pathTop; +int** ans; +int ansTop; +//记录每一个和等于target的path数组长度 +int* length; + +void backTracking(int target, int index, int* candidates, int candidatesSize, int sum) { + //若sum>=target就应该终止遍历 + if(sum >= target) { + //若sum等于target,将当前的组合放入ans数组中 + if(sum == target) { + int* tempPath = (int*)malloc(sizeof(int) * pathTop); + int j; + for(j = 0; j < pathTop; j++) { + tempPath[j] = path[j]; + } + ans[ansTop] = tempPath; + length[ansTop++] = pathTop; + } + return ; + } + + int i; + for(i = index; i < candidatesSize; i++) { + //将当前数字大小加入sum + sum+=candidates[i]; + path[pathTop++] = candidates[i]; + backTracking(target, i, candidates, candidatesSize, sum); + sum-=candidates[i]; + pathTop--; + } +} + +int** combinationSum(int* candidates, int candidatesSize, int target, int* returnSize, int** returnColumnSizes){ + //初始化变量 + path = (int*)malloc(sizeof(int) * 50); + ans = (int**)malloc(sizeof(int*) * 200); + length = (int*)malloc(sizeof(int) * 200); + ansTop = pathTop = 0; + backTracking(target, 0, candidates, candidatesSize, 0); + + //设置返回的数组大小 + *returnSize = ansTop; + *returnColumnSizes = (int*)malloc(sizeof(int) * ansTop); + int i; + for(i = 0; i < ansTop; i++) { + (*returnColumnSizes)[i] = length[i]; + } + return ans; +} +``` ----------------------- * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0040.组合总和II.md b/problems/0040.组合总和II.md index 70e012da..8925ef81 100644 --- a/problems/0040.组合总和II.md +++ b/problems/0040.组合总和II.md @@ -11,7 +11,7 @@ ## 40.组合总和II -题目链接:https://leetcode-cn.com/problems/combination-sum-ii/ +[力扣题目链接](https://leetcode-cn.com/problems/combination-sum-ii/) 给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。 @@ -44,12 +44,12 @@ candidates 中的每个数字在每个组合中只能使用一次。 **如果对回溯算法基础还不了解的话,我还特意录制了一期视频:[带你学透回溯算法(理论篇)](https://www.bilibili.com/video/BV1cy4y167mM/)** 可以结合题解和视频一起看,希望对大家理解回溯算法有所帮助。 -这道题目和[39.组合总和](https://mp.weixin.qq.com/s/FLg8G6EjVcxBjwCbzpACPw)如下区别: +这道题目和[39.组合总和](https://programmercarl.com/0039.组合总和.html)如下区别: 1. 本题candidates 中的每个数字在每个组合中只能使用一次。 -2. 本题数组candidates的元素是有重复的,而[39.组合总和](https://mp.weixin.qq.com/s/FLg8G6EjVcxBjwCbzpACPw)是无重复元素的数组candidates +2. 本题数组candidates的元素是有重复的,而[39.组合总和](https://programmercarl.com/0039.组合总和.html)是无重复元素的数组candidates -最后本题和[39.组合总和](https://mp.weixin.qq.com/s/FLg8G6EjVcxBjwCbzpACPw)要求一样,解集不能包含重复的组合。 +最后本题和[39.组合总和](https://programmercarl.com/0039.组合总和.html)要求一样,解集不能包含重复的组合。 **本题的难点在于区别2中:集合(数组candidates)有重复元素,但还不能有重复的组合**。 @@ -63,7 +63,7 @@ candidates 中的每个数字在每个组合中只能使用一次。 都知道组合问题可以抽象为树形结构,那么“使用过”在这个树形结构上是有两个维度的,一个维度是同一树枝上使用过,一个维度是同一树层上使用过。**没有理解这两个层面上的“使用过” 是造成大家没有彻底理解去重的根本原因。** -那么问题来了,我们是要同一树层上使用过,还是统一树枝上使用过呢? +那么问题来了,我们是要同一树层上使用过,还是同一树枝上使用过呢? 回看一下题目,元素在同一个组合内是可以重复的,怎么重复都没事,但两个组合不能相同。 @@ -84,13 +84,13 @@ candidates 中的每个数字在每个组合中只能使用一次。 * **递归函数参数** -与[39.组合总和](https://mp.weixin.qq.com/s/FLg8G6EjVcxBjwCbzpACPw)套路相同,此题还需要加一个bool型数组used,用来记录同一树枝上的元素是否使用过。 +与[39.组合总和](https://programmercarl.com/0039.组合总和.html)套路相同,此题还需要加一个bool型数组used,用来记录同一树枝上的元素是否使用过。 这个集合去重的重任就是used来完成的。 代码如下: -```C++ +```CPP vector> result; // 存放组合集合 vector path; // 符合条件的组合 void backtracking(vector& candidates, int target, int sum, int startIndex, vector& used) { @@ -98,11 +98,11 @@ void backtracking(vector& candidates, int target, int sum, int startIndex, * **递归终止条件** -与[39.组合总和](https://mp.weixin.qq.com/s/FLg8G6EjVcxBjwCbzpACPw)相同,终止条件为 `sum > target` 和 `sum == target`。 +与[39.组合总和](https://programmercarl.com/0039.组合总和.html)相同,终止条件为 `sum > target` 和 `sum == target`。 代码如下: -```C++ +```CPP if (sum > target) { // 这个条件其实可以省略 return; } @@ -116,7 +116,7 @@ if (sum == target) { * **单层搜索的逻辑** -这里与[39.组合总和](https://mp.weixin.qq.com/s/FLg8G6EjVcxBjwCbzpACPw)最大的不同就是要去重了。 +这里与[39.组合总和](https://programmercarl.com/0039.组合总和.html)最大的不同就是要去重了。 前面我们提到:要去重的是“同一树层上的使用过”,如果判断同一树层上元素(相同的元素)是否使用过了呢。 @@ -137,7 +137,7 @@ if (sum == target) { 那么单层搜索的逻辑代码如下: -```C++ +```CPP for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; i++) { // used[i - 1] == true,说明同一树支candidates[i - 1]使用过 // used[i - 1] == false,说明同一树层candidates[i - 1]使用过 @@ -161,7 +161,7 @@ for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; 回溯三部曲分析完了,整体C++代码如下: -```C++ +```CPP class Solution { private: vector> result; @@ -206,7 +206,7 @@ public: 这里直接用startIndex来去重也是可以的, 就不用used数组了。 -```C++ +```CPP class Solution { private: vector> result; @@ -244,7 +244,7 @@ public: ## 总结 -本题同样是求组合总和,但就是因为其数组candidates有重复元素,而要求不能有重复的组合,所以相对于[39.组合总和](https://mp.weixin.qq.com/s/FLg8G6EjVcxBjwCbzpACPw)难度提升了不少。 +本题同样是求组合总和,但就是因为其数组candidates有重复元素,而要求不能有重复的组合,所以相对于[39.组合总和](https://programmercarl.com/0039.组合总和.html)难度提升了不少。 **关键是去重的逻辑,代码很简单,网上一搜一大把,但几乎没有能把这块代码含义讲明白的,基本都是给出代码,然后说这就是去重了,究竟怎么个去重法也是模棱两可**。 @@ -296,7 +296,7 @@ class Solution { } ``` Python: -```py +```python class Solution: def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]: res = [] @@ -392,10 +392,69 @@ var combinationSum2 = function(candidates, target) { } }; ``` +C: +```c +int* path; +int pathTop; +int** ans; +int ansTop; +//记录ans中每一个一维数组的大小 +int* length; +int cmp(const void* a1, const void* a2) { + return *((int*)a1) - *((int*)a2); +} +void backTracking(int* candidates, int candidatesSize, int target, int sum, int startIndex) { + if(sum >= target) { + //若sum等于target,复制当前path进入 + if(sum == target) { + int* tempPath = (int*)malloc(sizeof(int) * pathTop); + int j; + for(j = 0; j < pathTop; j++) { + tempPath[j] = path[j]; + } + length[ansTop] = pathTop; + ans[ansTop++] = tempPath; + } + return ; + } + + int i; + for(i = startIndex; i < candidatesSize; i++) { + //对同一层树中使用过的元素跳过 + if(i > startIndex && candidates[i] == candidates[i-1]) + continue; + path[pathTop++] = candidates[i]; + sum += candidates[i]; + backTracking(candidates, candidatesSize, target, sum, i + 1); + //回溯 + sum -= candidates[i]; + pathTop--; + } +} + +int** combinationSum2(int* candidates, int candidatesSize, int target, int* returnSize, int** returnColumnSizes){ + path = (int*)malloc(sizeof(int) * 50); + ans = (int**)malloc(sizeof(int*) * 100); + length = (int*)malloc(sizeof(int) * 100); + pathTop = ansTop = 0; + //快速排序candidates,让相同元素挨到一起 + qsort(candidates, candidatesSize, sizeof(int), cmp); + + backTracking(candidates, candidatesSize, target, 0, 0); + + *returnSize = ansTop; + *returnColumnSizes = (int*)malloc(sizeof(int) * ansTop); + int i; + for(i = 0; i < ansTop; i++) { + (*returnColumnSizes)[i] = length[i]; + } + return ans; +} +``` ----------------------- * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0042.接雨水.md b/problems/0042.接雨水.md index 3f4d0123..4383a0b8 100644 --- a/problems/0042.接雨水.md +++ b/problems/0042.接雨水.md @@ -11,7 +11,7 @@ # 42. 接雨水 -题目链接:https://leetcode-cn.com/problems/trapping-rain-water/ +[力扣题目链接](https://leetcode-cn.com/problems/trapping-rain-water/) 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。 @@ -27,7 +27,7 @@ * 输入:height = [4,2,0,3,2,5] * 输出:9 -  + # 思路 @@ -77,7 +77,7 @@ 一样的方法,只要从头遍历一遍所有的列,然后求出每一列雨水的体积,相加之后就是总雨水的体积了。 首先从头遍历所有的列,并且**要注意第一个柱子和最后一个柱子不接雨水**,代码如下: -```C++ +```CPP for (int i = 0; i < height.size(); i++) { // 第一个柱子和最后一个柱子不接雨水 if (i == 0 || i == height.size() - 1) continue; @@ -86,7 +86,7 @@ for (int i = 0; i < height.size(); i++) { 在for循环中求左右两边最高柱子,代码如下: -```C++ +```CPP int rHeight = height[i]; // 记录右边柱子的最高高度 int lHeight = height[i]; // 记录左边柱子的最高高度 for (int r = i + 1; r < height.size(); r++) { @@ -99,14 +99,14 @@ for (int l = i - 1; l >= 0; l--) { 最后,计算该列的雨水高度,代码如下: -```C++ +```CPP int h = min(lHeight, rHeight) - height[i]; if (h > 0) sum += h; // 注意只有h大于零的时候,在统计到总和中 ``` 整体代码如下: -```C++ +```CPP class Solution { public: int trap(vector& height) { @@ -152,7 +152,7 @@ public: 代码如下: -```C++ +```CPP class Solution { public: int trap(vector& height) { @@ -186,7 +186,7 @@ public: 这个解法可以说是最不好理解的了,所以下面我花了大量的篇幅来介绍这种方法。 -单调栈就是保持栈内元素有序。和[栈与队列:单调队列](https://mp.weixin.qq.com/s/Xgcqx5eBa3xZabt_LurnNQ)一样,需要我们自己维持顺序,没有现成的容器可以用。 +单调栈就是保持栈内元素有序。和[栈与队列:单调队列](https://programmercarl.com/0239.滑动窗口最大值.html)一样,需要我们自己维持顺序,没有现成的容器可以用。 ### 准备工作 @@ -287,7 +287,7 @@ if (height[i] == height[st.top()]) { // 例如 5 5 1 7 这种情况 求当前凹槽雨水的体积代码如下: -```C++ +```CPP while (!st.empty() && height[i] > height[st.top()]) { // 注意这里是while,持续跟新栈顶元素 int mid = st.top(); st.pop(); @@ -301,7 +301,7 @@ while (!st.empty() && height[i] > height[st.top()]) { // 注意这里是while, 关键部分讲完了,整体代码如下: -```C++ +```CPP class Solution { public: int trap(vector& height) { @@ -335,7 +335,7 @@ public: 以上代码冗余了一些,但是思路是清晰的,下面我将代码精简一下,如下: -```C++ +```CPP class Solution { public: int trap(vector& height) { @@ -366,6 +366,107 @@ public: Java: +双指针法 +```java +class Solution { + public int trap(int[] height) { + int sum = 0; + for (int i = 0; i < height.length; i++) { + // 第一个柱子和最后一个柱子不接雨水 + if (i==0 || i== height.length - 1) continue; + + int rHeight = height[i]; // 记录右边柱子的最高高度 + int lHeight = height[i]; // 记录左边柱子的最高高度 + for (int r = i+1; r < height.length; r++) { + if (height[r] > rHeight) rHeight = height[r]; + } + for (int l = i-1; l >= 0; l--) { + if(height[l] > lHeight) lHeight = height[l]; + } + int h = Math.min(lHeight, rHeight) - height[i]; + if (h > 0) sum += h; + } + return sum; + + } +} +``` + +动态规划法 +```java +class Solution { + public int trap(int[] height) { + int length = height.length; + if (length <= 2) return 0; + int[] maxLeft = new int[length]; + int[] maxRight = new int[length]; + + // 记录每个柱子左边柱子最大高度 + maxLeft[0] = height[0]; + for (int i = 1; i< length; i++) maxLeft[i] = Math.max(height[i], maxLeft[i-1]); + + // 记录每个柱子右边柱子最大高度 + maxRight[length - 1] = height[length - 1]; + for(int i = length - 2; i >= 0; i--) maxRight[i] = Math.max(height[i], maxRight[i+1]); + + // 求和 + int sum = 0; + for (int i = 0; i < length; i++) { + int count = Math.min(maxLeft[i], maxRight[i]) - height[i]; + if (count > 0) sum += count; + } + return sum; + } +} +``` + +单调栈法 +```java +class Solution { + public int trap(int[] height){ + int size = height.length; + + if (size <= 2) return 0; + + // in the stack, we push the index of array + // using height[] to access the real height + Stack stack = new Stack(); + stack.push(0); + + int sum = 0; + for (int index = 1; index < size; index++){ + int stackTop = stack.peek(); + if (height[index] < height[stackTop]){ + stack.push(index); + }else if (height[index] == height[stackTop]){ + // 因为相等的相邻墙,左边一个是不可能存放雨水的,所以pop左边的index, push当前的index + stack.pop(); + stack.push(index); + }else{ + //pop up all lower value + int heightAtIdx = height[index]; + while (!stack.isEmpty() && (heightAtIdx > height[stackTop])){ + int mid = stack.pop(); + + if (!stack.isEmpty()){ + int left = stack.peek(); + + int h = Math.min(height[left], height[index]) - height[mid]; + int w = index - left - 1; + int hold = h * w; + if (hold > 0) sum += hold; + stackTop = stack.peek(); + } + } + stack.push(index); + } + } + + return sum; + } +} +``` + Python: 双指针法 @@ -388,6 +489,44 @@ class Solution: res += res1 return res ``` +动态规划 +```python3 +class Solution: + def trap(self, height: List[int]) -> int: + leftheight, rightheight = [0]*len(height), [0]*len(height) + + leftheight[0]=height[0] + for i in range(1,len(height)): + leftheight[i]=max(leftheight[i-1],height[i]) + rightheight[-1]=height[-1] + for i in range(len(height)-2,-1,-1): + rightheight[i]=max(rightheight[i+1],height[i]) + + result = 0 + for i in range(0,len(height)): + summ = min(leftheight[i],rightheight[i])-height[i] + result += summ + return result +``` +单调栈 +```python3 +class Solution: + def trap(self, height: List[int]) -> int: + st =[0] + result = 0 + for i in range(1,len(height)): + while st!=[] and height[i]>height[st[-1]]: + midh = height[st[-1]] + st.pop() + if st!=[]: + hright = height[i] + hleft = height[st[-1]] + h = min(hright,hleft)-midh + w = i-st[-1]-1 + result+=h*w + st.append(i) + return result +``` Go: @@ -398,4 +537,4 @@ JavaScript: * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0045.跳跃游戏II.md b/problems/0045.跳跃游戏II.md index a161d944..8dd59838 100644 --- a/problems/0045.跳跃游戏II.md +++ b/problems/0045.跳跃游戏II.md @@ -11,7 +11,7 @@ ## 45.跳跃游戏II -题目地址:https://leetcode-cn.com/problems/jump-game-ii/ +[力扣题目链接](https://leetcode-cn.com/problems/jump-game-ii/) 给定一个非负整数数组,你最初位于数组的第一个位置。 @@ -30,7 +30,7 @@ ## 思路 -本题相对于[55.跳跃游戏](https://mp.weixin.qq.com/s/606_N9j8ACKCODoCbV1lSA)还是难了不少。 +本题相对于[55.跳跃游戏](https://programmercarl.com/0055.跳跃游戏.html)还是难了不少。 但思路是相似的,还是要看最大覆盖范围。 @@ -63,7 +63,7 @@ C++代码如下:(详细注释) -```C++ +```CPP // 版本一 class Solution { public: @@ -106,7 +106,7 @@ public: 代码如下: -```C++ +```CPP // 版本二 class Solution { public: @@ -132,7 +132,7 @@ public: ## 总结 -相信大家可以发现,这道题目相当于[55.跳跃游戏](https://mp.weixin.qq.com/s/606_N9j8ACKCODoCbV1lSA)难了不止一点。 +相信大家可以发现,这道题目相当于[55.跳跃游戏](https://programmercarl.com/0055.跳跃游戏.html)难了不止一点。 但代码又十分简单,贪心就是这么巧妙。 @@ -236,4 +236,4 @@ var jump = function(nums) { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0046.全排列.md b/problems/0046.全排列.md index 3ee7271e..001c249e 100644 --- a/problems/0046.全排列.md +++ b/problems/0046.全排列.md @@ -9,7 +9,7 @@ ## 46.全排列 -题目链接:https://leetcode-cn.com/problems/permutations/ +[力扣题目链接](https://leetcode-cn.com/problems/permutations/) 给定一个 没有重复 数字的序列,返回其所有可能的全排列。 @@ -30,11 +30,11 @@ **如果对回溯算法基础还不了解的话,我还特意录制了一期视频:[带你学透回溯算法(理论篇)](https://www.bilibili.com/video/BV1cy4y167mM/)** 可以结合题解和视频一起看,希望对大家理解回溯算法有所帮助。 -此时我们已经学习了[77.组合问题](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)、 [131.分割回文串](https://mp.weixin.qq.com/s/Pb1epUTbU8fHIht-g_MS5Q)和[78.子集问题](https://mp.weixin.qq.com/s/NNRzX-vJ_pjK4qxohd_LtA),接下来看一看排列问题。 +此时我们已经学习了[77.组合问题](https://programmercarl.com/0077.组合.html)、 [131.分割回文串](https://programmercarl.com/0131.分割回文串.html)和[78.子集问题](https://programmercarl.com/0078.子集.html),接下来看一看排列问题。 相信这个排列问题就算是让你用for循环暴力把结果搜索出来,这个暴力也不是很好写。 -所以正如我们在[关于回溯算法,你该了解这些!](https://mp.weixin.qq.com/s/gjSgJbNbd1eAA5WkA-HeWw)所讲的为什么回溯法是暴力搜索,效率这么低,还要用它? +所以正如我们在[关于回溯算法,你该了解这些!](https://programmercarl.com/回溯算法理论基础.html)所讲的为什么回溯法是暴力搜索,效率这么低,还要用它? **因为一些问题能暴力搜出来就已经很不错了!** @@ -84,7 +84,7 @@ if (path.size() == nums.size()) { * 单层搜索的逻辑 -这里和[77.组合问题](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)、[131.切割问题](https://mp.weixin.qq.com/s/Pb1epUTbU8fHIht-g_MS5Q)和[78.子集问题](https://mp.weixin.qq.com/s/NNRzX-vJ_pjK4qxohd_LtA)最大的不同就是for循环里不用startIndex了。 +这里和[77.组合问题](https://programmercarl.com/0077.组合.html)、[131.切割问题](https://programmercarl.com/0131.分割回文串.html)和[78.子集问题](https://programmercarl.com/0078.子集.html)最大的不同就是for循环里不用startIndex了。 因为排列问题,每次都要从头开始搜索,例如元素1在[1,2]中已经使用过了,但是在[2,1]中还要再使用一次1。 @@ -106,7 +106,7 @@ for (int i = 0; i < nums.size(); i++) { 整体C++代码如下: -```C++ +```CPP class Solution { public: vector> result; @@ -183,6 +183,32 @@ class Solution { } } ``` +```java +// 解法2:通过判断path中是否存在数字,排除已经选择的数字 +class Solution { + List> result = new ArrayList<>(); + LinkedList path = new LinkedList<>(); + public List> permute(int[] nums) { + if (nums.length == 0) return result; + backtrack(nums, path); + return result; + } + public void backtrack(int[] nums, LinkedList path) { + if (path.size() == nums.length) { + result.add(new ArrayList<>(path)); + } + for (int i =0; i < nums.length; i++) { + // 如果path中已有,则跳过 + if (path.contains(nums[i])) { + continue; + } + path.add(nums[i]); + backtrack(nums, path); + path.removeLast(); + } + } +} +``` Python: ```python3 @@ -227,24 +253,27 @@ class Solution: Go: ```Go -var result [][]int -func backtrack(nums,pathNums []int,used []bool){ - if len(nums)==len(pathNums){ - tmp:=make([]int,len(nums)) - copy(tmp,pathNums) - result=append(result,tmp) - //result=append(result,pathNums) - return - } - for i:=0;i +
diff --git a/problems/0047.全排列II.md b/problems/0047.全排列II.md index 66107c95..01706eb3 100644 --- a/problems/0047.全排列II.md +++ b/problems/0047.全排列II.md @@ -10,7 +10,7 @@ ## 47.全排列 II -题目链接:https://leetcode-cn.com/problems/permutations-ii/ +[力扣题目链接](https://leetcode-cn.com/problems/permutations-ii/) 给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。 @@ -33,11 +33,11 @@ **如果对回溯算法基础还不了解的话,我还特意录制了一期视频:[带你学透回溯算法(理论篇)](https://www.bilibili.com/video/BV1cy4y167mM/)** 可以结合题解和视频一起看,希望对大家理解回溯算法有所帮助。 -这道题目和[回溯算法:排列问题!](https://mp.weixin.qq.com/s/SCOjeMX1t41wcvJq49GhMw)的区别在与**给定一个可包含重复数字的序列**,要返回**所有不重复的全排列**。 +这道题目和[回溯算法:排列问题!](https://programmercarl.com/0046.全排列.html)的区别在与**给定一个可包含重复数字的序列**,要返回**所有不重复的全排列**。 这里又涉及到去重了。 -在[回溯算法:求组合总和(三)](https://mp.weixin.qq.com/s/_1zPYk70NvHsdY8UWVGXmQ) 、[回溯算法:求子集问题(二)](https://mp.weixin.qq.com/s/WJ4JNDRJgsW3eUN72Hh3uQ)我们分别详细讲解了组合问题和子集问题如何去重。 +在[回溯算法:求组合总和(三)](https://programmercarl.com/0040.组合总和II.html) 、[回溯算法:求子集问题(二)](https://programmercarl.com/0090.子集II.html)我们分别详细讲解了组合问题和子集问题如何去重。 那么排列问题其实也是一样的套路。 @@ -51,7 +51,7 @@ **一般来说:组合问题和排列问题是在树形结构的叶子节点上收集结果,而子集问题就是取树上所有节点的结果**。 -在[回溯算法:排列问题!](https://mp.weixin.qq.com/s/SCOjeMX1t41wcvJq49GhMw)中已经详解讲解了排列问题的写法,在[回溯算法:求组合总和(三)](https://mp.weixin.qq.com/s/_1zPYk70NvHsdY8UWVGXmQ) 、[回溯算法:求子集问题(二)](https://mp.weixin.qq.com/s/WJ4JNDRJgsW3eUN72Hh3uQ)中详细讲解的去重的写法,所以这次我就不用回溯三部曲分析了,直接给出代码,如下: +在[回溯算法:排列问题!](https://programmercarl.com/0046.全排列.html)中已经详解讲解了排列问题的写法,在[回溯算法:求组合总和(三)](https://programmercarl.com/0040.组合总和II.html) 、[回溯算法:求子集问题(二)](https://programmercarl.com/0090.子集II.html)中详细讲解的去重的写法,所以这次我就不用回溯三部曲分析了,直接给出代码,如下: ## C++代码 @@ -228,35 +228,31 @@ Go: ```go var res [][]int func permute(nums []int) [][]int { - res = [][]int{} - sort.Ints(nums) - dfs(nums, make([]int, 0), make([]bool, len(nums))) - return res + res = [][]int{} + backTrack(nums,len(nums),[]int{}) + return res } +func backTrack(nums []int,numsLen int,path []int) { + if len(nums)==0{ + p:=make([]int,len(path)) + copy(p,path) + res = append(res,p) + } + used := [21]int{}//跟前一题唯一的区别,同一层不使用重复的数。关于used的思想carl在递增子序列那一题中提到过 + for i:=0;i +
diff --git a/problems/0051.N皇后.md b/problems/0051.N皇后.md index a8919ec3..69802ecb 100644 --- a/problems/0051.N皇后.md +++ b/problems/0051.N皇后.md @@ -9,7 +9,7 @@ ## 第51题. N皇后 -题目链接: https://leetcode-cn.com/problems/n-queens/ +[力扣题目链接](https://leetcode-cn.com/problems/n-queens/) n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。 @@ -21,18 +21,27 @@ n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并 每一种解法包含一个明确的 n 皇后问题的棋子放置方案,该方案中 'Q' 和 '.' 分别代表了皇后和空位。 示例: + 输入: 4 -输出: [ - [".Q..", // 解法 1 + +输出: + +解法 1 + +[ + [".Q..", "...Q", "Q...", "..Q."], - ["..Q.", // 解法 2 +解法 2 + + ["..Q.", "Q...", "...Q", ".Q.."] ] + 解释: 4 皇后问题存在两个不同的解法。 提示: @@ -171,7 +180,7 @@ bool isValid(int row, int col, vector& chessboard, int n) { ## C++代码 -```C++ +```CPP class Solution { private: vector> result; @@ -332,7 +341,7 @@ class Solution { public boolean isValid(int row, int col, int n, char[][] chessboard) { // 检查列 - for (int i=0; i +
diff --git a/problems/0052.N皇后II.md b/problems/0052.N皇后II.md new file mode 100644 index 00000000..1a34f763 --- /dev/null +++ b/problems/0052.N皇后II.md @@ -0,0 +1,104 @@ + +

+ + + + +

+

欢迎大家参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!

+ + +# 52. N皇后II + +题目链接:https://leetcode-cn.com/problems/n-queens-ii/ + +n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。 + +上图为 8 皇后问题的一种解法。 +![51n皇后](https://img-blog.csdnimg.cn/20200821152118456.png) + +给定一个整数 n,返回 n 皇后不同的解决方案的数量。 + +示例: + +输入: 4 + +输出: 2 + +解释: 4 皇后问题存在如下两个不同的解法。 + +解法 1 + +[ + [".Q..", +  "...Q", +  "Q...", +  "..Q."], + +解法 2 + + ["..Q.", +  "Q...", +  "...Q", +  ".Q.."] +] + +# 思路 + + +想看:[51.N皇后](https://mp.weixin.qq.com/s/lU_QwCMj6g60nh8m98GAWg) ,基本没有区别 + +# C++代码 + +```CPP +class Solution { +private: +int count = 0; +void backtracking(int n, int row, vector& chessboard) { + if (row == n) { + count++; + return; + } + for (int col = 0; col < n; col++) { + if (isValid(row, col, chessboard, n)) { + chessboard[row][col] = 'Q'; // 放置皇后 + backtracking(n, row + 1, chessboard); + chessboard[row][col] = '.'; // 回溯 + } + } +} +bool isValid(int row, int col, vector& chessboard, int n) { + int count = 0; + // 检查列 + for (int i = 0; i < row; i++) { // 这是一个剪枝 + if (chessboard[i][col] == 'Q') { + return false; + } + } + // 检查 45度角是否有皇后 + for (int i = row - 1, j = col - 1; i >=0 && j >= 0; i--, j--) { + if (chessboard[i][j] == 'Q') { + return false; + } + } + // 检查 135度角是否有皇后 + for(int i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++) { + if (chessboard[i][j] == 'Q') { + return false; + } + } + return true; +} + +public: + int totalNQueens(int n) { + std::vector chessboard(n, std::string(n, '.')); + backtracking(n, 0, chessboard); + return count; + + } +}; +``` + +# 其他语言补充 + diff --git a/problems/0053.最大子序和.md b/problems/0053.最大子序和.md index 81e0b35a..53159978 100644 --- a/problems/0053.最大子序和.md +++ b/problems/0053.最大子序和.md @@ -9,7 +9,7 @@ ## 53. 最大子序和 -题目地址:https://leetcode-cn.com/problems/maximum-subarray/ +[力扣题目链接](https://leetcode-cn.com/problems/maximum-subarray/) 给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。 @@ -25,7 +25,7 @@ 时间复杂度:O(n^2) 空间复杂度:O(1) -```C++ +```CPP class Solution { public: int maxSubArray(vector& nums) { @@ -81,7 +81,7 @@ if (count > result) result = count; 那么不难写出如下C++代码(关键地方已经注释) -```C++ +```CPP class Solution { public: int maxSubArray(vector& nums) { @@ -109,7 +109,7 @@ public: 那么先给出我的dp代码如下,有时间的录友可以提前做一做: -```C++ +```CPP class Solution { public: int maxSubArray(vector& nums) { @@ -214,4 +214,4 @@ var maxSubArray = function(nums) { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0053.最大子序和(动态规划).md b/problems/0053.最大子序和(动态规划).md index dd0e513b..1efd7e12 100644 --- a/problems/0053.最大子序和(动态规划).md +++ b/problems/0053.最大子序和(动态规划).md @@ -8,7 +8,7 @@ ## 53. 最大子序和 -题目地址:https://leetcode-cn.com/problems/maximum-subarray/ +[力扣题目链接](https://leetcode-cn.com/problems/maximum-subarray/) 给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。 @@ -19,7 +19,7 @@ ## 思路 -这道题之前我们在讲解贪心专题的时候用贪心算法解决过一次,[贪心算法:最大子序和](https://mp.weixin.qq.com/s/DrjIQy6ouKbpletQr0g1Fg)。 +这道题之前我们在讲解贪心专题的时候用贪心算法解决过一次,[贪心算法:最大子序和](https://programmercarl.com/0053.最大子序和.html)。 这次我们用动态规划的思路再来分析一次。 @@ -65,7 +65,7 @@ dp[0]应该是多少呢? 以上动规五部曲分析完毕,完整代码如下: -```C++ +```CPP class Solution { public: int maxSubArray(vector& nums) { @@ -87,7 +87,7 @@ public: ## 总结 -这道题目用贪心也很巧妙,但有一点绕,需要仔细想一想,如果想回顾一下贪心就看这里吧:[贪心算法:最大子序和](https://mp.weixin.qq.com/s/DrjIQy6ouKbpletQr0g1Fg) +这道题目用贪心也很巧妙,但有一点绕,需要仔细想一想,如果想回顾一下贪心就看这里吧:[贪心算法:最大子序和](https://programmercarl.com/0053.最大子序和.html) 动规的解法还是很直接的。 @@ -138,7 +138,52 @@ class Solution: ``` Go: +```Go +// solution +// 1, dp +// 2, 贪心 +func maxSubArray(nums []int) int { + n := len(nums) + // 这里的dp[i] 表示,最大的连续子数组和,包含num[i] 元素 + dp := make([]int,n) + // 初始化,由于dp 状态转移方程依赖dp[0] + dp[0] = nums[0] + // 初始化最大的和 + mx := nums[0] + for i:=1;ib { + return a + } + return b +} +``` + +JavaScript: + +```javascript +const maxSubArray = nums => { + // 数组长度,dp初始化 + const [len, dp] = [nums.length, [nums[0]]]; + // 最大值初始化为dp[0] + let max = dp[0]; + for (let i = 1; i < len; i++) { + dp[i] = Math.max(dp[i - 1] + nums[i], nums[i]); + // 更新最大值 + max = Math.max(max, dp[i]); + } + return max; +}; +``` @@ -146,4 +191,4 @@ Go: * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0055.跳跃游戏.md b/problems/0055.跳跃游戏.md index 8618515e..c9ebe0fe 100644 --- a/problems/0055.跳跃游戏.md +++ b/problems/0055.跳跃游戏.md @@ -9,7 +9,7 @@ ## 55. 跳跃游戏 -题目链接:https://leetcode-cn.com/problems/jump-game/ +[力扣题目链接](https://leetcode-cn.com/problems/jump-game/) 给定一个非负整数数组,你最初位于数组的第一个位置。 @@ -58,7 +58,7 @@ i每次移动只能在cover的范围内移动,每移动一个元素,cover得 C++代码如下: -```C++ +```CPP class Solution { public: bool canJump(vector& nums) { @@ -161,4 +161,4 @@ var canJump = function(nums) { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0056.合并区间.md b/problems/0056.合并区间.md index d4ffc554..82ca29e6 100644 --- a/problems/0056.合并区间.md +++ b/problems/0056.合并区间.md @@ -9,7 +9,7 @@ ## 56. 合并区间 -题目链接:https://leetcode-cn.com/problems/merge-intervals/ +[力扣题目链接](https://leetcode-cn.com/problems/merge-intervals/) 给出一个区间的集合,请合并所有重叠的区间。 @@ -56,7 +56,7 @@ C++代码如下: -```C++ +```CPP class Solution { public: // 按照区间左边界从小到大排序 @@ -92,7 +92,7 @@ public: 当然以上代码有冗余一些,可以优化一下,如下:(思路是一样的) -```C++ +```CPP class Solution { public: vector> merge(vector>& intervals) { @@ -126,7 +126,7 @@ public: 那应该怎么办呢? -正如我贪心系列开篇词[关于贪心算法,你该了解这些!](https://mp.weixin.qq.com/s/O935TaoHE9Eexwe_vSbRAg)中讲解的一样,贪心本来就没有套路,也没有框架,所以各种常规解法需要多接触多练习,自然而然才会想到。 +正如我贪心系列开篇词[关于贪心算法,你该了解这些!](https://programmercarl.com/贪心算法理论基础.html)中讲解的一样,贪心本来就没有套路,也没有框架,所以各种常规解法需要多接触多练习,自然而然才会想到。 「代码随想录」会把贪心常见的经典题目覆盖到,大家只要认真学习打卡就可以了。 @@ -157,6 +157,28 @@ class Solution { } } ``` +```java +// 版本2 +class Solution { + public int[][] merge(int[][] intervals) { + LinkedList res = new LinkedList<>(); + Arrays.sort(intervals, (o1, o2) -> Integer.compare(o1[0], o2[0])); + res.add(intervals[0]); + for (int i = 1; i < intervals.length; i++) { + if (intervals[i][0] <= res.getLast()[1]) { + int start = res.getLast()[0]; + int end = Math.max(intervals[i][1], res.getLast()[1]); + res.removeLast(); + res.add(new int[]{start, end}); + } + else { + res.add(intervals[i]); + } + } + return res.toArray(new int[res.size()][]); + } +} +``` Python: ```python @@ -176,30 +198,27 @@ class Solution: ``` Go: -```Go +```golang func merge(intervals [][]int) [][]int { - sort.Slice(intervals, func(i, j int) bool { - return intervals[i][0]=intervals[i+1][0]{ + intervals[i][1]=max(intervals[i][1],intervals[i+1][1])//赋值最大值 + intervals=append(intervals[:i+1],intervals[i+2:]...) + i-- + } + } + return intervals } -func max(a, b int) int { - if a > b { return a } - return b +func max(a,b int)int{ + if a>b{ + return a + } + return b } ``` @@ -229,4 +248,4 @@ var merge = function (intervals) { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0059.螺旋矩阵II.md b/problems/0059.螺旋矩阵II.md index 90b74005..9a7dc850 100644 --- a/problems/0059.螺旋矩阵II.md +++ b/problems/0059.螺旋矩阵II.md @@ -10,8 +10,9 @@ ## 59.螺旋矩阵II -题目地址:https://leetcode-cn.com/problems/spiral-matrix-ii/ -给定一个正整数 n,生成一个包含 1 到 n2 所有元素,且元素按顺时针顺序螺旋排列的正方形矩阵。 +[力扣题目链接](https://leetcode-cn.com/problems/spiral-matrix-ii/) + +给定一个正整数 n,生成一个包含 1 到 n^2 所有元素,且元素按顺时针顺序螺旋排列的正方形矩阵。 示例: @@ -33,7 +34,7 @@ 结果运行的时候各种问题,然后开始各种修修补补,最后发现改了这里哪里有问题,改了那里这里又跑不起来了。 -大家还记得我们在这篇文章[数组:每次遇到二分法,都是一看就会,一写就废](https://mp.weixin.qq.com/s/4X-8VRgnYRGd5LYGZ33m4w)中讲解了二分法,提到如果要写出正确的二分法一定要坚持**循环不变量原则**。 +大家还记得我们在这篇文章[数组:每次遇到二分法,都是一看就会,一写就废](https://programmercarl.com/0704.二分查找.html)中讲解了二分法,提到如果要写出正确的二分法一定要坚持**循环不变量原则**。 而求解本题依然是要坚持循环不变量原则。 @@ -66,7 +67,7 @@ 整体C++代码如下: -```C++ +```CPP class Solution { public: vector> generateMatrix(int n) { @@ -191,33 +192,48 @@ class Solution { python: -```python +```python3 class Solution: + def generateMatrix(self, n: int) -> List[List[int]]: - left, right, up, down = 0, n-1, 0, n-1 - matrix = [ [0]*n for _ in range(n)] - num = 1 - while left<=right and up<=down: - # 填充左到右 - for i in range(left, right+1): - matrix[up][i] = num - num += 1 - up += 1 - # 填充上到下 - for i in range(up, down+1): - matrix[i][right] = num - num += 1 - right -= 1 - # 填充右到左 - for i in range(right, left-1, -1): - matrix[down][i] = num - num += 1 - down -= 1 - # 填充下到上 - for i in range(down, up-1, -1): - matrix[i][left] = num - num += 1 + # 初始化要填充的正方形 + matrix = [[0] * n for _ in range(n)] + + left, right, up, down = 0, n - 1, 0, n - 1 + number = 1 # 要填充的数字 + + while left < right and up < down: + + # 从左到右填充上边 + for x in range(left, right): + matrix[up][x] = number + number += 1 + + # 从上到下填充右边 + for y in range(up, down): + matrix[y][right] = number + number += 1 + + # 从右到左填充下边 + for x in range(right, left, -1): + matrix[down][x] = number + number += 1 + + # 从下到上填充左边 + for y in range(down, up, -1): + matrix[y][left] = number + number += 1 + + # 缩小要填充的范围 left += 1 + right -= 1 + up += 1 + down -= 1 + + # 如果阶数为奇数,额外填充一次中心 + if n % 2: + matrix[n // 2][n // 2] = number + return matrix ``` @@ -302,11 +318,219 @@ func generateMatrix(n int) [][]int { } ``` +Swift: +```swift +func generateMatrix(_ n: Int) -> [[Int]] { + var result = [[Int]](repeating: [Int](repeating: 0, count: n), count: n) + var startRow = 0 + var startColumn = 0 + var loopCount = n / 2 + let mid = n / 2 + var count = 1 + var offset = 1 + var row: Int + var column: Int + + while loopCount > 0 { + row = startRow + column = startColumn + + for c in column ..< startColumn + n - offset { + result[startRow][c] = count + count += 1 + column += 1 + } + + for r in row ..< startRow + n - offset { + result[r][column] = count + count += 1 + row += 1 + } + + for _ in startColumn ..< column { + result[row][column] = count + count += 1 + column -= 1 + } + + for _ in startRow ..< row { + result[row][column] = count + count += 1 + row -= 1 + } + + startRow += 1 + startColumn += 1 + offset += 2 + loopCount -= 1 + } + + if (n % 2) != 0 { + result[mid][mid] = count + } + return result +} +``` + +Rust: + +```rust +impl Solution { + pub fn generate_matrix(n: i32) -> Vec> { + let mut res = vec![vec![0; n as usize]; n as usize]; + let (mut startX, mut startY, mut offset): (usize, usize, usize) = (0, 0, 1); + let mut loopIdx = n/2; + let mid: usize = loopIdx as usize; + let mut count = 1; + let (mut i, mut j): (usize, usize) = (0, 0); + while loopIdx > 0 { + i = startX; + j = startY; + + while j < (startY + (n as usize) - offset) { + res[i][j] = count; + count += 1; + j += 1; + } + + while i < (startX + (n as usize) - offset) { + res[i][j] = count; + count += 1; + i += 1; + } + + while j > startY { + res[i][j] = count; + count += 1; + j -= 1; + } + + while i > startX { + res[i][j] = count; + count += 1; + i -= 1; + } + + startX += 1; + startY += 1; + offset += 2; + loopIdx -= 1; + } + + if(n % 2 == 1) { + res[mid][mid] = count; + } + res + } +} +``` + +PHP: +```php +class Solution { + /** + * @param Integer $n + * @return Integer[][] + */ + function generateMatrix($n) { + // 初始化数组 + $res = array_fill(0, $n, array_fill(0, $n, 0)); + $mid = $loop = floor($n / 2); + $startX = $startY = 0; + $offset = 1; + $count = 1; + while ($loop > 0) { + $i = $startX; + $j = $startY; + for (; $j < $startY + $n - $offset; $j++) { + $res[$i][$j] = $count++; + } + for (; $i < $startX + $n - $offset; $i++) { + $res[$i][$j] = $count++; + } + for (; $j > $startY; $j--) { + $res[$i][$j] = $count++; + } + for (; $i > $startX; $i--) { + $res[$i][$j] = $count++; + } + $startX += 1; + $startY += 1; + $offset += 2; + $loop--; + } + if ($n % 2 == 1) { + $res[$mid][$mid] = $count; + } + return $res; + } +} +``` + +C: +```c +int** generateMatrix(int n, int* returnSize, int** returnColumnSizes){ + //初始化返回的结果数组的大小 + *returnSize = n; + *returnColumnSizes = (int*)malloc(sizeof(int) * n); + //初始化返回结果数组ans + int** ans = (int**)malloc(sizeof(int*) * n); + int i; + for(i = 0; i < n; i++) { + ans[i] = (int*)malloc(sizeof(int) * n); + (*returnColumnSizes)[i] = n; + } + + //设置每次循环的起始位置 + int startX = 0; + int startY = 0; + //设置二维数组的中间值,若n为奇数。需要最后在中间填入数字 + int mid = n / 2; + //循环圈数 + int loop = n / 2; + //偏移数 + int offset = 1; + //当前要添加的元素 + int count = 1; + + while(loop) { + int i = startX; + int j = startY; + //模拟上侧从左到右 + for(; j < startY + n - offset; j++) { + ans[startX][j] = count++; + } + //模拟右侧从上到下 + for(; i < startX + n - offset; i++) { + ans[i][j] = count++; + } + //模拟下侧从右到左 + for(; j > startY; j--) { + ans[i][j] = count++; + } + //模拟左侧从下到上 + for(; i > startX; i--) { + ans[i][j] = count++; + } + //偏移值每次加2 + offset+=2; + //遍历起始位置每次+1 + startX++; + startY++; + loop--; + } + //若n为奇数需要单独给矩阵中间赋值 + if(n%2) + ans[mid][mid] = count; + + return ans; +} +``` ----------------------- * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0062.不同路径.md b/problems/0062.不同路径.md index 50e70b3e..ce29cca1 100644 --- a/problems/0062.不同路径.md +++ b/problems/0062.不同路径.md @@ -8,7 +8,7 @@ ## 62.不同路径 -题目链接:https://leetcode-cn.com/problems/unique-paths/ +[力扣题目链接](https://leetcode-cn.com/problems/unique-paths/) 一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。 @@ -40,7 +40,7 @@ 示例 4: 输入:m = 3, n = 3 输出:6 -  + 提示: * 1 <= m, n <= 100 * 题目数据保证答案小于等于 2 * 10^9 @@ -59,7 +59,7 @@ 此时问题就可以转化为求二叉树叶子节点的个数,代码如下: -```C++ +```CPP class Solution { private: int dfs(int i, int j, int m, int n) { @@ -128,7 +128,7 @@ for (int j = 0; j < n; j++) dp[0][j] = 1; 以上动规五部曲分析完毕,C++代码如下: -```C++ +```CPP class Solution { public: int uniquePaths(int m, int n) { @@ -149,7 +149,7 @@ public: 其实用一个一维数组(也可以理解是滚动数组)就可以了,但是不利于理解,可以优化点空间,建议先理解了二维,在理解一维,C++代码如下: -```C++ +```CPP class Solution { public: int uniquePaths(int m, int n) { @@ -187,7 +187,7 @@ public: 例如如下代码是不行的。 -```C++ +```CPP class Solution { public: int uniquePaths(int m, int n) { @@ -204,7 +204,7 @@ public: 需要在计算分子的时候,不断除以分母,代码如下: -```C++ +```CPP class Solution { public: int uniquePaths(int m, int n) { @@ -333,4 +333,4 @@ var uniquePaths = function(m, n) { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0063.不同路径II.md b/problems/0063.不同路径II.md index a61ffd02..c01846bd 100644 --- a/problems/0063.不同路径II.md +++ b/problems/0063.不同路径II.md @@ -8,7 +8,7 @@ ## 63. 不同路径 II -题目链接:https://leetcode-cn.com/problems/unique-paths-ii/ +[力扣题目链接](https://leetcode-cn.com/problems/unique-paths-ii/) 一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。 @@ -49,11 +49,11 @@ ## 思路 -这道题相对于[62.不同路径](https://mp.weixin.qq.com/s/MGgGIt4QCpFMROE9X9he_A) 就是有了障碍。 +这道题相对于[62.不同路径](https://programmercarl.com/0062.不同路径.html) 就是有了障碍。 第一次接触这种题目的同学可能会有点懵,这有障碍了,应该怎么算呢? -[62.不同路径](https://mp.weixin.qq.com/s/MGgGIt4QCpFMROE9X9he_A)中我们已经详细分析了没有障碍的情况,有障碍的话,其实就是标记对应的dp table(dp数组)保持初始值(0)就可以了。 +[62.不同路径](https://programmercarl.com/0062.不同路径.html)中我们已经详细分析了没有障碍的情况,有障碍的话,其实就是标记对应的dp table(dp数组)保持初始值(0)就可以了。 动规五部曲: @@ -77,7 +77,7 @@ if (obstacleGrid[i][j] == 0) { // 当(i, j)没有障碍的时候,再推导dp[i 3. dp数组如何初始化 -在[62.不同路径](https://mp.weixin.qq.com/s/MGgGIt4QCpFMROE9X9he_A)不同路径中我们给出如下的初始化: +在[62.不同路径](https://programmercarl.com/0062.不同路径.html)不同路径中我们给出如下的初始化: ``` vector> dp(m, vector(n, 0)); // 初始值为0 @@ -97,7 +97,7 @@ for (int j = 0; j < n; j++) dp[0][j] = 1; 所以本题初始化代码为: -```C++ +```CPP vector> dp(m, vector(n, 0)); for (int i = 0; i < m && obstacleGrid[i][0] == 0; i++) dp[i][0] = 1; for (int j = 0; j < n && obstacleGrid[0][j] == 0; j++) dp[0][j] = 1; @@ -111,7 +111,7 @@ for (int j = 0; j < n && obstacleGrid[0][j] == 0; j++) dp[0][j] = 1; 代码如下: -```C++ +```CPP for (int i = 1; i < m; i++) { for (int j = 1; j < n; j++) { if (obstacleGrid[i][j] == 1) continue; @@ -135,7 +135,7 @@ for (int i = 1; i < m; i++) { 动规五部分分析完毕,对应C++代码如下: -```C++ +```CPP class Solution { public: int uniquePathsWithObstacles(vector>& obstacleGrid) { @@ -159,7 +159,7 @@ public: ## 总结 -本题是[62.不同路径](https://mp.weixin.qq.com/s/MGgGIt4QCpFMROE9X9he_A)的障碍版,整体思路大体一致。 +本题是[62.不同路径](https://programmercarl.com/0062.不同路径.html)的障碍版,整体思路大体一致。 但就算是做过62.不同路径,在做本题也会有感觉遇到障碍无从下手。 @@ -341,4 +341,4 @@ var uniquePathsWithObstacles = function(obstacleGrid) { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0070.爬楼梯.md b/problems/0070.爬楼梯.md index 96899d37..4f049e4a 100644 --- a/problems/0070.爬楼梯.md +++ b/problems/0070.爬楼梯.md @@ -7,7 +7,7 @@

欢迎大家参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!

## 70. 爬楼梯 -题目地址:https://leetcode-cn.com/problems/climbing-stairs/ +[力扣题目链接](https://leetcode-cn.com/problems/climbing-stairs/) 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 @@ -109,7 +109,7 @@ dp[i]: 爬到第i层楼梯,有dp[i]种方法 以上五部分析完之后,C++代码如下: -```C++ +```CPP // 版本一 class Solution { public: @@ -130,7 +130,7 @@ public: 当然依然也可以,优化一下空间复杂度,代码如下: -```C++ +```CPP // 版本二 class Solution { public: @@ -163,7 +163,7 @@ public: 这里我先给出我的实现代码: -```C++ +```CPP class Solution { public: int climbStairs(int n) { @@ -196,9 +196,9 @@ public: ## 总结 -这道题目和[动态规划:斐波那契数](https://mp.weixin.qq.com/s/ko0zLJplF7n_4TysnPOa_w)题目基本是一样的,但是会发现本题相比[动态规划:斐波那契数](https://mp.weixin.qq.com/s/ko0zLJplF7n_4TysnPOa_w)难多了,为什么呢? +这道题目和[动态规划:斐波那契数](https://programmercarl.com/0509.斐波那契数.html)题目基本是一样的,但是会发现本题相比[动态规划:斐波那契数](https://programmercarl.com/0509.斐波那契数.html)难多了,为什么呢? -关键是 [动态规划:斐波那契数](https://mp.weixin.qq.com/s/ko0zLJplF7n_4TysnPOa_w) 题目描述就已经把动规五部曲里的递归公式和如何初始化都给出来了,剩下几部曲也自然而然的推出来了。 +关键是 [动态规划:斐波那契数](https://programmercarl.com/0509.斐波那契数.html) 题目描述就已经把动规五部曲里的递归公式和如何初始化都给出来了,剩下几部曲也自然而然的推出来了。 而本题,就需要逐个分析了,大家现在应该初步感受出[关于动态规划,你该了解这些!](https://leetcode-cn.com/circle/article/tNuNnM/)里给出的动规五部曲了。 @@ -301,4 +301,4 @@ var climbStairs = function(n) { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0070.爬楼梯完全背包版本.md b/problems/0070.爬楼梯完全背包版本.md index 5c8270b6..4410dbaf 100644 --- a/problems/0070.爬楼梯完全背包版本.md +++ b/problems/0070.爬楼梯完全背包版本.md @@ -13,7 +13,7 @@ ## 70. 爬楼梯 -链接:https://leetcode-cn.com/problems/climbing-stairs/ +[力扣题目链接](https://leetcode-cn.com/problems/climbing-stairs/) 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 @@ -38,7 +38,7 @@ ## 思路 -这道题目 我们在[动态规划:爬楼梯](https://mp.weixin.qq.com/s/Ohop0jApSII9xxOMiFhGIw) 中已经讲过一次了,原题其实是一道简单动规的题目。 +这道题目 我们在[动态规划:爬楼梯](https://programmercarl.com/0070.爬楼梯.html) 中已经讲过一次了,原题其实是一道简单动规的题目。 既然这么简单为什么还要讲呢,其实本题稍加改动就是一道面试好题。 @@ -52,7 +52,7 @@ **此时大家应该发现这就是一个完全背包问题了!** -和昨天的题目[动态规划:377. 组合总和 Ⅳ](https://mp.weixin.qq.com/s/Iixw0nahJWQgbqVNk8k6gA)基本就是一道题了。 +和昨天的题目[动态规划:377. 组合总和 Ⅳ](https://programmercarl.com/0377.组合总和Ⅳ.html)基本就是一道题了。 动规五部曲分析如下: @@ -62,7 +62,7 @@ 2. 确定递推公式 -在[动态规划:494.目标和](https://mp.weixin.qq.com/s/2pWmaohX75gwxvBENS-NCw) 、 [动态规划:518.零钱兑换II](https://mp.weixin.qq.com/s/PlowDsI4WMBOzf3q80AksQ)、[动态规划:377. 组合总和 Ⅳ](https://mp.weixin.qq.com/s/Iixw0nahJWQgbqVNk8k6gA)中我们都讲过了,求装满背包有几种方法,递推公式一般都是dp[i] += dp[i - nums[j]]; +在[动态规划:494.目标和](https://programmercarl.com/0494.目标和.html) 、 [动态规划:518.零钱兑换II](https://programmercarl.com/0518.零钱兑换II.html)、[动态规划:377. 组合总和 Ⅳ](https://programmercarl.com/0377.组合总和Ⅳ.html)中我们都讲过了,求装满背包有几种方法,递推公式一般都是dp[i] += dp[i - nums[j]]; 本题呢,dp[i]有几种来源,dp[i - 1],dp[i - 2],dp[i - 3] 等等,即:dp[i - j] @@ -84,7 +84,7 @@ 5. 举例来推导dp数组 -介于本题和[动态规划:377. 组合总和 Ⅳ](https://mp.weixin.qq.com/s/Iixw0nahJWQgbqVNk8k6gA)几乎是一样的,这里我就不再重复举例了。 +介于本题和[动态规划:377. 组合总和 Ⅳ](https://programmercarl.com/0377.组合总和Ⅳ.html)几乎是一样的,这里我就不再重复举例了。 以上分析完毕,C++代码如下: @@ -192,4 +192,4 @@ func climbStairs(n int) int { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0072.编辑距离.md b/problems/0072.编辑距离.md index 9ddca7c0..2045c1fd 100644 --- a/problems/0072.编辑距离.md +++ b/problems/0072.编辑距离.md @@ -8,7 +8,7 @@ ## 72. 编辑距离 -https://leetcode-cn.com/problems/edit-distance/ +[力扣题目链接](https://leetcode-cn.com/problems/edit-distance/) 给你两个单词 word1 和 word2,请你计算出将 word1 转换成 word2 所使用的最少操作数 。 @@ -35,7 +35,7 @@ inention -> enention (将 'i' 替换为 'e') enention -> exention (将 'n' 替换为 'x') exention -> exection (将 'n' 替换为 'c') exection -> execution (插入 'u') -  + 提示: @@ -91,18 +91,18 @@ if (word1[i - 1] != word2[j - 1]) `if (word1[i - 1] != word2[j - 1])`,此时就需要编辑了,如何编辑呢? -操作一:word1增加一个元素,使其word1[i - 1]与word2[j - 1]相同,那么就是以下标i-2为结尾的word1 与 j-1为结尾的word2的最近编辑距离 加上一个增加元素的操作。 +* 操作一:word1删除一个元素,那么就是以下标i - 2为结尾的word1 与 j-1为结尾的word2的最近编辑距离 再加上一个操作。 即 `dp[i][j] = dp[i - 1][j] + 1;` -操作二:word2添加一个元素,使其word1[i - 1]与word2[j - 1]相同,那么就是以下标i-1为结尾的word1 与 j-2为结尾的word2的最近编辑距离 加上一个增加元素的操作。 +* 操作二:word2删除一个元素,那么就是以下标i - 1为结尾的word1 与 j-2为结尾的word2的最近编辑距离 再加上一个操作。 即 `dp[i][j] = dp[i][j - 1] + 1;` -这里有同学发现了,怎么都是添加元素,删除元素去哪了。 +这里有同学发现了,怎么都是删除元素,添加元素去哪了。 -**word2添加一个元素,相当于word1删除一个元素**,例如 `word1 = "ad" ,word2 = "a"`,`word1`删除元素`'d'`,`word2`添加一个元素`'d'`,变成`word1="a", word2="ad"`, 最终的操作数是一样! dp数组如下图所示意的: +**word2添加一个元素,相当于word1删除一个元素**,例如 `word1 = "ad" ,word2 = "a"`,`word1`删除元素`'d'` 和 `word2`添加一个元素`'d'`,变成`word1="a", word2="ad"`, 最终的操作数是一样! dp数组如下图所示意的: ``` a a d @@ -123,7 +123,7 @@ if (word1[i - 1] != word2[j - 1]) 递归公式代码如下: -```C++ +```CPP if (word1[i - 1] == word2[j - 1]) { dp[i][j] = dp[i - 1][j - 1]; } @@ -151,7 +151,7 @@ dp[i][0] :以下标i-1为结尾的字符串word1,和空字符串word2,最 所以C++代码如下: -```C++ +```CPP for (int i = 0; i <= word1.size(); i++) dp[i][0] = i; for (int j = 0; j <= word2.size(); j++) dp[0][j] = j; ``` @@ -175,7 +175,7 @@ for (int j = 0; j <= word2.size(); j++) dp[0][j] = j; 代码如下: -```C++ +```CPP for (int i = 1; i <= word1.size(); i++) { for (int j = 1; j <= word2.size(); j++) { if (word1[i - 1] == word2[j - 1]) { @@ -198,7 +198,7 @@ for (int i = 1; i <= word1.size(); i++) { 以上动规五部分析完毕,C++代码如下: -```C++ +```CPP class Solution { public: int minDistance(string word1, string word2) { @@ -338,4 +338,4 @@ const minDistance = (word1, word2) => { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0077.组合.md b/problems/0077.组合.md index 0b289a40..9b44b572 100644 --- a/problems/0077.组合.md +++ b/problems/0077.组合.md @@ -11,7 +11,7 @@ # 第77题. 组合 -题目链接:https://leetcode-cn.com/problems/combinations/ +[力扣题目链接](https://leetcode-cn.com/problems/combinations/ ) 给定两个整数 n 和 k,返回 1 ... n 中所有可能的 k 个数的组合。 @@ -80,7 +80,7 @@ for (int i = 1; i <= n; i++) { 如果脑洞模拟回溯搜索的过程,绝对可以让人窒息,所以需要抽象图形结构来进一步理解。 -**我们在[关于回溯算法,你该了解这些!](https://mp.weixin.qq.com/s/gjSgJbNbd1eAA5WkA-HeWw)中说道回溯法解决的问题都可以抽象为树形结构(N叉树),用树形结构来理解回溯就容易多了**。 +**我们在[关于回溯算法,你该了解这些!](https://programmercarl.com/回溯算法理论基础.html)中说道回溯法解决的问题都可以抽象为树形结构(N叉树),用树形结构来理解回溯就容易多了**。 那么我把组合问题抽象为如下树形结构: @@ -100,7 +100,7 @@ for (int i = 1; i <= n; i++) { 相当于只需要把达到叶子节点的结果收集起来,就可以求得 n个数中k个数的组合集合。 -在[关于回溯算法,你该了解这些!](https://mp.weixin.qq.com/s/gjSgJbNbd1eAA5WkA-HeWw)中我们提到了回溯法三部曲,那么我们按照回溯法三部曲开始正式讲解代码了。 +在[关于回溯算法,你该了解这些!](https://programmercarl.com/回溯算法理论基础.html)中我们提到了回溯法三部曲,那么我们按照回溯法三部曲开始正式讲解代码了。 ## 回溯法三部曲 @@ -173,7 +173,7 @@ for循环每次从startIndex开始遍历,然后用path保存取到的节点i 代码如下: -```C++ +```CPP for (int i = startIndex; i <= n; i++) { // 控制树的横向遍历 path.push_back(i); // 处理节点 backtracking(n, k, i + 1); // 递归:控制树的纵向遍历,注意下一层搜索要从i+1开始 @@ -188,7 +188,7 @@ backtracking的下面部分就是回溯的操作了,撤销本次处理的结 关键地方都讲完了,组合问题C++完整代码如下: -```C++ +```CPP class Solution { private: vector> result; // 存放符合条件结果的集合 @@ -214,7 +214,7 @@ public: }; ``` -还记得我们在[关于回溯算法,你该了解这些!](https://mp.weixin.qq.com/s/gjSgJbNbd1eAA5WkA-HeWw)中给出的回溯法模板么? +还记得我们在[关于回溯算法,你该了解这些!](https://programmercarl.com/回溯算法理论基础.html)中给出的回溯法模板么? 如下: ``` @@ -435,9 +435,62 @@ func backtrack(n,k,start int,track []int){ } ``` +C: +```c +int* path; +int pathTop; +int** ans; +int ansTop; + +void backtracking(int n, int k,int startIndex) { + //当path中元素个数为k个时,我们需要将path数组放入ans二维数组中 + if(pathTop == k) { + //path数组为我们动态申请,若直接将其地址放入二维数组,path数组中的值会随着我们回溯而逐渐变化 + //因此创建新的数组存储path中的值 + int* temp = (int*)malloc(sizeof(int) * k); + int i; + for(i = 0; i < k; i++) { + temp[i] = path[i]; + } + ans[ansTop++] = temp; + return ; + } + + int j; + for(j = startIndex; j <=n ;j++) { + //将当前结点放入path数组 + path[pathTop++] = j; + //进行递归 + backtracking(n, k, j + 1); + //进行回溯,将数组最上层结点弹出 + pathTop--; + } +} + +int** combine(int n, int k, int* returnSize, int** returnColumnSizes){ + //path数组存储符合条件的结果 + path = (int*)malloc(sizeof(int) * k); + //ans二维数组存储符合条件的结果数组的集合。(数组足够大,避免极端情况) + ans = (int**)malloc(sizeof(int*) * 10000); + pathTop = ansTop = 0; + + //回溯算法 + backtracking(n, k, 1); + //最后的返回大小为ans数组大小 + *returnSize = ansTop; + //returnColumnSizes数组存储ans二维数组对应下标中一维数组的长度(都为k) + *returnColumnSizes = (int*)malloc(sizeof(int) *(*returnSize)); + int i; + for(i = 0; i < *returnSize; i++) { + (*returnColumnSizes)[i] = k; + } + //返回ans二维数组 + return ans; +} +``` ----------------------- * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0077.组合优化.md b/problems/0077.组合优化.md index d3e82f09..136ceb34 100644 --- a/problems/0077.组合优化.md +++ b/problems/0077.组合优化.md @@ -10,7 +10,7 @@ -在[回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)中,我们通过回溯搜索法,解决了n个数中求k个数的组合问题。 +在[回溯算法:求组合问题!](https://programmercarl.com/0077.组合.html)中,我们通过回溯搜索法,解决了n个数中求k个数的组合问题。 > 可以直接看我的B栈视频讲解:[带你学透回溯算法-组合问题的剪枝操作](https://www.bilibili.com/video/BV1wi4y157er) @@ -18,7 +18,7 @@ 链接:https://leetcode-cn.com/problems/combinations/ -**看本篇之前,需要先看[回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)**。 +**看本篇之前,需要先看[回溯算法:求组合问题!](https://programmercarl.com/0077.组合.html)**。 大家先回忆一下[77. 组合]给出的回溯法的代码: @@ -242,11 +242,62 @@ var combine = function(n, k) { }; ``` +C: +```c +int* path; +int pathTop; +int** ans; +int ansTop; +void backtracking(int n, int k,int startIndex) { + //当path中元素个数为k个时,我们需要将path数组放入ans二维数组中 + if(pathTop == k) { + //path数组为我们动态申请,若直接将其地址放入二维数组,path数组中的值会随着我们回溯而逐渐变化 + //因此创建新的数组存储path中的值 + int* temp = (int*)malloc(sizeof(int) * k); + int i; + for(i = 0; i < k; i++) { + temp[i] = path[i]; + } + ans[ansTop++] = temp; + return ; + } + int j; + for(j = startIndex; j <= n- (k - pathTop) + 1;j++) { + //将当前结点放入path数组 + path[pathTop++] = j; + //进行递归 + backtracking(n, k, j + 1); + //进行回溯,将数组最上层结点弹出 + pathTop--; + } +} + +int** combine(int n, int k, int* returnSize, int** returnColumnSizes){ + //path数组存储符合条件的结果 + path = (int*)malloc(sizeof(int) * k); + //ans二维数组存储符合条件的结果数组的集合。(数组足够大,避免极端情况) + ans = (int**)malloc(sizeof(int*) * 10000); + pathTop = ansTop = 0; + + //回溯算法 + backtracking(n, k, 1); + //最后的返回大小为ans数组大小 + *returnSize = ansTop; + //returnColumnSizes数组存储ans二维数组对应下标中一维数组的长度(都为k) + *returnColumnSizes = (int*)malloc(sizeof(int) *(*returnSize)); + int i; + for(i = 0; i < *returnSize; i++) { + (*returnColumnSizes)[i] = k; + } + //返回ans二维数组 + return ans; +} +``` ----------------------- * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0078.子集.md b/problems/0078.子集.md index 0b2f3c09..583fe664 100644 --- a/problems/0078.子集.md +++ b/problems/0078.子集.md @@ -9,7 +9,7 @@ ## 第78题. 子集 -题目地址:https://leetcode-cn.com/problems/subsets/ +[力扣题目链接](https://leetcode-cn.com/problems/subsets/) 给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。 @@ -31,7 +31,7 @@ ## 思路 -求子集问题和[回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)和[回溯算法:分割问题!](https://mp.weixin.qq.com/s/Pb1epUTbU8fHIht-g_MS5Q)又不一样了。 +求子集问题和[回溯算法:求组合问题!](https://programmercarl.com/0077.组合.html)和[回溯算法:分割问题!](https://programmercarl.com/0131.分割回文串.html)又不一样了。 如果把 子集问题、组合问题、分割问题都抽象为一棵树的话,**那么组合问题和分割问题都是收集树的叶子节点,而子集问题是找树的所有节点!** @@ -101,7 +101,7 @@ for (int i = startIndex; i < nums.size(); i++) { ## C++代码 -根据[关于回溯算法,你该了解这些!](https://mp.weixin.qq.com/s/gjSgJbNbd1eAA5WkA-HeWw)给出的回溯算法模板: +根据[关于回溯算法,你该了解这些!](https://programmercarl.com/回溯算法理论基础.html)给出的回溯算法模板: ``` void backtracking(参数) { @@ -120,7 +120,7 @@ void backtracking(参数) { 可以写出如下回溯算法C++代码: -```C++ +```CPP class Solution { private: vector> result; @@ -157,15 +157,15 @@ public: 相信大家经过了 * 组合问题: - * [回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ) - * [回溯算法:组合问题再剪剪枝](https://mp.weixin.qq.com/s/Ri7spcJMUmph4c6XjPWXQA) - * [回溯算法:求组合总和!](https://mp.weixin.qq.com/s/HX7WW6ixbFZJASkRnCTC3w) - * [回溯算法:电话号码的字母组合](https://mp.weixin.qq.com/s/e2ua2cmkE_vpYjM3j6HY0A) - * [回溯算法:求组合总和(二)](https://mp.weixin.qq.com/s/FLg8G6EjVcxBjwCbzpACPw) - * [回溯算法:求组合总和(三)](https://mp.weixin.qq.com/s/_1zPYk70NvHsdY8UWVGXmQ) + * [回溯算法:求组合问题!](https://programmercarl.com/0077.组合.html) + * [回溯算法:组合问题再剪剪枝](https://programmercarl.com/0077.组合优化.html) + * [回溯算法:求组合总和!](https://programmercarl.com/0216.组合总和III.html) + * [回溯算法:电话号码的字母组合](https://programmercarl.com/0017.电话号码的字母组合.html) + * [回溯算法:求组合总和(二)](https://programmercarl.com/0039.组合总和.html) + * [回溯算法:求组合总和(三)](https://programmercarl.com/0040.组合总和II.html) * 分割问题: - * [回溯算法:分割回文串](https://mp.weixin.qq.com/s/Pb1epUTbU8fHIht-g_MS5Q) - * [回溯算法:复原IP地址](https://mp.weixin.qq.com/s/v--VmA8tp9vs4bXCqHhBuA) + * [回溯算法:分割回文串](https://programmercarl.com/0131.分割回文串.html) + * [回溯算法:复原IP地址](https://programmercarl.com/0093.复原IP地址.html) 洗礼之后,发现子集问题还真的有点简单了,其实这就是一道标准的模板题。 @@ -268,4 +268,4 @@ var subsets = function(nums) { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0084.柱状图中最大的矩形.md b/problems/0084.柱状图中最大的矩形.md new file mode 100644 index 00000000..57df4161 --- /dev/null +++ b/problems/0084.柱状图中最大的矩形.md @@ -0,0 +1,324 @@ + + +# 84.柱状图中最大的矩形 + +[力扣题目链接](https://leetcode-cn.com/problems/largest-rectangle-in-histogram/) + +给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。 + +求在该柱状图中,能够勾勒出来的矩形的最大面积。 + +![](https://code-thinking-1253855093.file.myqcloud.com/pics/20210803220437.png) + +![](https://code-thinking-1253855093.file.myqcloud.com/pics/20210803220506.png) + + +# 思路 + +本题和[42. 接雨水](https://programmercarl.com/0042.接雨水.html),是遥相呼应的两道题目,建议都要仔细做一做,原理上有很多相同的地方,但细节上又有差异,更可以加深对单调栈的理解! + +其实这两道题目先做那一道都可以,但我先写的42.接雨水的题解,所以如果没做过接雨水的话,建议先做一做接雨水,可以参考我的题解:[42. 接雨水](https://programmercarl.com/0042.接雨水.html) + +我们先来看一下双指针的解法: + +## 双指针解法 + +```CPP +class Solution { +public: + int largestRectangleArea(vector& heights) { + int sum = 0; + for (int i = 0; i < heights.size(); i++) { + int left = i; + int right = i; + for (; left >= 0; left--) { + if (heights[left] < heights[i]) break; + } + for (; right < heights.size(); right++) { + if (heights[right] < heights[i]) break; + } + int w = right - left - 1; + int h = heights[i]; + sum = max(sum, w * h); + } + return sum; + } +}; +``` + +如上代码并不能通过leetcode,超时了,因为时间复杂度是O(n^2)。 + +## 动态规划 + +本题动态规划的写法整体思路和[42. 接雨水](https://programmercarl.com/0042.接雨水.html)是一致的,但要比[42. 接雨水](https://programmercarl.com/0042.接雨水.html)难一些。 + +难就难在本题要记录记录每个柱子 左边第一个小于该柱子的下标,而不是左边第一个小于该柱子的高度。 + +所以需要循环查找,也就是下面在寻找的过程中使用了while,详细请看下面注释,整理思路在题解:[42. 接雨水](https://programmercarl.com/0042.接雨水.html)中已经介绍了。 + +```CPP +class Solution { +public: + int largestRectangleArea(vector& heights) { + vector minLeftIndex(heights.size()); + vector minRightIndex(heights.size()); + int size = heights.size(); + + // 记录每个柱子 左边第一个小于该柱子的下标 + minLeftIndex[0] = -1; // 注意这里初始化,防止下面while死循环 + for (int i = 1; i < size; i++) { + int t = i - 1; + // 这里不是用if,而是不断向左寻找的过程 + while (t >= 0 && heights[t] >= heights[i]) t = minLeftIndex[t]; + minLeftIndex[i] = t; + } + // 记录每个柱子 右边第一个小于该柱子的下标 + minRightIndex[size - 1] = size; // 注意这里初始化,防止下面while死循环 + for (int i = size - 2; i >= 0; i--) { + int t = i + 1; + // 这里不是用if,而是不断向右寻找的过程 + while (t < size && heights[t] >= heights[i]) t = minRightIndex[t]; + minRightIndex[i] = t; + } + // 求和 + int result = 0; + for (int i = 0; i < size; i++) { + int sum = heights[i] * (minRightIndex[i] - minLeftIndex[i] - 1); + result = max(sum, result); + } + return result; + } +}; +``` + +## 单调栈 + +本地单调栈的解法和接雨水的题目是遥相呼应的。 + +为什么这么说呢,[42. 接雨水](https://programmercarl.com/0042.接雨水.html)是找每个柱子左右两边第一个大于该柱子高度的柱子,而本题是找每个柱子左右两边第一个小于该柱子的柱子。 + +**这里就涉及到了单调栈很重要的性质,就是单调栈里的顺序,是从小到大还是从大到小**。 + +在题解[42. 接雨水](https://programmercarl.com/0042.接雨水.html)中我讲解了接雨水的单调栈从栈头(元素从栈头弹出)到栈底的顺序应该是从小到大的顺序。 + +那么因为本题是要找每个柱子左右两边第一个小于该柱子的柱子,所以从栈头(元素从栈头弹出)到栈底的顺序应该是从大到小的顺序! + +我来举一个例子,如图: + +![84.柱状图中最大的矩形](https://img-blog.csdnimg.cn/20210223155303971.jpg) + +只有栈里从大到小的顺序,才能保证栈顶元素找到左右两边第一个小于栈顶元素的柱子。 + +所以本题单调栈的顺序正好与接雨水反过来。 + +此时大家应该可以发现其实就是**栈顶和栈顶的下一个元素以及要入栈的三个元素组成了我们要求最大面积的高度和宽度** + +理解这一点,对单调栈就掌握的比较到位了。 + +除了栈内元素顺序和接雨水不同,剩下的逻辑就都差不多了,在题解[42. 接雨水](https://programmercarl.com/0042.接雨水.html)我已经对单调栈的各个方面做了详细讲解,这里就不赘述了。 + +剩下就是分析清楚如下三种情况: + +* 情况一:当前遍历的元素heights[i]小于栈顶元素heights[st.top()]的情况 +* 情况二:当前遍历的元素heights[i]等于栈顶元素heights[st.top()]的情况 +* 情况三:当前遍历的元素heights[i]大于栈顶元素heights[st.top()]的情况 + +C++代码如下: + +```CPP +// 版本一 +class Solution { +public: + int largestRectangleArea(vector& heights) { + stack st; + heights.insert(heights.begin(), 0); // 数组头部加入元素0 + heights.push_back(0); // 数组尾部加入元素0 + st.push(0); + int result = 0; + // 第一个元素已经入栈,从下表1开始 + for (int i = 1; i < heights.size(); i++) { + // 注意heights[i] 是和heights[st.top()] 比较 ,st.top()是下表 + if (heights[i] > heights[st.top()]) { + st.push(i); + } else if (heights[i] == heights[st.top()]) { + st.pop(); // 这个可以加,可以不加,效果一样,思路不同 + st.push(i); + } else { + while (heights[i] < heights[st.top()]) { // 注意是while + int mid = st.top(); + st.pop(); + int left = st.top(); + int right = i; + int w = right - left - 1; + int h = heights[mid]; + result = max(result, w * h); + } + st.push(i); + } + } + return result; + } +}; + +``` + +代码精简之后: + +```CPP +// 版本二 +class Solution { +public: + int largestRectangleArea(vector& heights) { + stack st; + heights.insert(heights.begin(), 0); // 数组头部加入元素0 + heights.push_back(0); // 数组尾部加入元素0 + st.push(0); + int result = 0; + for (int i = 1; i < heights.size(); i++) { + while (heights[i] < heights[st.top()]) { + int mid = st.top(); + st.pop(); + int w = i - st.top() - 1; + int h = heights[mid]; + result = max(result, w * h); + } + st.push(i); + } + return result; + } +}; +``` + +这里我依然建议大家按部就班把版本一写出来,把情况一二三分析清楚,然后在精简代码到版本二。 直接看版本二容易忽略细节! + +## 其他语言版本 + +Java: + +动态规划 +```java +class Solution { + public int largestRectangleArea(int[] heights) { + int length = heights.length; + int[] minLeftIndex = new int [length]; + int[] maxRigthIndex = new int [length]; + // 记录左边第一个小于该柱子的下标 + minLeftIndex[0] = -1 ; + for (int i = 1; i < length; i++) { + int t = i - 1; + // 这里不是用if,而是不断向右寻找的过程 + while (t >= 0 && heights[t] >= heights[i]) t = minLeftIndex[t]; + minLeftIndex[i] = t; + } + // 记录每个柱子 右边第一个小于该柱子的下标 + maxRigthIndex[length - 1] = length; + for (int i = length - 2; i >= 0; i--) { + int t = i + 1; + while(t < length && heights[t] >= heights[i]) t = maxRigthIndex[t]; + maxRigthIndex[i] = t; + } + // 求和 + int result = 0; + for (int i = 0; i < length; i++) { + int sum = heights[i] * (maxRigthIndex[i] - minLeftIndex[i] - 1); + result = Math.max(sum, result); + } + return result; + } +} +``` + +单调栈 +```java +class Solution { + int largestRectangleArea(int[] heights) { + Stack st = new Stack(); + + // 数组扩容,在头和尾各加入一个元素 + int [] newHeights = new int[heights.length + 2]; + newHeights[0] = 0; + newHeights[newHeights.length - 1] = 0; + for (int index = 0; index < heights.length; index++){ + newHeights[index + 1] = heights[index]; + } + + heights = newHeights; + + st.push(0); + int result = 0; + // 第一个元素已经入栈,从下表1开始 + for (int i = 1; i < heights.length; i++) { + // 注意heights[i] 是和heights[st.top()] 比较 ,st.top()是下表 + if (heights[i] > heights[st.peek()]) { + st.push(i); + } else if (heights[i] == heights[st.peek()]) { + st.pop(); // 这个可以加,可以不加,效果一样,思路不同 + st.push(i); + } else { + while (heights[i] < heights[st.peek()]) { // 注意是while + int mid = st.peek(); + st.pop(); + int left = st.peek(); + int right = i; + int w = right - left - 1; + int h = heights[mid]; + result = Math.max(result, w * h); + } + st.push(i); + } + } + return result; + } +} +``` + +Python: + +动态规划 +```python3 +class Solution: + def largestRectangleArea(self, heights: List[int]) -> int: + result = 0 + minleftindex, minrightindex = [0]*len(heights), [0]*len(heights) + + minleftindex[0]=-1 + for i in range(1,len(heights)): + t = i-1 + while t>=0 and heights[t]>=heights[i]: t=minleftindex[t] + minleftindex[i]=t + + minrightindex[-1]=len(heights) + for i in range(len(heights)-2,-1,-1): + t=i+1 + while t=heights[i]: t=minrightindex[t] + minrightindex[i]=t + + for i in range(0,len(heights)): + left = minleftindex[i] + right = minrightindex[i] + summ = (right-left-1)*heights[i] + result = max(result,summ) + return result +``` +单调栈 版本二 +```python3 +class Solution: + def largestRectangleArea(self, heights: List[int]) -> int: + heights.insert(0,0) # 数组头部加入元素0 + heights.append(0) # 数组尾部加入元素0 + st = [0] + result = 0 + for i in range(1,len(heights)): + while st!=[] and heights[i] diff --git a/problems/0090.子集II.md b/problems/0090.子集II.md index 71aef5c7..6dc631de 100644 --- a/problems/0090.子集II.md +++ b/problems/0090.子集II.md @@ -9,7 +9,7 @@ ## 第90题.子集II -题目链接:https://leetcode-cn.com/problems/subsets-ii/ +[力扣题目链接](https://leetcode-cn.com/problems/subsets-ii/) 给定一个可能包含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。 @@ -30,11 +30,11 @@ ## 思路 -做本题之前一定要先做[78.子集](https://mp.weixin.qq.com/s/NNRzX-vJ_pjK4qxohd_LtA)。 +做本题之前一定要先做[78.子集](https://programmercarl.com/0078.子集.html)。 -这道题目和[回溯算法:求子集问题!](https://mp.weixin.qq.com/s/NNRzX-vJ_pjK4qxohd_LtA)区别就是集合里有重复元素了,而且求取的子集要去重。 +这道题目和[回溯算法:求子集问题!](https://programmercarl.com/0078.子集.html)区别就是集合里有重复元素了,而且求取的子集要去重。 -那么关于回溯算法中的去重问题,**在[40.组合总和II](https://mp.weixin.qq.com/s/_1zPYk70NvHsdY8UWVGXmQ)中已经详细讲解过了,和本题是一个套路**。 +那么关于回溯算法中的去重问题,**在[40.组合总和II](https://programmercarl.com/0040.组合总和II.html)中已经详细讲解过了,和本题是一个套路**。 **剧透一下,后期要讲解的排列问题里去重也是这个套路,所以理解“树层去重”和“树枝去重”非常重要**。 @@ -44,11 +44,11 @@ 从图中可以看出,同一树层上重复取2 就要过滤掉,同一树枝上就可以重复取2,因为同一树枝上元素的集合才是唯一子集! -本题就是其实就是[回溯算法:求子集问题!](https://mp.weixin.qq.com/s/NNRzX-vJ_pjK4qxohd_LtA)的基础上加上了去重,去重我们在[回溯算法:求组合总和(三)](https://mp.weixin.qq.com/s/_1zPYk70NvHsdY8UWVGXmQ)也讲过了,所以我就直接给出代码了: +本题就是其实就是[回溯算法:求子集问题!](https://programmercarl.com/0078.子集.html)的基础上加上了去重,去重我们在[回溯算法:求组合总和(三)](https://programmercarl.com/0040.组合总和II.html)也讲过了,所以我就直接给出代码了: ## C++代码 -``` +```c++ class Solution { private: vector> result; @@ -80,11 +80,10 @@ public: return result; } }; - ``` 使用set去重的版本。 -``` +```c++ class Solution { private: vector> result; @@ -113,7 +112,6 @@ public: return result; } }; - ``` ## 补充 @@ -124,7 +122,7 @@ public: 代码如下: -```C++ +```CPP class Solution { private: vector> result; @@ -151,7 +149,6 @@ public: return result; } }; - ``` ## 总结 @@ -288,4 +285,4 @@ var subsetsWithDup = function(nums) { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0093.复原IP地址.md b/problems/0093.复原IP地址.md index 40ad7684..9f2ea6e7 100644 --- a/problems/0093.复原IP地址.md +++ b/problems/0093.复原IP地址.md @@ -10,7 +10,7 @@ ## 93.复原IP地址 -题目地址:https://leetcode-cn.com/problems/restore-ip-addresses/ +[力扣题目链接](https://leetcode-cn.com/problems/restore-ip-addresses/) 给定一个只包含数字的字符串,复原它并返回所有可能的 IP 地址格式。 @@ -45,11 +45,11 @@ s 仅由数字组成 ## 思路 -做这道题目之前,最好先把[回溯算法:分割回文串](https://mp.weixin.qq.com/s/Pb1epUTbU8fHIht-g_MS5Q)这个做了。 +做这道题目之前,最好先把[回溯算法:分割回文串](https://programmercarl.com/0131.分割回文串.html)这个做了。 这道题目相信大家刚看的时候,应该会一脸茫然。 -其实只要意识到这是切割问题,**切割问题就可以使用回溯搜索法把所有可能性搜出来**,和刚做过的[回溯算法:分割回文串](https://mp.weixin.qq.com/s/Pb1epUTbU8fHIht-g_MS5Q)就十分类似了。 +其实只要意识到这是切割问题,**切割问题就可以使用回溯搜索法把所有可能性搜出来**,和刚做过的[回溯算法:分割回文串](https://programmercarl.com/0131.分割回文串.html)就十分类似了。 切割问题可以抽象为树型结构,如图: @@ -60,7 +60,7 @@ s 仅由数字组成 * 递归参数 -在[回溯算法:分割回文串](https://mp.weixin.qq.com/s/Pb1epUTbU8fHIht-g_MS5Q)中我们就提到切割问题类似组合问题。 +在[回溯算法:分割回文串](https://programmercarl.com/0131.分割回文串.html)中我们就提到切割问题类似组合问题。 startIndex一定是需要的,因为不能重复分割,记录下一层递归分割的起始位置。 @@ -76,7 +76,7 @@ startIndex一定是需要的,因为不能重复分割,记录下一层递归 * 递归终止条件 -终止条件和[回溯算法:分割回文串](https://mp.weixin.qq.com/s/Pb1epUTbU8fHIht-g_MS5Q)情况就不同了,本题明确要求只会分成4段,所以不能用切割线切到最后作为终止条件,而是分割的段数作为终止条件。 +终止条件和[回溯算法:分割回文串](https://programmercarl.com/0131.分割回文串.html)情况就不同了,本题明确要求只会分成4段,所以不能用切割线切到最后作为终止条件,而是分割的段数作为终止条件。 pointNum表示逗点数量,pointNum为3说明字符串分成了4段了。 @@ -96,7 +96,7 @@ if (pointNum == 3) { // 逗点数量为3时,分隔结束 * 单层搜索的逻辑 -在[回溯算法:分割回文串](https://mp.weixin.qq.com/s/Pb1epUTbU8fHIht-g_MS5Q)中已经讲过在循环遍历中如何截取子串。 +在[回溯算法:分割回文串](https://programmercarl.com/0131.分割回文串.html)中已经讲过在循环遍历中如何截取子串。 在`for (int i = startIndex; i < s.size(); i++)`循环中 [startIndex, i]这个区间就是截取的子串,需要判断这个子串是否合法。 @@ -164,7 +164,7 @@ bool isValid(const string& s, int start, int end) { ## C++代码 -根据[关于回溯算法,你该了解这些!](https://mp.weixin.qq.com/s/gjSgJbNbd1eAA5WkA-HeWw)给出的回溯算法模板: +根据[关于回溯算法,你该了解这些!](https://programmercarl.com/回溯算法理论基础.html)给出的回溯算法模板: ``` void backtracking(参数) { @@ -183,7 +183,7 @@ void backtracking(参数) { 可以写出如下回溯算法C++代码: -```C++ +```CPP class Solution { private: vector result;// 记录结果 @@ -239,11 +239,11 @@ public: ## 总结 -在[回溯算法:分割回文串](https://mp.weixin.qq.com/s/Pb1epUTbU8fHIht-g_MS5Q)中我列举的分割字符串的难点,本题都覆盖了。 +在[回溯算法:分割回文串](https://programmercarl.com/0131.分割回文串.html)中我列举的分割字符串的难点,本题都覆盖了。 而且本题还需要操作字符串添加逗号作为分隔符,并验证区间的合法性。 -可以说是[回溯算法:分割回文串](https://mp.weixin.qq.com/s/Pb1epUTbU8fHIht-g_MS5Q)的加强版。 +可以说是[回溯算法:分割回文串](https://programmercarl.com/0131.分割回文串.html)的加强版。 在本文的树形结构图中,我已经把详细的分析思路都画了出来,相信大家看了之后一定会思路清晰不少! @@ -309,7 +309,35 @@ class Solution { ``` python版本: +```python +class Solution: + def restoreIpAddresses(self, s: str) -> List[str]: + res = [] + path = [] # 存放分割后的字符 + # 判断数组中的数字是否合法 + def isValid(p): + if p == '0': return True # 解决"0000" + if p[0] == '0': return False + if int(p) > 0 and int(p) <256: return True + return False + def backtrack(s, startIndex): + if len(s) > 12: return # 字符串长度最大为12 + if len(path) == 4 and startIndex == len(s): # 确保切割完,且切割后的长度为4 + res.append(".".join(path[:])) # 字符拼接 + return + + for i in range(startIndex, len(s)): + if len(s) - startIndex > 3*(4 - len(path)): continue # 剪枝,剩下的字符串大于允许的最大长度则跳过 + p = s[startIndex:i+1] # 分割字符 + if isValid(p): # 判断字符是否有效 + path.append(p) + else: continue + backtrack(s, i + 1) # 寻找i+1为起始位置的子串 + path.pop() + backtrack(s, 0) + return res +``` ```python class Solution(object): def restoreIpAddresses(self, s): @@ -453,4 +481,4 @@ func isNormalIp(s string,startIndex,end int)bool{ * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0096.不同的二叉搜索树.md b/problems/0096.不同的二叉搜索树.md index 56f50d46..68ab11c8 100644 --- a/problems/0096.不同的二叉搜索树.md +++ b/problems/0096.不同的二叉搜索树.md @@ -8,7 +8,7 @@ ## 96.不同的二叉搜索树 -题目链接:https://leetcode-cn.com/problems/unique-binary-search-trees/ +[力扣题目链接](https://leetcode-cn.com/problems/unique-binary-search-trees/) 给定一个整数 n,求以 1 ... n 为节点组成的二叉搜索树有多少种? @@ -20,7 +20,7 @@ 这道题目描述很简短,但估计大部分同学看完都是懵懵的状态,这得怎么统计呢? -关于什么是二叉搜索树,我们之前在讲解二叉树专题的时候已经详细讲解过了,也可以看看这篇[二叉树:二叉搜索树登场!](https://mp.weixin.qq.com/s/vsKrWRlETxCVsiRr8v_hHg)在回顾一波。 +关于什么是二叉搜索树,我们之前在讲解二叉树专题的时候已经详细讲解过了,也可以看看这篇[二叉树:二叉搜索树登场!](https://programmercarl.com/0700.二叉搜索树中的搜索.html)在回顾一波。 了解了二叉搜索树之后,我们应该先举几个例子,画画图,看看有没有什么规律,如图: @@ -103,7 +103,7 @@ j相当于是头结点的元素,从1遍历到i为止。 代码如下: -```C++ +```CPP for (int i = 1; i <= n; i++) { for (int j = 1; j <= i; j++) { dp[i] += dp[j - 1] * dp[i - j]; @@ -123,7 +123,7 @@ n为5时候的dp数组状态如图: 综上分析完毕,C++代码如下: -```C++ +```CPP class Solution { public: int numTrees(int n) { @@ -234,4 +234,4 @@ const numTrees =(n) => { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0098.验证二叉搜索树.md b/problems/0098.验证二叉搜索树.md index 248d10f1..6d054634 100644 --- a/problems/0098.验证二叉搜索树.md +++ b/problems/0098.验证二叉搜索树.md @@ -7,9 +7,9 @@

欢迎大家参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!

-## 98.验证二叉搜索树 +# 98.验证二叉搜索树 -题目地址:https://leetcode-cn.com/problems/validate-binary-search-tree/ +[力扣题目链接](https://leetcode-cn.com/problems/validate-binary-search-tree/) 给定一个二叉树,判断其是否是一个有效的二叉搜索树。 @@ -22,7 +22,7 @@ ![98.验证二叉搜索树](https://img-blog.csdnimg.cn/20210203144334501.png) -## 思路 +# 思路 要知道中序遍历下,输出的二叉搜索树节点的数值是有序序列。 @@ -32,7 +32,7 @@ 可以递归中序遍历将二叉搜索树转变成一个数组,代码如下: -```C++ +```CPP vector vec; void traversal(TreeNode* root) { if (root == NULL) return; @@ -44,7 +44,7 @@ void traversal(TreeNode* root) { 然后只要比较一下,这个数组是否是有序的,**注意二叉搜索树中不能有重复元素**。 -```C++ +```CPP traversal(root); for (int i = 1; i < vec.size(); i++) { // 注意要小于等于,搜索树里不能有相同元素 @@ -55,7 +55,7 @@ return true; 整体代码如下: -```C++ +```CPP class Solution { private: vector vec; @@ -103,7 +103,7 @@ if (root->val > root->left->val && root->val < root->right->val) { ![二叉搜索树](https://img-blog.csdnimg.cn/20200812191501419.png) -节点10小于左节点5,大于右节点15,但右子树里出现了一个6 这就不符合了! +节点10大于左节点5,小于右节点15,但右子树里出现了一个6 这就不符合了! * 陷阱2 @@ -121,7 +121,7 @@ if (root->val > root->left->val && root->val < root->right->val) { 要定义一个longlong的全局变量,用来比较遍历的节点是否有序,因为后台测试数据中有int最小值,所以定义为longlong的类型,初始化为longlong最小值。 -注意递归函数要有bool类型的返回值, 我们在[二叉树:递归函数究竟什么时候需要返回值,什么时候不要返回值?](https://mp.weixin.qq.com/s/6TWAVjxQ34kVqROWgcRFOg) 中讲了,只有寻找某一条边(或者一个节点)的时候,递归函数会有bool类型的返回值。 +注意递归函数要有bool类型的返回值, 我们在[二叉树:递归函数究竟什么时候需要返回值,什么时候不要返回值?](https://programmercarl.com/0112.路径总和.html) 中讲了,只有寻找某一条边(或者一个节点)的时候,递归函数会有bool类型的返回值。 其实本题是同样的道理,我们在寻找一个不符合条件的节点,如果没有找到这个节点就遍历了整个树,如果找到不符合的节点了,立刻返回。 @@ -163,7 +163,7 @@ return left && right; 整体代码如下: -```C++ +```CPP class Solution { public: long long maxVal = LONG_MIN; // 因为后台测试数据中有int最小值 @@ -189,7 +189,7 @@ public: 代码如下: -```C++ +```CPP class Solution { public: TreeNode* pre = NULL; // 用来记录前一个节点 @@ -210,11 +210,11 @@ public: ## 迭代法 -可以用迭代法模拟二叉树中序遍历,对前中后序迭代法生疏的同学可以看这两篇[二叉树:听说递归能做的,栈也能做!](https://mp.weixin.qq.com/s/c_zCrGHIVlBjUH_hJtghCg),[二叉树:前中后序迭代方式统一写法](https://mp.weixin.qq.com/s/WKg0Ty1_3SZkztpHubZPRg) +可以用迭代法模拟二叉树中序遍历,对前中后序迭代法生疏的同学可以看这两篇[二叉树:听说递归能做的,栈也能做!](https://programmercarl.com/二叉树的迭代遍历.html),[二叉树:前中后序迭代方式统一写法](https://programmercarl.com/二叉树的统一迭代法.html) 迭代法中序遍历稍加改动就可以了,代码如下: -```C++ +```CPP class Solution { public: bool isValidBST(TreeNode* root) { @@ -240,9 +240,9 @@ public: }; ``` -在[二叉树:二叉搜索树登场!](https://mp.weixin.qq.com/s/vsKrWRlETxCVsiRr8v_hHg)中我们分明写出了痛哭流涕的简洁迭代法,怎么在这里不行了呢,因为本题是要验证二叉搜索树啊。 +在[二叉树:二叉搜索树登场!](https://programmercarl.com/0700.二叉搜索树中的搜索.html)中我们分明写出了痛哭流涕的简洁迭代法,怎么在这里不行了呢,因为本题是要验证二叉搜索树啊。 -## 总结 +# 总结 这道题目是一个简单题,但对于没接触过的同学还是有难度的。 @@ -251,10 +251,10 @@ public: 只要把基本类型的题目都做过,总结过之后,思路自然就开阔了。 -## 其他语言版本 +# 其他语言版本 -Java: +## Java ```Java class Solution { @@ -336,38 +336,62 @@ class Solution { } ``` -Python: +## Python + +**递归** - 利用BST中序遍历特性,把树"压缩"成数组 ```python -# Definition for a binary tree node. -# class TreeNode: -# def __init__(self, val=0, left=None, right=None): -# self.val = val -# self.left = left -# self.right = right -# 递归法 class Solution: def isValidBST(self, root: TreeNode) -> bool: - res = [] //把二叉搜索树按中序遍历写成list - def buildalist(root): - if not root: return - buildalist(root.left) //左 - res.append(root.val) //中 - buildalist(root.right) //右 - return res - buildalist(root) - return res == sorted(res) and len(set(res)) == len(res) //检查list里的数有没有重复元素,以及是否按从小到大排列 + # 思路: 利用BST中序遍历的特性. + # 中序遍历输出的二叉搜索树节点的数值是有序序列 + candidate_list = [] + + def __traverse(root: TreeNode) -> None: + nonlocal candidate_list + if not root: + return + __traverse(root.left) + candidate_list.append(root.val) + __traverse(root.right) + + def __is_sorted(nums: list) -> bool: + for i in range(1, len(nums)): + if nums[i] <= nums[i - 1]: # ⚠️ 注意: Leetcode定义二叉搜索树中不能有重复元素 + return False + return True + + __traverse(root) + res = __is_sorted(candidate_list) + + return res +``` -# 简单递归法 +**递归** - 标准做法 + +```python class Solution: def isValidBST(self, root: TreeNode) -> bool: - def isBST(root, min_val, max_val): - if not root: return True - if root.val >= max_val or root.val <= min_val: + # 规律: BST的中序遍历节点数值是从小到大. + cur_max = -float("INF") + def __isValidBST(root: TreeNode) -> bool: + nonlocal cur_max + + if not root: + return True + + is_left_valid = __isValidBST(root.left) + if cur_max < root.val: + cur_max = root.val + else: return False - return isBST(root.left, min_val, root.val) and isBST(root.right, root.val, max_val) - return isBST(root, float("-inf"), float("inf")) + is_right_valid = __isValidBST(root.right) + + return is_left_valid and is_right_valid + return __isValidBST(root) +``` -# 迭代-中序遍历 +```python +迭代-中序遍历 class Solution: def isValidBST(self, root: TreeNode) -> bool: stack = [] @@ -386,7 +410,8 @@ class Solution: return True ``` -Go: +## Go + ```Go import "math" @@ -429,9 +454,9 @@ func isValidBST(root *TreeNode) bool { } ``` -JavaScript版本 +## JavaScript -> 辅助数组解决 +辅助数组解决 ```javascript /** @@ -464,7 +489,7 @@ var isValidBST = function (root) { }; ``` -> 递归中解决 +递归中解决 ```javascript /** @@ -504,4 +529,4 @@ var isValidBST = function (root) { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0100.相同的树.md b/problems/0100.相同的树.md index b536ffe3..6ffaf3cb 100644 --- a/problems/0100.相同的树.md +++ b/problems/0100.相同的树.md @@ -10,7 +10,7 @@ # 100. 相同的树 -题目地址:https://leetcode-cn.com/problems/same-tree/ +[力扣题目链接](https://leetcode-cn.com/problems/same-tree/) 给定两个二叉树,编写一个函数来检验它们是否相同。 @@ -23,11 +23,11 @@ # 思路 -在[101.对称二叉树](https://mp.weixin.qq.com/s/Kgf0gjvlDlNDfKIH2b1Oxg)中,我们讲到对于二叉树是否对称,要比较的是根节点的左子树与右子树是不是相互翻转的,理解这一点就知道了**其实我们要比较的是两个树(这两个树是根节点的左右子树)**,所以在递归遍历的过程中,也是要同时遍历两棵树。 +在[101.对称二叉树](https://programmercarl.com/0101.对称二叉树.html)中,我们讲到对于二叉树是否对称,要比较的是根节点的左子树与右子树是不是相互翻转的,理解这一点就知道了**其实我们要比较的是两个树(这两个树是根节点的左右子树)**,所以在递归遍历的过程中,也是要同时遍历两棵树。 理解这一本质之后,就会发现,求二叉树是否对称,和求二叉树是否相同几乎是同一道题目。 -**如果没有读过[二叉树:我对称么?](https://mp.weixin.qq.com/s/Kgf0gjvlDlNDfKIH2b1Oxg)这一篇,请认真读完再做这道题,就会有感觉了。** +**如果没有读过[二叉树:我对称么?](https://programmercarl.com/0101.对称二叉树.html)这一篇,请认真读完再做这道题,就会有感觉了。** 递归三部曲中: @@ -42,7 +42,7 @@ bool compare(TreeNode* tree1, TreeNode* tree2) ``` -分析过程同[101.对称二叉树](https://mp.weixin.qq.com/s/Kgf0gjvlDlNDfKIH2b1Oxg)。 +分析过程同[101.对称二叉树](https://programmercarl.com/0101.对称二叉树.html)。 2. 确定终止条件 @@ -61,14 +61,14 @@ bool compare(TreeNode* tree1, TreeNode* tree2) 此时tree1、tree2节点不为空,且数值也不相同的情况我们也处理了。 代码如下: -```C++ +```CPP if (tree1 == NULL && tree2 != NULL) return false; else if (tree1 != NULL && tree2 == NULL) return false; else if (tree1 == NULL && tree2 == NULL) return true; else if (tree1->val != tree2->val) return false; // 注意这里我没有使用else ``` -分析过程同[101.对称二叉树](https://mp.weixin.qq.com/s/Kgf0gjvlDlNDfKIH2b1Oxg) +分析过程同[101.对称二叉树](https://programmercarl.com/0101.对称二叉树.html) 3. 确定单层递归的逻辑 @@ -77,7 +77,7 @@ else if (tree1->val != tree2->val) return false; // 注意这里我没有 代码如下: -```C++ +```CPP bool left = compare(tree1->left, tree2->left); // 左子树:左、 右子树:左 bool right = compare(tree1->right, tree2->right); // 左子树:右、 右子树:右 bool isSame = left && right; // 左子树:中、 右子树:中(逻辑处理) @@ -85,7 +85,7 @@ return isSame; ``` 最后递归的C++整体代码如下: -```C++ +```CPP class Solution { public: bool compare(TreeNode* tree1, TreeNode* tree2) { @@ -119,7 +119,7 @@ public: ## 递归 -```C++ +```CPP class Solution { public: bool compare(TreeNode* left, TreeNode* right) { @@ -138,30 +138,30 @@ public: ## 迭代法 -```C++ -lass Solution { +```CPP +class Solution { public: bool isSameTree(TreeNode* p, TreeNode* q) { if (p == NULL && q == NULL) return true; if (p == NULL || q == NULL) return false; queue que; - que.push(p); // - que.push(q); // + que.push(p); // 添加根节点p + que.push(q); // 添加根节点q while (!que.empty()) { // TreeNode* leftNode = que.front(); que.pop(); TreeNode* rightNode = que.front(); que.pop(); - if (!leftNode && !rightNode) { // + if (!leftNode && !rightNode) { // 若p的节点与q的节点都为空 continue; } - // + // 若p的节点与q的节点有一个为空或p的节点的值与q节点不同 if ((!leftNode || !rightNode || (leftNode->val != rightNode->val))) { return false; } - que.push(leftNode->left); // - que.push(rightNode->left); // - que.push(leftNode->right); // - que.push(rightNode->right); // + que.push(leftNode->left); // 添加p节点的左子树节点 + que.push(rightNode->left); // 添加q节点的左子树节点 + que.push(leftNode->right); // 添加p节点的右子树节点 + que.push(rightNode->right); // 添加q节点的右子树节点 } return true; } @@ -172,8 +172,72 @@ public: Java: -Python: +```java +// 递归法 +class Solution { + public boolean isSameTree(TreeNode p, TreeNode q) { + if (p == null && q == null) return true; + else if (q == null || p == null) return false; + else if (q.val != p.val) return false; + return isSameTree(q.left, p.left) && isSameTree(q.right, p.right); + } +} +``` +```java +// 迭代法 +class Solution { + public boolean isSameTree(TreeNode p, TreeNode q) { + if(p == null && q == null) return true; + if(p == null || q == null) return false; + Queue que= new LinkedList(); + que.offer(p); + que.offer(q); + while(!que.isEmpty()){ + TreeNode leftNode = que.poll(); + TreeNode rightNode = que.poll(); + if(leftNode == null && rightNode == null) continue; + if(leftNode == null || rightNode== null || leftNode.val != rightNode.val) return false; + que.offer(leftNode.left); + que.offer(rightNode.left); + que.offer(leftNode.right); + que.offer(rightNode.right); + } + return true; + } +} +``` +Python: +```python +# 递归法 +class Solution: + def isSameTree(self, p: TreeNode, q: TreeNode) -> bool: + if not p and not q: return True + elif not p or not q: return False + elif p.val != q.val: return False + return self.isSameTree(p.left, q.left) and self.isSameTree(p.right, q.right) +``` + +```python +# 迭代法 +class Solution: + def isSameTree(self, p: TreeNode, q: TreeNode) -> bool: + if not p and not q: return True + if not p or not q: return False + que = collections.deque() + que.append(p) + que.append(q) + while que: + leftNode = que.popleft() + rightNode = que.popleft() + if not leftNode and not rightNode: continue + if not leftNode or not rightNode or leftNode.val != rightNode.val: return False + que.append(leftNode.left) + que.append(rightNode.left) + que.append(leftNode.right) + que.append(rightNode.right) + return True +``` Go: JavaScript: @@ -182,5 +246,5 @@ JavaScript: * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0101.对称二叉树.md b/problems/0101.对称二叉树.md index 241564e9..03d3acaa 100644 --- a/problems/0101.对称二叉树.md +++ b/problems/0101.对称二叉树.md @@ -9,7 +9,7 @@ # 101. 对称二叉树 -题目地址:https://leetcode-cn.com/problems/symmetric-tree/ +[力扣题目链接](https://leetcode-cn.com/problems/symmetric-tree/) 给定一个二叉树,检查它是否是镜像对称的。 @@ -73,7 +73,7 @@ bool compare(TreeNode* left, TreeNode* right) 此时左右节点不为空,且数值也不相同的情况我们也处理了。 代码如下: -```C++ +```CPP if (left == NULL && right != NULL) return false; else if (left != NULL && right == NULL) return false; else if (left == NULL && right == NULL) return true; @@ -93,7 +93,7 @@ else if (left->val != right->val) return false; // 注意这里我没有 代码如下: -```C++ +```CPP bool outside = compare(left->left, right->right); // 左子树:左、 右子树:右 bool inside = compare(left->right, right->left); // 左子树:右、 右子树:左 bool isSame = outside && inside; // 左子树:中、 右子树:中(逻辑处理) @@ -104,7 +104,7 @@ return isSame; 最后递归的C++整体代码如下: -```C++ +```CPP class Solution { public: bool compare(TreeNode* left, TreeNode* right) { @@ -137,7 +137,7 @@ public: **盲目的照着抄,结果就是:发现这是一道“简单题”,稀里糊涂的就过了,但是真正的每一步判断逻辑未必想到清楚。** 当然我可以把如上代码整理如下: -```C++ +```CPP class Solution { public: bool compare(TreeNode* left, TreeNode* right) { @@ -177,7 +177,7 @@ public: 代码如下: -```C++ +```CPP class Solution { public: bool isSymmetric(TreeNode* root) { @@ -212,7 +212,7 @@ public: 只要把队列原封不动的改成栈就可以了,我下面也给出了代码。 -```C++ +```CPP class Solution { public: bool isSymmetric(TreeNode* root) { @@ -251,6 +251,8 @@ public: # 相关题目推荐 +这两道题目基本和本题是一样的,只要稍加修改就可以AC。 + * 100.相同的树 * 572.另一个树的子树 @@ -579,4 +581,4 @@ var isSymmetric = function(root) { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0102.二叉树的层序遍历.md b/problems/0102.二叉树的层序遍历.md index 8be3ac47..a2fd6f03 100644 --- a/problems/0102.二叉树的层序遍历.md +++ b/problems/0102.二叉树的层序遍历.md @@ -6,11 +6,8 @@

欢迎大家参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!

-# 二叉树的层序遍历 -看完这篇文章虽然不能打十个,但是可以迅速打八个!而且够快! - -学会二叉树的层序遍历,可以一口气撸完leetcode上八道题目: +学会二叉树的层序遍历,可以一口气打完以下十题: * 102.二叉树的层序遍历 * 107.二叉树的层次遍历II @@ -20,11 +17,18 @@ * 515.在每个树行中找最大值 * 116.填充每个节点的下一个右侧节点指针 * 117.填充每个节点的下一个右侧节点指针II +* 104.二叉树的最大深度 +* 111.二叉树的最小深度 + +在之前写过这篇文章 [二叉树:层序遍历登场!](https://programmercarl.com/0102.二叉树的层序遍历.html),可惜当时只打了5个,还不够,再给我一次机会,我打十个! + +![我要打十个](https://tva1.sinaimg.cn/large/008eGmZEly1gnadnltbpjg309603w4qp.gif) -## 102.二叉树的层序遍历 -题目地址:https://leetcode-cn.com/problems/binary-tree-level-order-traversal/ +# 102.二叉树的层序遍历 + +[力扣题目链接](https://leetcode-cn.com/problems/binary-tree-level-order-traversal/) 给你一个二叉树,请你返回其按 层序遍历 得到的节点值。 (即逐层地,从左到右访问所有节点)。 @@ -34,10 +38,9 @@ 我们之前讲过了三篇关于二叉树的深度优先遍历的文章: -* [二叉树:前中后序递归法](https://mp.weixin.qq.com/s/Ww60X5mIKWdMQV4cN3ejOA) -* [二叉树:前中后序迭代法](https://mp.weixin.qq.com/s/OH7aCVJ5-Gi32PkNCoZk4A) -* [二叉树:前中后序迭代方式统一写法](https://mp.weixin.qq.com/s/ATQMPCpBlaAgrqdLDMVPZA) - +* [二叉树:前中后序递归法](https://programmercarl.com/二叉树的递归遍历.html) +* [二叉树:前中后序迭代法](https://programmercarl.com/二叉树的迭代遍历.html) +* [二叉树:前中后序迭代方式统一写法](https://programmercarl.com/二叉树的统一迭代法.html) 接下来我们再来介绍二叉树的另一种遍历方式:层序遍历。 @@ -53,11 +56,11 @@ 这样就实现了层序从左到右遍历二叉树。 -代码如下:**这份代码也可以作为二叉树层序遍历的模板,以后再打七个就靠它了**。 +代码如下:**这份代码也可以作为二叉树层序遍历的模板,打十个就靠它了**。 C++代码: -``` +```CPP class Solution { public: vector> levelOrder(TreeNode* root) { @@ -84,28 +87,48 @@ public: python代码: + +```python3 + +class Solution: + """二叉树层序遍历迭代解法""" + + def levelOrder(self, root: TreeNode) -> List[List[int]]: + results = [] + if not root: + return results + + from collections import deque + que = deque([root]) + + while que: + size = len(que) + result = [] + for _ in range(size): + cur = que.popleft() + result.append(cur.val) + if cur.left: + que.append(cur.left) + if cur.right: + que.append(cur.right) + results.append(result) + + return results +``` ```python +# 递归法 class Solution: def levelOrder(self, root: TreeNode) -> List[List[int]]: - if not root: - return [] - - quene = [root] - out_list = [] - - while quene: - length = len(queue) - in_list = [] - for _ in range(length): - curnode = queue.pop(0) # (默认移除列表最后一个元素)这里需要移除队列最头上的那个 - in_list.append(curnode.val) - if curnode.left: queue.append(curnode.left) - if curnode.right: queue.append(curnode.right) - out_list.append(in_list) - - return out_list + res = [] + def helper(root, depth): + if not root: return [] + if len(res) == depth: res.append([]) # start the current depth + res[depth].append(root.val) # fulfil the current depth + if root.left: helper(root.left, depth + 1) # process child nodes for the next depth + if root.right: helper(root.right, depth + 1) + helper(root, 0) + return res ``` - java: ```Java @@ -225,11 +248,12 @@ var levelOrder = function(root) { ``` -**此时我们就掌握了二叉树的层序遍历了,那么如下五道leetcode上的题目,只需要修改模板的一两行代码(不能再多了),便可打倒!** +**此时我们就掌握了二叉树的层序遍历了,那么如下九道力扣上的题目,只需要修改模板的两三行代码(不能再多了),便可打倒!** -## 107.二叉树的层次遍历 II -题目链接:https://leetcode-cn.com/problems/binary-tree-level-order-traversal-ii/ +# 107.二叉树的层次遍历 II + +[力扣题目链接](https://leetcode-cn.com/problems/binary-tree-level-order-traversal-ii/) 给定一个二叉树,返回其节点值自底向上的层次遍历。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历) @@ -241,7 +265,7 @@ var levelOrder = function(root) { C++代码: -```C++ +```CPP class Solution { public: vector> levelOrderBottom(TreeNode* root) { @@ -270,29 +294,29 @@ python代码: ```python class Solution: + """二叉树层序遍历II迭代解法""" + def levelOrderBottom(self, root: TreeNode) -> List[List[int]]: + results = [] if not root: - return [] - quene = [root] - out_list = [] + return results - while quene: - in_list = [] - for _ in range(len(quene)): - node = quene.pop(0) - in_list.append(node.val) - if node.left: - quene.append(node.left) - if node.right: - quene.append(node.right) - - out_list.append(in_list) - - out_list.reverse() - return out_list - -# 执行用时:36 ms, 在所有 Python3 提交中击败了92.00%的用户 -# 内存消耗:15.2 MB, 在所有 Python3 提交中击败了63.76%的用户 + from collections import deque + que = deque([root]) + + while que: + result = [] + for _ in range(len(que)): + cur = que.popleft() + result.append(cur.val) + if cur.left: + que.append(cur.left) + if cur.right: + que.append(cur.right) + results.append(result) + + results.reverse() + return results ``` Java: @@ -404,9 +428,9 @@ var levelOrderBottom = function(root) { ``` -## 199.二叉树的右视图 +# 199.二叉树的右视图 -题目链接:https://leetcode-cn.com/problems/binary-tree-right-side-view/ +[力扣题目链接](https://leetcode-cn.com/problems/binary-tree-right-side-view/) 给定一棵二叉树,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。 @@ -418,7 +442,7 @@ var levelOrderBottom = function(root) { C++代码: -```C++ +```CPP class Solution { public: vector rightSideView(TreeNode* root) { @@ -581,9 +605,9 @@ var rightSideView = function(root) { }; ``` -## 637.二叉树的层平均值 +# 637.二叉树的层平均值 -题目链接:https://leetcode-cn.com/problems/average-of-levels-in-binary-tree/ +[力扣题目链接](https://leetcode-cn.com/problems/average-of-levels-in-binary-tree/) 给定一个非空二叉树, 返回一个由每层节点平均值组成的数组。 @@ -595,7 +619,7 @@ var rightSideView = function(root) { C++代码: -```C++ +```CPP class Solution { public: vector averageOfLevels(TreeNode* root) { @@ -624,32 +648,29 @@ python代码: ```python class Solution: + """二叉树层平均值迭代解法""" + def averageOfLevels(self, root: TreeNode) -> List[float]: + results = [] if not root: - return [] + return results - quene = deque([root]) - out_list = [] - - while quene: - in_list = [] - - for _ in range(len(quene)): - node = quene.popleft() - in_list.append(node.val) - if node.left: - quene.append(node.left) - if node.right: - quene.append(node.right) - - out_list.append(in_list) - - out_list = map(lambda x: sum(x) / len(x), out_list) - - return out_list + from collections import deque + que = deque([root]) -# 执行用时:56 ms, 在所有 Python3 提交中击败了81.48%的用户 -# 内存消耗:17 MB, 在所有 Python3 提交中击败了89.68%的用户 + while que: + size = len(que) + sum_ = 0 + for _ in range(size): + cur = que.popleft() + sum_ += cur.val + if cur.left: + que.append(cur.left) + if cur.right: + que.append(cur.right) + results.append(sum_ / size) + + return results ``` java: @@ -765,9 +786,9 @@ var averageOfLevels = function(root) { }; ``` -## 429.N叉树的层序遍历 +# 429.N叉树的层序遍历 -题目链接:https://leetcode-cn.com/problems/n-ary-tree-level-order-traversal/ +[力扣题目链接](https://leetcode-cn.com/problems/n-ary-tree-level-order-traversal/) 给定一个 N 叉树,返回其节点值的层序遍历。 (即从左到右,逐层遍历)。 @@ -790,7 +811,7 @@ var averageOfLevels = function(root) { C++代码: -```C++ +```CPP class Solution { public: vector> levelOrder(Node* root) { @@ -819,52 +840,28 @@ public: python代码: ```python - class Solution: + """N叉树的层序遍历迭代法""" + def levelOrder(self, root: 'Node') -> List[List[int]]: + results = [] if not root: - return [] + return results - quene = deque([root]) - out_list = [] - - while quene: - in_list = [] - - for _ in range(len(quene)): - node = quene.popleft() - in_list.append(node.val) - if node.children: - # 这个地方要用extend而不是append,我们看下面的例子: - # In [18]: alist=[] - # In [19]: alist.append([1,2,3]) - # In [20]: alist - # Out[20]: [[1, 2, 3]] - # In [21]: alist.extend([4,5,6]) - # In [22]: alist - # Out[22]: [[1, 2, 3], 4, 5, 6] - # 可以看到extend对要添加的list进行了一个解包操作 - # print(root.children),可以得到children是一个包含 - # 孩子节点地址的list,我们使用for遍历quene的时候, - # 希望quene是一个单层list,所以要用extend - # 使用extend的情况,如果print(quene),结果是 - # deque([<__main__.Node object at 0x7f60763ae0a0>]) - # deque([<__main__.Node object at 0x7f607636e6d0>, <__main__.Node object at 0x7f607636e130>, <__main__.Node object at 0x7f607636e310>]) - # deque([<__main__.Node object at 0x7f607636e880>, <__main__.Node object at 0x7f607636ef10>]) - # 可以看到是单层list - # 如果使用append,print(quene)的结果是 - # deque([<__main__.Node object at 0x7f18907530a0>]) - # deque([[<__main__.Node object at 0x7f18907136d0>, <__main__.Node object at 0x7f1890713130>, <__main__.Node object at 0x7f1890713310>]]) - # 可以看到是两层list,这样for的遍历就会报错 - - quene.extend(node.children) - - out_list.append(in_list) + from collections import deque + que = deque([root]) - return out_list - -# 执行用时:60 ms, 在所有 Python3 提交中击败了76.99%的用户 -# 内存消耗:16.5 MB, 在所有 Python3 提交中击败了89.19%的用户 + while que: + result = [] + for _ in range(len(que)): + cur = que.popleft() + result.append(cur.val) + # cur.children 是 Node 对象组成的列表,也可能为 None + if cur.children: + que.extend(cur.children) + results.append(result) + + return results ``` java: @@ -985,9 +982,9 @@ var levelOrder = function(root) { }; ``` -## 515.在每个树行中找最大值 +# 515.在每个树行中找最大值 -题目链接:https://leetcode-cn.com/problems/find-largest-value-in-each-tree-row/ +[力扣题目链接](https://leetcode-cn.com/problems/find-largest-value-in-each-tree-row/) 您需要在二叉树的每一行中找到最大的值。 @@ -999,7 +996,7 @@ var levelOrder = function(root) { C++代码: -```C++ +```CPP class Solution { public: vector largestValues(TreeNode* root) { @@ -1042,6 +1039,31 @@ class Solution: out_list.append(max(in_list)) return out_list ``` +java代码: + +```java +class Solution { + public List largestValues(TreeNode root) { + List retVal = new ArrayList(); + Queue tmpQueue = new LinkedList(); + if (root != null) tmpQueue.add(root); + + while (tmpQueue.size() != 0){ + int size = tmpQueue.size(); + List lvlVals = new ArrayList(); + for (int index = 0; index < size; index++){ + TreeNode node = tmpQueue.poll(); + lvlVals.add(node.val); + if (node.left != null) tmpQueue.add(node.left); + if (node.right != null) tmpQueue.add(node.right); + } + retVal.add(Collections.max(lvlVals)); + } + + return retVal; + } +} +``` go: @@ -1100,7 +1122,7 @@ var largestValues = function(root) { queue.push(root); while(root!==null&&queue.length){ //设置max初始值就是队列的第一个元素 - let max=queue[0]; + let max=queue[0].val; let length=queue.length; while(length--){ let node = queue.shift(); @@ -1115,9 +1137,9 @@ var largestValues = function(root) { }; ``` -## 116.填充每个节点的下一个右侧节点指针 +# 116.填充每个节点的下一个右侧节点指针 -题目链接:https://leetcode-cn.com/problems/populating-next-right-pointers-in-each-node/ +[力扣题目链接](https://leetcode-cn.com/problems/populating-next-right-pointers-in-each-node/) 给定一个完美二叉树,其所有叶子节点都在同一层,每个父节点都有两个子节点。二叉树定义如下: @@ -1143,7 +1165,7 @@ struct Node { C++代码: -```C++ +```CPP class Solution { public: Node* connect(Node* root) { @@ -1176,6 +1198,35 @@ public: }; ``` +java代码: + +```java +class Solution { + public Node connect(Node root) { + Queue tmpQueue = new LinkedList(); + if (root != null) tmpQueue.add(root); + + while (tmpQueue.size() != 0){ + int size = tmpQueue.size(); + + Node cur = tmpQueue.poll(); + if (cur.left != null) tmpQueue.add(cur.left); + if (cur.right != null) tmpQueue.add(cur.right); + + for (int index = 1; index < size; index++){ + Node next = tmpQueue.poll(); + if (next.left != null) tmpQueue.add(next.left); + if (next.right != null) tmpQueue.add(next.right); + + cur.next = next; + cur = next; + } + } + + return root; + } +} +``` python代码: @@ -1254,9 +1305,9 @@ func connect(root *Node) *Node { } ``` -## 117.填充每个节点的下一个右侧节点指针II +# 117.填充每个节点的下一个右侧节点指针II -题目地址:https://leetcode-cn.com/problems/populating-next-right-pointers-in-each-node-ii/ +[力扣题目链接](https://leetcode-cn.com/problems/populating-next-right-pointers-in-each-node-ii/) 思路: @@ -1264,7 +1315,7 @@ func connect(root *Node) *Node { C++代码: -```C++ +```CPP class Solution { public: Node* connect(Node* root) { @@ -1295,6 +1346,44 @@ public: } }; ``` + +Java 代码: + +```java +// 二叉树之层次遍历 +class Solution { + public Node connect(Node root) { + Queue queue = new LinkedList<>(); + if (root != null) { + queue.add(root); + } + while (!queue.isEmpty()) { + int size = queue.size(); + Node node = null; + Node nodePre = null; + + for (int i = 0; i < size; i++) { + if (i == 0) { + nodePre = queue.poll(); // 取出本层头一个节点 + node = nodePre; + } else { + node = queue.poll(); + nodePre.next = node; // 本层前一个节点 next 指向当前节点 + nodePre = nodePre.next; + } + if (node.left != null) { + queue.add(node.left); + } + if (node.right != null) { + queue.add(node.right); + } + } + nodePre.next = null; // 本层最后一个节点 next 指向 null + } + return root; + } +} +``` python代码: ```python @@ -1378,12 +1467,297 @@ func connect(root *Node) *Node { return root } ``` +# 104.二叉树的最大深度 -## 总结 +[力扣题目链接](https://leetcode-cn.com/problems/maximum-depth-of-binary-tree/) -二叉树的层序遍历,就是图论中的广度优先搜索在二叉树中的应用,需要借助队列来实现(此时是不是又发现队列的应用了)。 +给定一个二叉树,找出其最大深度。 -虽然不能一口气打十个,打八个也还行。 +二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。 + +说明: 叶子节点是指没有子节点的节点。 + +示例: + +给定二叉树 [3,9,20,null,null,15,7], + +![104. 二叉树的最大深度](https://img-blog.csdnimg.cn/20210203153031914.png) + +返回它的最大深度 3 。 + +思路: + +使用迭代法的话,使用层序遍历是最为合适的,因为最大的深度就是二叉树的层数,和层序遍历的方式极其吻合。 + +在二叉树中,一层一层的来遍历二叉树,记录一下遍历的层数就是二叉树的深度,如图所示: + +![层序遍历](https://img-blog.csdnimg.cn/20200810193056585.png) + +所以这道题的迭代法就是一道模板题,可以使用二叉树层序遍历的模板来解决的。 + +C++代码如下: + +```CPP +class Solution { +public: + int maxDepth(TreeNode* root) { + if (root == NULL) return 0; + int depth = 0; + queue que; + que.push(root); + while(!que.empty()) { + int size = que.size(); + depth++; // 记录深度 + for (int i = 0; i < size; i++) { + TreeNode* node = que.front(); + que.pop(); + if (node->left) que.push(node->left); + if (node->right) que.push(node->right); + } + } + return depth; + } +}; +``` + +Java: +```Java +class Solution { + public int maxDepth(TreeNode root) { + if (root == null) return 0; + Queue que = new LinkedList<>(); + que.offer(root); + int depth = 0; + while (!que.isEmpty()) + { + int len = que.size(); + while (len > 0) + { + TreeNode node = que.poll(); + if (node.left != null) que.offer(node.left); + if (node.right != null) que.offer(node.right); + len--; + } + depth++; + } + return depth; + } +} +``` + + +Python: +```python 3 +class Solution: + def maxDepth(self, root: TreeNode) -> int: + if root == None: + return 0 + + queue_ = [root] + result = [] + while queue_: + length = len(queue_) + sub = [] + for i in range(length): + cur = queue_.pop(0) + sub.append(cur.val) + #子节点入队列 + if cur.left: queue_.append(cur.left) + if cur.right: queue_.append(cur.right) + result.append(sub) + + + return len(result) +``` + +Go: + +```go +/** + * Definition for a binary tree node. + * type TreeNode struct { + * Val int + * Left *TreeNode + * Right *TreeNode + * } + */ +func maxDepth(root *TreeNode) int { + ans:=0 + if root==nil{ + return 0 + } + queue:=list.New() + queue.PushBack(root) + for queue.Len()>0{ + length:=queue.Len() + for i:=0;i que; + que.push(root); + while(!que.empty()) { + int size = que.size(); + depth++; // 记录最小深度 + for (int i = 0; i < size; i++) { + TreeNode* node = que.front(); + que.pop(); + if (node->left) que.push(node->left); + if (node->right) que.push(node->right); + if (!node->left && !node->right) { // 当左右孩子都为空的时候,说明是最低点的一层了,退出 + return depth; + } + } + } + return depth; + } +}; +``` + +Java: +```java +class Solution { + public int minDepth(TreeNode root){ + if (root == null) { + return 0; + } + Queue queue = new LinkedList<>(); + queue.offer(root); + int depth = 0; + while (!queue.isEmpty()){ + int size = queue.size(); + depth++; + TreeNode cur = null; + for (int i = 0; i < size; i++) { + cur = queue.poll(); + //如果当前节点的左右孩子都为空,直接返回最小深度 + if (cur.left == null && cur.right == null){ + return depth; + } + if (cur.left != null) queue.offer(cur.left); + if (cur.right != null) queue.offer(cur.right); + } + } + return depth; + } +} +``` + + + +Python 3: + +```python 3 +# Definition for a binary tree node. +# class TreeNode: +# def __init__(self, val=0, left=None, right=None): +# self.val = val +# self.left = left +# self.right = right +class Solution: + def minDepth(self, root: TreeNode) -> int: + if root == None: + return 0 + + #根节点的深度为1 + queue_ = [(root,1)] + while queue_: + cur, depth = queue_.pop(0) + + if cur.left == None and cur.right == None: + return depth + #先左子节点,由于左子节点没有孩子,则就是这一层了 + if cur.left: + queue_.append((cur.left,depth + 1)) + if cur.right: + queue_.append((cur.right,depth + 1)) + + return 0 +``` + +Go: + +```go +/** + * Definition for a binary tree node. + * type TreeNode struct { + * Val int + * Left *TreeNode + * Right *TreeNode + * } + */ +func minDepth(root *TreeNode) int { + ans:=0 + if root==nil{ + return 0 + } + queue:=list.New() + queue.PushBack(root) + for queue.Len()>0{ + length:=queue.Len() + for i:=0;i 二叉树的层序遍历(Javascript语言完全版) (迭代 + 递归) - -```js -/** - * 102. 二叉树的层序遍历 - * @param {TreeNode} root - * @return {number[][]} - */ - -// 迭代 - -var levelOrder = function(root) { - const queue = [], res = []; - root && queue.push(root); - while(len = queue.length) { - const val = []; - while(len--) { - const node = queue.shift(); - val.push(node.val); - node.left && queue.push(node.left); - node.right && queue.push(node.right); - } - res.push(val); - } - return res; -}; - -// 递归 -var levelOrder = function(root) { - const res = []; - function defs (root, i) { - if(!root) return; - if(!res[i]) res[i] = []; - res[i].push(root.val) - root.left && defs(root.left, i + 1); - root.right && defs(root.right, i + 1); - } - defs(root, 0); - return res; -}; - - -/** - * 107. 二叉树的层序遍历 II - * @param {TreeNode} root - * @return {number[][]} - */ - -// 迭代 - -var levelOrderBottom = function(root) { - const queue = [], res = []; - root && queue.push(root); - while(len = queue.length) { - const val = []; - while(len--) { - const node = queue.shift(); - val.push(node.val); - node.left && queue.push(node.left); - node.right && queue.push(node.right); - } - res.push(val); - } - return res.reverse() -}; - -// 递归 - -var levelOrderBottom = function(root) { - const res = []; - function defs (root, i) { - if(!root) return; - if(!res[i]) res[i] = []; - res[i].push(root.val); - root.left && defs(root.left, i + 1); - root.right && defs(root.right, i + 1); - } - defs(root, 0); - return res.reverse(); -}; - -/** - * 199. 二叉树的右视图 - * @param {TreeNode} root - * @return {number[]} - */ - -// 迭代 - -var rightSideView = function(root) { - const res = [], queue = []; - root && queue.push(root); - while(l = queue.length) { - while (l--) { - const {val, left, right} = queue.shift(); - !l && res.push(val); - left && queue.push(left); - right && queue.push(right); - } - } - return res; -}; - -// 递归 -var rightSideView = function(root) { - const res = []; - function defs(root, i) { - if(!root) return; - res[i] = root.val; - root.left && defs(root.left, i + 1); - root.right && defs(root.right, i + 1); - } - defs(root, 0); - return res; -}; - -/** - * 637. 二叉树的层平均值 - * @param {TreeNode} root - * @return {number[]} - */ - -// 迭代 -var averageOfLevels = function(root) { - const queue = [], res = []; - root && queue.push(root); - while(len = queue.length) { - let sum = 0, l = len; - while(l--) { - const {val, left, right} = queue.shift(); - sum += val; - left && queue.push(left); - right && queue.push(right); - } - res.push(sum/len); - } - return res; -}; - -// 递归 -var averageOfLevels = function(root) { - const resCount = [], res = []; - function defs(root, i) { - if(!root) return; - if(isNaN(res[i])) resCount[i] = res[i] = 0; - res[i] += root.val; - resCount[i]++; - root.left && defs(root.left, i + 1); - root.right && defs(root.right, i + 1); - } - defs(root, 0); - return res.map((val, i) => val / resCount[i]); -}; - -/** - * 515. 在每个树行中找最大值 - * @param {TreeNode} root - * @return {number[]} - */ - -// 迭代 -const MIN_G = Number.MIN_SAFE_INTEGER; -var largestValues = function(root) { - const queue = [], res = []; - root && queue.push(root); - while(len = queue.length) { - let max = MIN_G; - while(len--) { - const {val, left, right} = queue.shift(); - max = max > val ? max : val; - left && queue.push(left); - right && queue.push(right); - } - res.push(max); - } - return res; -}; - -// 递归 -var largestValues = function(root) { - const res = []; - function defs (root, i) { - if(!root) return; - if(isNaN(res[i])) res[i] = root.val; - res[i] = res[i] > root.val ? res[i] : root.val; - root.left && defs(root.left, i + 1); - root.right && defs(root.right, i + 1); - } - defs(root, 0); - return res; -}; - -/** - * 429. N 叉树的层序遍历 - * @param {Node|null} root - * @return {number[][]} - */ - -// 迭代 -var levelOrder = function(root) { - const queue = [], res = []; - root && queue.push(root); - while(len = queue.length) { - const vals = []; - while(len--) { - const {val, children} = queue.shift(); - vals.push(val); - for(const e of children) { - queue.push(e); - } - } - res.push(vals); - } - return res; -}; - -// 递归 - -var levelOrder = function(root) { - const res = []; - function defs (root, i) { - if(!root) return; - if(!res[i]) res[i] = []; - res[i].push(root.val); - for(const e of root.children) { - defs(e, i + 1); - } - } - defs(root, 0); - return res; -}; - -/** - * 116. 填充每个节点的下一个右侧节点指针 - * 117. 填充每个节点的下一个右侧节点指针 II - * @param {Node} root - * @return {Node} - */ - -// 迭代 -var connect = function(root) { - const queue = []; - root && queue.push(root); - while(len = queue.length) { - while(len--) { - const node1 = queue.shift(), - node2 = len ? queue[0] : null; - node1.next = node2; - node1.left && queue.push(node1.left); - node1.right && queue.push(node1.right); - } - } - return root; -}; - -// 递归 -var connect = function(root) { - const res = []; - function defs (root, i) { - if(!root) return; - if(res[i]) res[i].next = root; - res[i] = root; - root.left && defs(root.left, i + 1); - root.right && defs(root.right, i + 1); - } - defs(root, 0); - return root; -}; -``` - ----------------------- * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0104.二叉树的最大深度.md b/problems/0104.二叉树的最大深度.md index 218a966c..cde3d460 100644 --- a/problems/0104.二叉树的最大深度.md +++ b/problems/0104.二叉树的最大深度.md @@ -8,12 +8,13 @@ 看完本篇可以一起做了如下两道题目: + * 104.二叉树的最大深度 -* 559.N叉树的最大深度 +* 559.n叉树的最大深度 -## 104.二叉树的最大深度 +# 104.二叉树的最大深度 -题目地址:https://leetcode-cn.com/problems/maximum-depth-of-binary-tree/ +[力扣题目链接](https://leetcode-cn.com/problems/maximum-depth-of-binary-tree/) 给定一个二叉树,找出其最大深度。 @@ -28,7 +29,7 @@ 返回它的最大深度 3 。 -### 递归法 +## 递归法 本题可以使用前序(中左右),也可以使用后序遍历(左右中),使用前序求的就是深度,使用后序求的是高度。 @@ -41,53 +42,53 @@ 1. 确定递归函数的参数和返回值:参数就是传入树的根节点,返回就返回这棵树的深度,所以返回值为int类型。 代码如下: -``` -int getDepth(TreeNode* node) +```c++ +int getdepth(treenode* node) ``` 2. 确定终止条件:如果为空节点的话,就返回0,表示高度为0。 代码如下: -``` -if (node == NULL) return 0; +```c++ +if (node == null) return 0; ``` 3. 确定单层递归的逻辑:先求它的左子树的深度,再求的右子树的深度,最后取左右深度最大的数值 再+1 (加1是因为算上当前中间节点)就是目前节点为根节点的树的深度。 代码如下: -``` -int leftDepth = getDepth(node->left); // 左 -int rightDepth = getDepth(node->right); // 右 -int depth = 1 + max(leftDepth, rightDepth); // 中 +```c++ +int leftdepth = getdepth(node->left); // 左 +int rightdepth = getdepth(node->right); // 右 +int depth = 1 + max(leftdepth, rightdepth); // 中 return depth; ``` -所以整体C++代码如下: +所以整体c++代码如下: -```C++ -class Solution { +```c++ +class solution { public: - int getDepth(TreeNode* node) { - if (node == NULL) return 0; - int leftDepth = getDepth(node->left); // 左 - int rightDepth = getDepth(node->right); // 右 - int depth = 1 + max(leftDepth, rightDepth); // 中 + int getdepth(treenode* node) { + if (node == null) return 0; + int leftdepth = getdepth(node->left); // 左 + int rightdepth = getdepth(node->right); // 右 + int depth = 1 + max(leftdepth, rightdepth); // 中 return depth; } - int maxDepth(TreeNode* root) { - return getDepth(root); + int maxdepth(treenode* root) { + return getdepth(root); } }; ``` -代码精简之后C++代码如下: -```C++ -class Solution { +代码精简之后c++代码如下: +```c++ +class solution { public: - int maxDepth(TreeNode* root) { - if (root == NULL) return 0; - return 1 + max(maxDepth(root->left), maxDepth(root->right)); + int maxdepth(treenode* root) { + if (root == null) return 0; + return 1 + max(maxdepth(root->left), maxdepth(root->right)); } }; @@ -98,65 +99,65 @@ public: 本题当然也可以使用前序,代码如下:(**充分表现出求深度回溯的过程**) -```C++ -class Solution { +```c++ +class solution { public: int result; - void getDepth(TreeNode* node, int depth) { + void getdepth(treenode* node, int depth) { result = depth > result ? depth : result; // 中 - if (node->left == NULL && node->right == NULL) return ; + if (node->left == null && node->right == null) return ; if (node->left) { // 左 depth++; // 深度+1 - getDepth(node->left, depth); + getdepth(node->left, depth); depth--; // 回溯,深度-1 } if (node->right) { // 右 depth++; // 深度+1 - getDepth(node->right, depth); + getdepth(node->right, depth); depth--; // 回溯,深度-1 } return ; } - int maxDepth(TreeNode* root) { + int maxdepth(treenode* root) { result = 0; if (root == 0) return result; - getDepth(root, 1); - return result; - } -}; -``` - -**可以看出使用了前序(中左右)的遍历顺序,这才是真正求深度的逻辑!** - -注意以上代码是为了把细节体现出来,简化一下代码如下: - -```C++ -class Solution { -public: - int result; - void getDepth(TreeNode* node, int depth) { - result = depth > result ? depth : result; // 中 - if (node->left == NULL && node->right == NULL) return ; - if (node->left) { // 左 - getDepth(node->left, depth + 1); - } - if (node->right) { // 右 - getDepth(node->right, depth + 1); - } - return ; - } - int maxDepth(TreeNode* root) { - result = 0; - if (root == 0) return result; - getDepth(root, 1); + getdepth(root, 1); return result; } }; ``` -### 迭代法 +**可以看出使用了前序(中左右)的遍历顺序,这才是真正求深度的逻辑!** + +注意以上代码是为了把细节体现出来,简化一下代码如下: + +```c++ +class solution { +public: + int result; + void getdepth(treenode* node, int depth) { + result = depth > result ? depth : result; // 中 + if (node->left == null && node->right == null) return ; + if (node->left) { // 左 + getdepth(node->left, depth + 1); + } + if (node->right) { // 右 + getdepth(node->right, depth + 1); + } + return ; + } + int maxdepth(treenode* root) { + result = 0; + if (root == 0) return result; + getdepth(root, 1); + return result; + } +}; +``` + +## 迭代法 使用迭代法的话,使用层序遍历是最为合适的,因为最大的深度就是二叉树的层数,和层序遍历的方式极其吻合。 @@ -166,23 +167,23 @@ public: 所以这道题的迭代法就是一道模板题,可以使用二叉树层序遍历的模板来解决的。 -如果对层序遍历还不清楚的话,可以看这篇:[二叉树:层序遍历登场!](https://mp.weixin.qq.com/s/Gb3BjakIKGNpup2jYtTzog) +如果对层序遍历还不清楚的话,可以看这篇:[二叉树:层序遍历登场!](https://programmercarl.com/0102.二叉树的层序遍历.html) -C++代码如下: +c++代码如下: -```C++ -class Solution { +```c++ +class solution { public: - int maxDepth(TreeNode* root) { - if (root == NULL) return 0; + int maxdepth(treenode* root) { + if (root == null) return 0; int depth = 0; - queue que; + queue que; que.push(root); while(!que.empty()) { int size = que.size(); depth++; // 记录深度 for (int i = 0; i < size; i++) { - TreeNode* node = que.front(); + treenode* node = que.front(); que.pop(); if (node->left) que.push(node->left); if (node->right) que.push(node->right); @@ -193,19 +194,19 @@ public: }; ``` -那么我们可以顺便解决一下N叉树的最大深度问题 +那么我们可以顺便解决一下n叉树的最大深度问题 -## 559.N叉树的最大深度 +# 559.n叉树的最大深度 -题目地址:https://leetcode-cn.com/problems/maximum-depth-of-n-ary-tree/ +[力扣题目链接](https://leetcode-cn.com/problems/maximum-depth-of-n-ary-tree/) -给定一个 N 叉树,找到其最大深度。 +给定一个 n 叉树,找到其最大深度。 最大深度是指从根节点到最远叶子节点的最长路径上的节点总数。 例如,给定一个 3叉树 : -![559.N叉树的最大深度](https://img-blog.csdnimg.cn/2021020315313214.png) +![559.n叉树的最大深度](https://img-blog.csdnimg.cn/2021020315313214.png) 我们应返回其最大深度,3。 @@ -213,39 +214,39 @@ public: 依然可以提供递归法和迭代法,来解决这个问题,思路是和二叉树思路一样的,直接给出代码如下: -### 递归法 +## 递归法 -C++代码: +c++代码: -```C++ -class Solution { +```c++ +class solution { public: - int maxDepth(Node* root) { + int maxdepth(node* root) { if (root == 0) return 0; int depth = 0; for (int i = 0; i < root->children.size(); i++) { - depth = max (depth, maxDepth(root->children[i])); + depth = max (depth, maxdepth(root->children[i])); } return depth + 1; } }; ``` -### 迭代法 +## 迭代法 依然是层序遍历,代码如下: -```C++ -class Solution { +```c++ +class solution { public: - int maxDepth(Node* root) { - queue que; - if (root != NULL) que.push(root); + int maxdepth(node* root) { + queue que; + if (root != null) que.push(root); int depth = 0; while (!que.empty()) { int size = que.size(); depth++; // 记录深度 for (int i = 0; i < size; i++) { - Node* node = que.front(); + node* node = que.front(); que.pop(); for (int j = 0; j < node->children.size(); j++) { if (node->children[j]) que.push(node->children[j]); @@ -257,45 +258,46 @@ public: }; ``` -## 其他语言版本 +# 其他语言版本 +## java -Java: +### 104.二叉树的最大深度 -```Java -class Solution { +```java +class solution { /** * 递归法 */ - public int maxDepth(TreeNode root) { + public int maxdepth(treenode root) { if (root == null) { return 0; } - int leftDepth = maxDepth(root.left); - int rightDepth = maxDepth(root.right); - return Math.max(leftDepth, rightDepth) + 1; + int leftdepth = maxdepth(root.left); + int rightdepth = maxdepth(root.right); + return math.max(leftdepth, rightdepth) + 1; } } ``` -```Java -class Solution { +```java +class solution { /** * 迭代法,使用层序遍历 */ - public int maxDepth(TreeNode root) { + public int maxdepth(treenode root) { if(root == null) { return 0; } - Deque deque = new LinkedList<>(); + deque deque = new linkedlist<>(); deque.offer(root); int depth = 0; - while (!deque.isEmpty()) { + while (!deque.isempty()) { int size = deque.size(); depth++; for (int i = 0; i < size; i++) { - TreeNode poll = deque.poll(); + treenode poll = deque.poll(); if (poll.left != null) { deque.offer(poll.left); } @@ -309,37 +311,68 @@ class Solution { } ``` -Python: +### 559.n叉树的最大深度 +```java +class solution { + /** + * 迭代法,使用层序遍历 + */ + public int maxDepth(Node root) { + if (root == null) return 0; + int depth = 0; + Queue que = new LinkedList<>(); + que.offer(root); + while (!que.isEmpty()) + { + depth ++; + int len = que.size(); + while (len > 0) + { + Node node = que.poll(); + for (int i = 0; i < node.children.size(); i++) + if (node.children.get(i) != null) + que.offer(node.children.get(i)); + len--; + } + } + return depth; + } +} +``` -104.二叉树的最大深度 -> 递归法: +## python + +### 104.二叉树的最大深度 + +递归法: ```python -class Solution: - def maxDepth(self, root: TreeNode) -> int: - return self.getDepth(root) +class solution: + def maxdepth(self, root: treenode) -> int: + return self.getdepth(root) - def getDepth(self, node): + def getdepth(self, node): if not node: return 0 - leftDepth = self.getDepth(node.left) #左 - rightDepth = self.getDepth(node.right) #右 - depth = 1 + max(leftDepth, rightDepth) #中 + leftdepth = self.getdepth(node.left) #左 + rightdepth = self.getdepth(node.right) #右 + depth = 1 + max(leftdepth, rightdepth) #中 return depth ``` -> 递归法;精简代码 + +递归法:精简代码 ```python -class Solution: - def maxDepth(self, root: TreeNode) -> int: +class solution: + def maxdepth(self, root: treenode) -> int: if not root: return 0 - return 1 + max(self.maxDepth(root.left), self.maxDepth(root.right)) + return 1 + max(self.maxdepth(root.left), self.maxdepth(root.right)) ``` -> 迭代法: +迭代法: ```python import collections -class Solution: - def maxDepth(self, root: TreeNode) -> int: +class solution: + def maxdepth(self, root: treenode) -> int: if not root: return 0 depth = 0 #记录深度 @@ -357,24 +390,25 @@ class Solution: return depth ``` -559.N叉树的最大深度 -> 递归法: +### 559.n叉树的最大深度 + +递归法: ```python -class Solution: - def maxDepth(self, root: 'Node') -> int: +class solution: + def maxdepth(self, root: 'node') -> int: if not root: return 0 depth = 0 for i in range(len(root.children)): - depth = max(depth, self.maxDepth(root.children[i])) + depth = max(depth, self.maxdepth(root.children[i])) return depth + 1 ``` -> 迭代法: +迭代法: ```python import collections -class Solution: - def maxDepth(self, root: 'Node') -> int: +class solution: + def maxdepth(self, root: 'node') -> int: queue = collections.deque() if root: queue.append(root) @@ -390,10 +424,10 @@ class Solution: return depth ``` -> 使用栈来模拟后序遍历依然可以 +使用栈来模拟后序遍历依然可以 ```python -class Solution: - def maxDepth(self, root: 'Node') -> int: +class solution: + def maxdepth(self, root: 'node') -> int: st = [] if root: st.append(root) @@ -401,9 +435,9 @@ class Solution: result = 0 while st: node = st.pop() - if node != None: + if node != none: st.append(node) #中 - st.append(None) + st.append(none) depth += 1 for i in range(len(node.children)): #处理孩子 if node.children[i]: @@ -417,15 +451,15 @@ class Solution: ``` -Go: +## go ```go /** - * Definition for a binary tree node. - * type TreeNode struct { - * Val int - * Left *TreeNode - * Right *TreeNode + * definition for a binary tree node. + * type treenode struct { + * val int + * left *treenode + * right *treenode * } */ func max (a, b int) int { @@ -435,28 +469,28 @@ func max (a, b int) int { return b; } // 递归 -func maxDepth(root *TreeNode) int { +func maxdepth(root *treenode) int { if root == nil { return 0; } - return max(maxDepth(root.Left), maxDepth(root.Right)) + 1; + return max(maxdepth(root.left), maxdepth(root.right)) + 1; } // 遍历 -func maxDepth(root *TreeNode) int { +func maxdepth(root *treenode) int { levl := 0; - queue := make([]*TreeNode, 0); + queue := make([]*treenode, 0); if root != nil { queue = append(queue, root); } for l := len(queue); l > 0; { for ;l > 0;l-- { node := queue[0]; - if node.Left != nil { - queue = append(queue, node.Left); + if node.left != nil { + queue = append(queue, node.left); } - if node.Right != nil { - queue = append(queue, node.Right); + if node.right != nil { + queue = append(queue, node.right); } queue = queue[1:]; } @@ -469,49 +503,82 @@ func maxDepth(root *TreeNode) int { ``` -JavaScript +## javascript + ```javascript -var maxDepth = function(root) { +var maxdepth = function(root) { if (!root) return root - return 1 + Math.max(maxDepth(root.left), maxDepth(root.right)) + return 1 + math.max(maxdepth(root.left), maxdepth(root.right)) }; ``` + 二叉树最大深度递归遍历 ```javascript -var maxDepth = function(root) { +var maxdepth = function(root) { //使用递归的方法 递归三部曲 //1. 确定递归函数的参数和返回值 - const getDepth=function(node){ + const getdepth=function(node){ //2. 确定终止条件 if(node===null){ return 0; } //3. 确定单层逻辑 - let leftDepth=getDepth(node.left); - let rightDepth=getDepth(node.right); - let depth=1+Math.max(leftDepth,rightDepth); + let leftdepth=getdepth(node.left); + let rightdepth=getdepth(node.right); + let depth=1+math.max(leftdepth,rightdepth); return depth; } - return getDepth(root); + return getdepth(root); }; ``` + 二叉树最大深度层级遍历 ```javascript var maxDepth = function(root) { - //使用递归的方法 递归三部曲 - //1. 确定递归函数的参数和返回值 - const getDepth=function(node){ - //2. 确定终止条件 - if(node===null){ - return 0; + if(!root) return 0 + let count = 0 + const queue = [root] + while(queue.length) { + let size = queue.length + /* 层数+1 */ + count++ + while(size--) { + let node = queue.shift(); + node.left && queue.push(node.left); + node.right && queue.push(node.right); } - //3. 确定单层逻辑 - let leftDepth=getDepth(node.left); - let rightDepth=getDepth(node.right); - let depth=1+Math.max(leftDepth,rightDepth); - return depth; } - return getDepth(root); + return count +}; +``` + +N叉树的最大深度 递归写法 +```js +var maxDepth = function(root) { + if(!root) return 0 + let depth = 0 + for(let node of root.children) { + depth = Math.max(depth, maxDepth(node)) + } + return depth + 1 +} +``` + +N叉树的最大深度 层序遍历 +```js +var maxDepth = function(root) { + if(!root) return 0 + let count = 0 + let queue = [root] + while(queue.length) { + let size = queue.length + count++ + while(size--) { + let node = queue.shift() + node && (queue = [...queue, ...node.children]) + } + } + return count }; ``` @@ -519,4 +586,4 @@ var maxDepth = function(root) { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0106.从中序与后序遍历序列构造二叉树.md b/problems/0106.从中序与后序遍历序列构造二叉树.md index 4c5a70a0..a9639d8f 100644 --- a/problems/0106.从中序与后序遍历序列构造二叉树.md +++ b/problems/0106.从中序与后序遍历序列构造二叉树.md @@ -12,9 +12,9 @@ * 106.从中序与后序遍历序列构造二叉树 * 105.从前序与中序遍历序列构造二叉树 -## 106.从中序与后序遍历序列构造二叉树 +# 106.从中序与后序遍历序列构造二叉树 -题目地址:https://leetcode-cn.com/problems/construct-binary-tree-from-inorder-and-postorder-traversal/ +[力扣题目链接](https://leetcode-cn.com/problems/construct-binary-tree-from-inorder-and-postorder-traversal/) 根据一棵树的中序遍历与后序遍历构造二叉树。 @@ -29,7 +29,7 @@ ![106. 从中序与后序遍历序列构造二叉树1](https://img-blog.csdnimg.cn/20210203154316774.png) -### 思路 +## 思路 首先回忆一下如何根据两个顺序构造一个唯一的二叉树,相信理论知识大家应该都清楚,就是以 后序数组的最后一个元素为切割点,先切中序数组,根据中序数组,反过来在切后序数组。一层一层切下去,每次后序数组最后一个元素就是节点元素。 @@ -59,7 +59,7 @@ 不难写出如下代码:(先把框架写出来) -```C++ +```CPP TreeNode* traversal (vector& inorder, vector& postorder) { // 第一步 @@ -95,7 +95,7 @@ TreeNode* traversal (vector& inorder, vector& postorder) { **在切割的过程中会产生四个区间,把握不好不变量的话,一会左闭右开,一会左闭又闭,必然乱套!** -我在[数组:每次遇到二分法,都是一看就会,一写就废](https://mp.weixin.qq.com/s/fCf5QbPDtE6SSlZ1yh_q8Q)和[数组:这个循环可以转懵很多人!](https://mp.weixin.qq.com/s/KTPhaeqxbMK9CxHUUgFDmg)中都强调过循环不变量的重要性,在二分查找以及螺旋矩阵的求解中,坚持循环不变量非常重要,本题也是。 +我在[数组:每次遇到二分法,都是一看就会,一写就废](https://programmercarl.com/0035.搜索插入位置.html)和[数组:这个循环可以转懵很多人!](https://programmercarl.com/0059.螺旋矩阵II.html)中都强调过循环不变量的重要性,在二分查找以及螺旋矩阵的求解中,坚持循环不变量非常重要,本题也是。 首先要切割中序数组,为什么先切割中序数组呢? @@ -105,7 +105,7 @@ TreeNode* traversal (vector& inorder, vector& postorder) { 中序数组相对比较好切,找到切割点(后序数组的最后一个元素)在中序数组的位置,然后切割,如下代码中我坚持左闭右开的原则: -``` +```C++ // 找到中序遍历的切割点 int delimiterIndex; for (delimiterIndex = 0; delimiterIndex < inorder.size(); delimiterIndex++) { @@ -155,7 +155,7 @@ root->right = traversal(rightInorder, rightPostorder); ### C++完整代码 -```C++ +```CPP class Solution { private: TreeNode* traversal (vector& inorder, vector& postorder) { @@ -209,7 +209,7 @@ public: 加了日志的代码如下:(加了日志的代码不要在leetcode上提交,容易超时) -```C++ +```CPP class Solution { private: TreeNode* traversal (vector& inorder, vector& postorder) { @@ -277,7 +277,7 @@ public: 下面给出用下表索引写出的代码版本:(思路是一样的,只不过不用重复定义vector了,每次用下表索引来分割) ### C++优化版本 -```C++ +```CPP class Solution { private: // 中序区间:[inorderBegin, inorderEnd),后序区间[postorderBegin, postorderEnd) @@ -325,7 +325,7 @@ public: 那么这个版本写出来依然要打日志进行调试,打日志的版本如下:(**该版本不要在leetcode上提交,容易超时**) -```C++ +```CPP class Solution { private: TreeNode* traversal (vector& inorder, int inorderBegin, int inorderEnd, vector& postorder, int postorderBegin, int postorderEnd) { @@ -394,9 +394,9 @@ public: }; ``` -## 105.从前序与中序遍历序列构造二叉树 +# 105.从前序与中序遍历序列构造二叉树 -题目地址:https://leetcode-cn.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal/ +[力扣题目链接](https://leetcode-cn.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal/) 根据一棵树的前序遍历与中序遍历构造二叉树。 @@ -411,7 +411,7 @@ public: ![105. 从前序与中序遍历序列构造二叉树](https://img-blog.csdnimg.cn/20210203154626672.png) -### 思路 +## 思路 本题和106是一样的道理。 @@ -419,7 +419,7 @@ public: 带日志的版本C++代码如下: (**带日志的版本仅用于调试,不要在leetcode上提交,会超时**) -```C++ +```CPP class Solution { private: TreeNode* traversal (vector& inorder, int inorderBegin, int inorderEnd, vector& preorder, int preorderBegin, int preorderEnd) { @@ -493,7 +493,7 @@ public: 105.从前序与中序遍历序列构造二叉树,最后版本,C++代码: -```C++ +```CPP class Solution { private: TreeNode* traversal (vector& inorder, int inorderBegin, int inorderEnd, vector& preorder, int preorderBegin, int preorderEnd) { @@ -540,7 +540,7 @@ public: }; ``` -## 思考题 +# 思考题 前序和中序可以唯一确定一颗二叉树。 @@ -562,7 +562,7 @@ tree2 的前序遍历是[1 2 3], 后序遍历是[3 2 1]。 所以前序和后序不能唯一确定一颗二叉树! -## 总结 +# 总结 之前我们讲的二叉树题目都是各种遍历二叉树,这次开始构造二叉树了,思路其实比较简单,但是真正代码实现出来并不容易。 @@ -578,9 +578,9 @@ tree2 的前序遍历是[1 2 3], 后序遍历是[3 2 1]。 -## 其他语言版本 +# 其他语言版本 -Java: +## Java 106.从中序与后序遍历序列构造二叉树 @@ -607,6 +607,7 @@ class Solution { for (int i = inLeft; i < inRight; i++) { if (inorder[i] == rootVal) { rootIndex = i; + break; } } // 根据rootIndex划分左右子树 @@ -653,47 +654,75 @@ class Solution { } ``` -Python: +## Python + 105.从前序与中序遍历序列构造二叉树 ```python -# Definition for a binary tree node. -# class TreeNode: -# def __init__(self, val=0, left=None, right=None): -# self.val = val -# self.left = left -# self.right = right -//递归法 class Solution: def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode: - if not preorder: return None //特殊情况 - root = TreeNode(preorder[0]) //新建父节点 - p=inorder.index(preorder[0]) //找到父节点在中序遍历的位置(因为没有重复的元素,才可以这样找) - root.left = self.buildTree(preorder[1:p+1],inorder[:p]) //注意左节点时分割中序数组和前续数组的开闭环 - root.right = self.buildTree(preorder[p+1:],inorder[p+1:]) //分割中序数组和前续数组 - return root -``` + # 第一步: 特殊情况讨论: 树为空. 或者说是递归终止条件 + if not preorder: + return None + + # 第二步: 前序遍历的第一个就是当前的中间节点. + root_val = preorder[0] + root = TreeNode(root_val) + + # 第三步: 找切割点. + separator_idx = inorder.index(root_val) + + # 第四步: 切割inorder数组. 得到inorder数组的左,右半边. + inorder_left = inorder[:separator_idx] + inorder_right = inorder[separator_idx + 1:] + + # 第五步: 切割preorder数组. 得到preorder数组的左,右半边. + # ⭐️ 重点1: 中序数组大小一定跟前序数组大小是相同的. + preorder_left = preorder[1:1 + len(inorder_left)] + preorder_right = preorder[1 + len(inorder_left):] + + # 第六步: 递归 + root.left = self.buildTree(preorder_left, inorder_left) + root.right = self.buildTree(preorder_right, inorder_right) + + return root +``` + 106.从中序与后序遍历序列构造二叉树 ```python -# Definition for a binary tree node. -# class TreeNode: -# def __init__(self, val=0, left=None, right=None): -# self.val = val -# self.left = left -# self.right = right -//递归法 class Solution: def buildTree(self, inorder: List[int], postorder: List[int]) -> TreeNode: - if not postorder: return None //特殊情况 - root = TreeNode(postorder[-1]) //新建父节点 - p=inorder.index(postorder[-1]) //找到父节点在中序遍历的位置*因为没有重复的元素,才可以这样找 - root.left = self.buildTree(inorder[:p],postorder[:p]) //分割中序数组和后续数组 - root.right = self.buildTree(inorder[p+1:],postorder[p:-1]) //注意右节点时分割中序数组和后续数组的开闭环 - return root -``` -Go: -> 106 从中序与后序遍历序列构造二叉树 + # 第一步: 特殊情况讨论: 树为空. (递归终止条件) + if not postorder: + return None + + # 第二步: 后序遍历的最后一个就是当前的中间节点. + root_val = postorder[-1] + root = TreeNode(root_val) + + # 第三步: 找切割点. + separator_idx = inorder.index(root_val) + + # 第四步: 切割inorder数组. 得到inorder数组的左,右半边. + inorder_left = inorder[:separator_idx] + inorder_right = inorder[separator_idx + 1:] + + # 第五步: 切割postorder数组. 得到postorder数组的左,右半边. + # ⭐️ 重点1: 中序数组大小一定跟后序数组大小是相同的. + postorder_left = postorder[:len(inorder_left)] + postorder_right = postorder[len(inorder_left): len(postorder) - 1] + + # 第六步: 递归 + root.left = self.buildTree(inorder_left, postorder_left) + root.right = self.buildTree(inorder_right, postorder_right) + + return root +``` + +## Go + +106 从中序与后序遍历序列构造二叉树 ```go /** @@ -726,7 +755,7 @@ func findRootIndex(inorder []int,target int) (index int){ } ``` -> 105 从前序与中序遍历序列构造二叉树 +105 从前序与中序遍历序列构造二叉树 ```go /** @@ -759,7 +788,8 @@ func findRootIndex(target int,inorder []int) int{ -JavaScript +## JavaScript + ```javascript var buildTree = function(inorder, postorder) { if (!postorder.length) return null @@ -793,4 +823,4 @@ var buildTree = function(preorder, inorder) { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0108.将有序数组转换为二叉搜索树.md b/problems/0108.将有序数组转换为二叉搜索树.md index 8ec5f3dc..f7bff27d 100644 --- a/problems/0108.将有序数组转换为二叉搜索树.md +++ b/problems/0108.将有序数组转换为二叉搜索树.md @@ -9,9 +9,9 @@ > 构造二叉搜索树,一不小心就平衡了 -## 108.将有序数组转换为二叉搜索树 +# 108.将有序数组转换为二叉搜索树 -题目链接:https://leetcode-cn.com/problems/convert-sorted-array-to-binary-search-tree/ +[力扣题目链接](https://leetcode-cn.com/problems/convert-sorted-array-to-binary-search-tree/) 将一个按照升序排列的有序数组,转换为一棵高度平衡二叉搜索树。 @@ -21,14 +21,14 @@ ![108.将有序数组转换为二叉搜索树](https://img-blog.csdnimg.cn/20201022164420763.png) -## 思路 +# 思路 做这道题目之前大家可以了解一下这几道: -* [106.从中序与后序遍历序列构造二叉树](https://mp.weixin.qq.com/s/7r66ap2s-shvVvlZxo59xg) -* [654.最大二叉树](https://mp.weixin.qq.com/s/1iWJV6Aov23A7xCF4nV88w)中其实已经讲过了,如果根据数组构造一颗二叉树。 -* [701.二叉搜索树中的插入操作](https://mp.weixin.qq.com/s/lwKkLQcfbCNX2W-5SOeZEA) -* [450.删除二叉搜索树中的节点](https://mp.weixin.qq.com/s/-p-Txvch1FFk3ygKLjPAKw) +* [106.从中序与后序遍历序列构造二叉树](https://programmercarl.com/0106.从中序与后序遍历序列构造二叉树.html) +* [654.最大二叉树](https://programmercarl.com/0654.最大二叉树.html)中其实已经讲过了,如果根据数组构造一颗二叉树。 +* [701.二叉搜索树中的插入操作](https://programmercarl.com/0701.二叉搜索树中的插入操作.html) +* [450.删除二叉搜索树中的节点](https://programmercarl.com/0450.删除二叉搜索树中的节点.html) 进入正题: @@ -38,11 +38,11 @@ 其实这里不用强调平衡二叉搜索树,数组构造二叉树,构成平衡树是自然而然的事情,因为大家默认都是从数组中间位置取值作为节点元素,一般不会随机取,**所以想构成不平衡的二叉树是自找麻烦**。 -在[二叉树:构造二叉树登场!](https://mp.weixin.qq.com/s/7r66ap2s-shvVvlZxo59xg)和[二叉树:构造一棵最大的二叉树](https://mp.weixin.qq.com/s/1iWJV6Aov23A7xCF4nV88w)中其实已经讲过了,如果根据数组构造一颗二叉树。 +在[二叉树:构造二叉树登场!](https://programmercarl.com/0106.从中序与后序遍历序列构造二叉树.html)和[二叉树:构造一棵最大的二叉树](https://programmercarl.com/0654.最大二叉树.html)中其实已经讲过了,如果根据数组构造一颗二叉树。 **本质就是寻找分割点,分割点作为当前节点,然后递归左区间和右区间**。 -本题其实要比[二叉树:构造二叉树登场!](https://mp.weixin.qq.com/s/7r66ap2s-shvVvlZxo59xg) 和 [二叉树:构造一棵最大的二叉树](https://mp.weixin.qq.com/s/1iWJV6Aov23A7xCF4nV88w)简单一些,因为有序数组构造二叉搜索树,寻找分割点就比较容易了。 +本题其实要比[二叉树:构造二叉树登场!](https://programmercarl.com/0106.从中序与后序遍历序列构造二叉树.html) 和 [二叉树:构造一棵最大的二叉树](https://programmercarl.com/0654.最大二叉树.html)简单一些,因为有序数组构造二叉搜索树,寻找分割点就比较容易了。 分割点就是数组中间位置的节点。 @@ -68,11 +68,11 @@ 删除二叉树节点,增加二叉树节点,都是用递归函数的返回值来完成,这样是比较方便的。 -相信大家如果仔细看了[二叉树:搜索树中的插入操作](https://mp.weixin.qq.com/s/lwKkLQcfbCNX2W-5SOeZEA)和[二叉树:搜索树中的删除操作](https://mp.weixin.qq.com/s/-p-Txvch1FFk3ygKLjPAKw),一定会对递归函数返回值的作用深有感触。 +相信大家如果仔细看了[二叉树:搜索树中的插入操作](https://programmercarl.com/0701.二叉搜索树中的插入操作.html)和[二叉树:搜索树中的删除操作](https://programmercarl.com/0450.删除二叉搜索树中的节点.html),一定会对递归函数返回值的作用深有感触。 那么本题要构造二叉树,依然用递归函数的返回值来构造中节点的左右孩子。 -再来看参数,首先是传入数组,然后就是左下表left和右下表right,我们在[二叉树:构造二叉树登场!](https://mp.weixin.qq.com/s/7r66ap2s-shvVvlZxo59xg)中提过,在构造二叉树的时候尽量不要重新定义左右区间数组,而是用下表来操作原数组。 +再来看参数,首先是传入数组,然后就是左下表left和右下表right,我们在[二叉树:构造二叉树登场!](https://programmercarl.com/0106.从中序与后序遍历序列构造二叉树.html)中提过,在构造二叉树的时候尽量不要重新定义左右区间数组,而是用下表来操作原数组。 所以代码如下: @@ -83,7 +83,7 @@ TreeNode* traversal(vector& nums, int left, int right) 这里注意,**我这里定义的是左闭右闭区间,在不断分割的过程中,也会坚持左闭右闭的区间,这又涉及到我们讲过的循环不变量**。 -在[二叉树:构造二叉树登场!](https://mp.weixin.qq.com/s/7r66ap2s-shvVvlZxo59xg),[35.搜索插入位置](https://mp.weixin.qq.com/s/fCf5QbPDtE6SSlZ1yh_q8Q) 和[59.螺旋矩阵II](https://mp.weixin.qq.com/s/KTPhaeqxbMK9CxHUUgFDmg)都详细讲过循环不变量。 +在[二叉树:构造二叉树登场!](https://programmercarl.com/0106.从中序与后序遍历序列构造二叉树.html),[35.搜索插入位置](https://programmercarl.com/0035.搜索插入位置.html) 和[59.螺旋矩阵II](https://programmercarl.com/0059.螺旋矩阵II.html)都详细讲过循环不变量。 * 确定递归终止条件 @@ -98,7 +98,7 @@ if (left > right) return nullptr; * 确定单层递归的逻辑 -首先取数组中间元素的位置,不难写出`int mid = (left + right) / 2;`,**这么写其实有一个问题,就是数值越界,例如left和right都是最大int,这么操作就越界了,在[二分法](https://mp.weixin.qq.com/s/fCf5QbPDtE6SSlZ1yh_q8Q)中尤其需要注意!** +首先取数组中间元素的位置,不难写出`int mid = (left + right) / 2;`,**这么写其实有一个问题,就是数值越界,例如left和right都是最大int,这么操作就越界了,在[二分法](https://programmercarl.com/0035.搜索插入位置.html)中尤其需要注意!** 所以可以这么写:`int mid = left + ((right - left) / 2);` @@ -122,7 +122,7 @@ return root; * 递归整体代码如下: -```C++ +```CPP class Solution { private: TreeNode* traversal(vector& nums, int left, int right) { @@ -150,7 +150,7 @@ public: 模拟的就是不断分割的过程,C++代码如下:(我已经详细注释) -```C++ +```CPP class Solution { public: TreeNode* sortedArrayToBST(vector& nums) { @@ -192,9 +192,9 @@ public: }; ``` -## 总结 +# 总结 -**在[二叉树:构造二叉树登场!](https://mp.weixin.qq.com/s/7r66ap2s-shvVvlZxo59xg) 和 [二叉树:构造一棵最大的二叉树](https://mp.weixin.qq.com/s/1iWJV6Aov23A7xCF4nV88w)之后,我们顺理成章的应该构造一下二叉搜索树了,一不小心还是一棵平衡二叉搜索树**。 +**在[二叉树:构造二叉树登场!](https://programmercarl.com/0106.从中序与后序遍历序列构造二叉树.html) 和 [二叉树:构造一棵最大的二叉树](https://programmercarl.com/0654.最大二叉树.html)之后,我们顺理成章的应该构造一下二叉搜索树了,一不小心还是一棵平衡二叉搜索树**。 其实思路也是一样的,不断中间分割,然后递归处理左区间,右区间,也可以说是分治。 @@ -205,10 +205,10 @@ public: 最后依然给出迭代的方法,其实就是模拟取中间元素,然后不断分割去构造二叉树的过程。 -## 其他语言版本 +# 其他语言版本 -Java: +## Java 递归: 左闭右开 [left,right) ```Java @@ -253,7 +253,8 @@ class Solution { return root; } } -``` +``` + 迭代: 左闭右闭 [left,right] ```java class Solution { @@ -303,15 +304,10 @@ class Solution { } ``` -Python: +## Python + +递归法: ```python3 -# Definition for a binary tree node. -# class TreeNode: -# def __init__(self, val=0, left=None, right=None): -# self.val = val -# self.left = left -# self.right = right -#递归法 class Solution: def sortedArrayToBST(self, nums: List[int]) -> TreeNode: def buildaTree(left,right): @@ -326,21 +322,11 @@ class Solution: return root ``` -Go: +## Go - -> 递归(隐含回溯) +递归(隐含回溯) ```go -/** - * Definition for a binary tree node. - * type TreeNode struct { - * Val int - * Left *TreeNode - * Right *TreeNode - * } - */ - //递归(隐含回溯) func sortedArrayToBST(nums []int) *TreeNode { if len(nums)==0{return nil}//终止条件,最后数组为空则可以返回 root:=&TreeNode{nums[len(nums)/2],nil,nil}//按照BSL的特点,从中间构造节点 @@ -350,21 +336,9 @@ func sortedArrayToBST(nums []int) *TreeNode { } ``` -JavaScript版本 +## JavaScript ```javascript -/** - * Definition for a binary tree node. - * function TreeNode(val, left, right) { - * this.val = (val===undefined ? 0 : val) - * this.left = (left===undefined ? null : left) - * this.right = (right===undefined ? null : right) - * } - */ -/** - * @param {number[]} nums - * @return {TreeNode} - */ var sortedArrayToBST = function (nums) { const buildTree = (Arr, left, right) => { if (left > right) @@ -388,4 +362,4 @@ var sortedArrayToBST = function (nums) { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0110.平衡二叉树.md b/problems/0110.平衡二叉树.md index b6e50853..43a49758 100644 --- a/problems/0110.平衡二叉树.md +++ b/problems/0110.平衡二叉树.md @@ -9,9 +9,9 @@ > 求高度还是求深度,你搞懂了不? -## 110.平衡二叉树 +# 110.平衡二叉树 -题目地址:https://leetcode-cn.com/problems/balanced-binary-tree/ +[力扣题目链接](https://leetcode-cn.com/problems/balanced-binary-tree/) 给定一个二叉树,判断它是否是高度平衡的二叉树。 @@ -33,9 +33,10 @@ 返回 false 。 -## 题外话 +# 题外话 -咋眼一看这道题目和[104.二叉树的最大深度](https://mp.weixin.qq.com/s/guKwV-gSNbA1CcbvkMtHBg)很像,其实有很大区别。 + +咋眼一看这道题目和[104.二叉树的最大深度](https://programmercarl.com/0104.二叉树的最大深度.html)很像,其实有很大区别。 这里强调一波概念: @@ -50,13 +51,13 @@ 因为求深度可以从上到下去查 所以需要前序遍历(中左右),而高度只能从下到上去查,所以只能后序遍历(左右中) -有的同学一定疑惑,为什么[104.二叉树的最大深度](https://mp.weixin.qq.com/s/guKwV-gSNbA1CcbvkMtHBg)中求的是二叉树的最大深度,也用的是后序遍历。 +有的同学一定疑惑,为什么[104.二叉树的最大深度](https://programmercarl.com/0104.二叉树的最大深度.html)中求的是二叉树的最大深度,也用的是后序遍历。 **那是因为代码的逻辑其实是求的根节点的高度,而根节点的高度就是这颗树的最大深度,所以才可以使用后序遍历。** -在[104.二叉树的最大深度](https://mp.weixin.qq.com/s/guKwV-gSNbA1CcbvkMtHBg)中,如果真正求取二叉树的最大深度,代码应该写成如下:(前序遍历) +在[104.二叉树的最大深度](https://programmercarl.com/0104.二叉树的最大深度.html)中,如果真正求取二叉树的最大深度,代码应该写成如下:(前序遍历) -```C++ +```CPP class Solution { public: int result; @@ -90,7 +91,7 @@ public: 注意以上代码是为了把细节体现出来,简化一下代码如下: -```C++ +```CPP class Solution { public: int result; @@ -114,9 +115,9 @@ public: }; ``` -## 本题思路 +# 本题思路 -### 递归 +## 递归 此时大家应该明白了既然要求比较高度,必然是要后序遍历。 @@ -160,7 +161,7 @@ if (node == NULL) { 代码如下: -``` +```CPP int leftDepth = depth(node->left); // 左 if (leftDepth == -1) return -1; int rightDepth = depth(node->right); // 右 @@ -178,7 +179,7 @@ return result; 代码精简之后如下: -``` +```CPP int leftDepth = getDepth(node->left); if (leftDepth == -1) return -1; int rightDepth = getDepth(node->right); @@ -190,7 +191,7 @@ return abs(leftDepth - rightDepth) > 1 ? -1 : 1 + max(leftDepth, rightDepth); getDepth整体代码如下: -```C++ +```CPP int getDepth(TreeNode* node) { if (node == NULL) { return 0; @@ -205,7 +206,7 @@ int getDepth(TreeNode* node) { 最后本题整体递归代码如下: -```C++ +```CPP class Solution { public: // 返回以该节点为根节点的二叉树的高度,如果不是二叉搜索树了则返回-1 @@ -225,9 +226,9 @@ public: }; ``` -### 迭代 +## 迭代 -在[104.二叉树的最大深度](https://mp.weixin.qq.com/s/guKwV-gSNbA1CcbvkMtHBg)中我们可以使用层序遍历来求深度,但是就不能直接用层序遍历来求高度了,这就体现出求高度和求深度的不同。 +在[104.二叉树的最大深度](https://programmercarl.com/0104.二叉树的最大深度.html)中我们可以使用层序遍历来求深度,但是就不能直接用层序遍历来求高度了,这就体现出求高度和求深度的不同。 本题的迭代方式可以先定义一个函数,专门用来求高度。 @@ -235,7 +236,7 @@ public: 代码如下: -```C++ +```CPP // cur节点的最大深度,就是cur的高度 int getDepth(TreeNode* cur) { stack st; @@ -266,7 +267,7 @@ int getDepth(TreeNode* cur) { 然后再用栈来模拟前序遍历,遍历每一个节点的时候,再去判断左右孩子的高度是否符合,代码如下: -``` +```CPP bool isBalanced(TreeNode* root) { stack st; if (root == NULL) return true; @@ -286,7 +287,7 @@ bool isBalanced(TreeNode* root) { 整体代码如下: -``` +```CPP class Solution { private: int getDepth(TreeNode* cur) { @@ -342,7 +343,7 @@ public: 因为对于回溯算法已经是非常复杂的递归了,如果在用迭代的话,就是自己给自己找麻烦,效率也并不一定高。 -## 总结 +# 总结 通过本题可以了解求二叉树深度 和 二叉树高度的差异,求深度适合用前序遍历,而求高度适合用后序遍历。 @@ -351,9 +352,9 @@ public: 但是递归方式是一定要掌握的! -## 其他语言版本 +# 其他语言版本 -Java: +## Java ```Java class Solution { @@ -494,9 +495,9 @@ class Solution { } ``` -Python: +## Python -> 递归法: +递归法: ```python class Solution: def isBalanced(self, root: TreeNode) -> bool: @@ -513,7 +514,7 @@ class Solution: return -1 if abs(leftDepth - rightDepth)>1 else 1 + max(leftDepth, rightDepth) ``` -> 迭代法: +迭代法: ```python class Solution: def isBalanced(self, root: TreeNode) -> bool: @@ -553,7 +554,7 @@ class Solution: ``` -Go: +## Go ```Go func isBalanced(root *TreeNode) bool { if root==nil{ @@ -589,7 +590,7 @@ func abs(a int)int{ } ``` -JavaScript: +## JavaScript ```javascript var isBalanced = function(root) { //还是用递归三部曲 + 后序遍历 左右中 当前左子树右子树高度相差大于1就返回-1 @@ -623,4 +624,4 @@ var isBalanced = function(root) { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0111.二叉树的最小深度.md b/problems/0111.二叉树的最小深度.md index a36faeff..12eadd60 100644 --- a/problems/0111.二叉树的最小深度.md +++ b/problems/0111.二叉树的最小深度.md @@ -9,9 +9,9 @@ > 和求最大深度一个套路? -## 111.二叉树的最小深度 +# 111.二叉树的最小深度 -题目地址:https://leetcode-cn.com/problems/minimum-depth-of-binary-tree/ +[力扣题目链接](https://leetcode-cn.com/problems/minimum-depth-of-binary-tree/) 给定一个二叉树,找出其最小深度。 @@ -27,9 +27,9 @@ 返回它的最小深度 2. -## 思路 +# 思路 -看完了这篇[104.二叉树的最大深度](https://mp.weixin.qq.com/s/guKwV-gSNbA1CcbvkMtHBg),再来看看如何求最小深度。 +看完了这篇[104.二叉树的最大深度](https://programmercarl.com/0104.二叉树的最大深度.html),再来看看如何求最小深度。 直觉上好像和求最大深度差不多,其实还是差不少的。 @@ -87,7 +87,7 @@ return result; 代码如下: -```C++ +```CPP int leftDepth = getDepth(node->left); // 左 int rightDepth = getDepth(node->right); // 右 // 中 @@ -106,7 +106,7 @@ return result; 遍历的顺序为后序(左右中),可以看出:**求二叉树的最小深度和求二叉树的最大深度的差别主要在于处理左右孩子不为空的逻辑。** 整体递归代码如下: -```C++ +```CPP class Solution { public: int getDepth(TreeNode* node) { @@ -134,7 +134,7 @@ public: 精简之后代码如下: -```C++ +```CPP class Solution { public: int minDepth(TreeNode* root) { @@ -154,15 +154,15 @@ public: ## 迭代法 -相对于[104.二叉树的最大深度](https://mp.weixin.qq.com/s/guKwV-gSNbA1CcbvkMtHBg),本题还可以使用层序遍历的方式来解决,思路是一样的。 +相对于[104.二叉树的最大深度](https://programmercarl.com/0104.二叉树的最大深度.html),本题还可以使用层序遍历的方式来解决,思路是一样的。 -如果对层序遍历还不清楚的话,可以看这篇:[二叉树:层序遍历登场!](https://mp.weixin.qq.com/s/Gb3BjakIKGNpup2jYtTzog) +如果对层序遍历还不清楚的话,可以看这篇:[二叉树:层序遍历登场!](https://programmercarl.com/0102.二叉树的层序遍历.html) **需要注意的是,只有当左右孩子都为空的时候,才说明遍历的最低点了。如果其中一个孩子为空则不是最低点** 代码如下:(详细注释) -```C++ +```CPP class Solution { public: @@ -190,10 +190,10 @@ public: ``` -## 其他语言版本 +# 其他语言版本 -Java: +## Java ```Java class Solution { @@ -253,7 +253,7 @@ class Solution { } ``` -Python: +## Python 递归法: @@ -299,7 +299,7 @@ class Solution: ``` -Go: +## Go ```go /** @@ -360,7 +360,7 @@ func minDepth(root *TreeNode) int { ``` -JavaScript: +## JavaScript 递归法: @@ -413,4 +413,4 @@ var minDepth = function(root) { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0112.路径总和.md b/problems/0112.路径总和.md index ae9a9267..da62452c 100644 --- a/problems/0112.路径总和.md +++ b/problems/0112.路径总和.md @@ -9,16 +9,16 @@ > 递归函数什么时候需要返回值 -相信很多同学都会疑惑,递归函数什么时候要有返回值,什么时候没有返回值,特别是有的时候递归函数返回类型为bool类型。那么 +相信很多同学都会疑惑,递归函数什么时候要有返回值,什么时候没有返回值,特别是有的时候递归函数返回类型为bool类型。 -接下来我通过详细讲解如下两道题,来回答这个问题: +那么接下来我通过详细讲解如下两道题,来回答这个问题: * 112.路径总和 -* 113.路径总和II +* 113.路径总和ii -## 112. 路径总和 +# 112. 路径总和 -题目地址:https://leetcode-cn.com/problems/path-sum/ +[力扣题目链接](https://leetcode-cn.com/problems/path-sum/) 给定一个二叉树和一个目标和,判断该树中是否存在根节点到叶子节点的路径,这条路径上所有节点值相加等于目标和。 @@ -31,11 +31,11 @@ 返回 true, 因为存在目标和为 22 的根节点到叶子节点的路径 5->4->11->2。 -### 思路 +# 思路 这道题我们要遍历从根节点到叶子节点的的路径看看总和是不是目标和。 -### 递归 +## 递归 可以使用深度优先遍历的方式(本题前中后序都可以,无所谓,因为中节点也没有处理逻辑)来遍历二叉树 @@ -43,13 +43,11 @@ 参数:需要二叉树的根节点,还需要一个计数器,这个计数器用来计算二叉树的一条边之和是否正好是目标和,计数器为int型。 -**再来看返回值,递归函数什么时候需要返回值?什么时候不需要返回值?** +再来看返回值,递归函数什么时候需要返回值?什么时候不需要返回值?这里总结如下三点: -在文章[二叉树:我的左下角的值是多少?](https://mp.weixin.qq.com/s/MH2gbLvzQ91jHPKqiub0Nw)中,我给出了一个结论: - -**如果需要搜索整颗二叉树,那么递归函数就不要返回值,如果要搜索其中一条符合条件的路径,递归函数就需要返回值,因为遇到符合条件的路径了就要及时返回。** - -在[二叉树:我的左下角的值是多少?](https://mp.weixin.qq.com/s/MH2gbLvzQ91jHPKqiub0Nw)中,因为要遍历树的所有路径,找出深度最深的叶子节点,所以递归函数不要返回值。 +* 如果需要搜索整颗二叉树且不用处理递归返回值,递归函数就不要返回值。(这种情况就是本文下半部分介绍的113.路径总和ii) +* 如果需要搜索整颗二叉树且需要处理递归返回值,递归函数就需要返回值。 (这种情况我们在[236. 二叉树的最近公共祖先](https://programmercarl.com/0236.二叉树的最近公共祖先.html)中介绍) +* 如果要搜索其中一条符合条件的路径,那么递归一定需要返回值,因为遇到符合条件的路径了就要及时返回。(本题的情况) 而本题我们要找一条符合条件的路径,所以递归函数需要返回值,及时返回,那么返回类型是什么呢? @@ -62,7 +60,7 @@ 所以代码如下: ``` -bool traversal(TreeNode* cur, int count) // 注意函数的返回类型 +bool traversal(treenode* cur, int count) // 注意函数的返回类型 ``` @@ -91,7 +89,7 @@ if (!cur->left && !cur->right) return false; // 遇到叶子节点而没有找 代码如下: -```C++ +```cpp if (cur->left) { // 左 (空节点不遍历) // 遇到叶子节点返回true,则直接返回true if (traversal(cur->left, count - cur->left->val)) return true; // 注意这里有回溯的逻辑 @@ -109,7 +107,7 @@ return false; 为了把回溯的过程体现出来,可以改为如下代码: -```C++ +```cpp if (cur->left) { // 左 count -= cur->left->val; // 递归,处理节点; if (traversal(cur->left, count)) return true; @@ -126,10 +124,10 @@ return false; 整体代码如下: -```C++ -class Solution { +```cpp +class solution { private: - bool traversal(TreeNode* cur, int count) { + bool traversal(treenode* cur, int count) { if (!cur->left && !cur->right && count == 0) return true; // 遇到叶子节点,并且计数为0 if (!cur->left && !cur->right) return false; // 遇到叶子节点直接返回 @@ -147,8 +145,8 @@ private: } public: - bool hasPathSum(TreeNode* root, int sum) { - if (root == NULL) return false; + bool haspathsum(treenode* root, int sum) { + if (root == null) return false; return traversal(root, sum - root->val); } }; @@ -156,15 +154,15 @@ public: 以上代码精简之后如下: -```C++ -class Solution { +```cpp +class solution { public: - bool hasPathSum(TreeNode* root, int sum) { - if (root == NULL) return false; + bool haspathsum(treenode* root, int sum) { + if (root == null) return false; if (!root->left && !root->right && sum == root->val) { return true; } - return hasPathSum(root->left, sum - root->val) || hasPathSum(root->right, sum - root->val); + return haspathsum(root->left, sum - root->val) || haspathsum(root->right, sum - root->val); } }; ``` @@ -172,43 +170,43 @@ public: **是不是发现精简之后的代码,已经完全看不出分析的过程了,所以我们要把题目分析清楚之后,在追求代码精简。** 这一点我已经强调很多次了! -### 迭代 +## 迭代 如果使用栈模拟递归的话,那么如果做回溯呢? **此时栈里一个元素不仅要记录该节点指针,还要记录从头结点到该节点的路径数值总和。** -C++就我们用pair结构来存放这个栈里的元素。 +c++就我们用pair结构来存放这个栈里的元素。 -定义为:`pair` pair<节点指针,路径数值> +定义为:`pair` pair<节点指针,路径数值> 这个为栈里的一个元素。 如下代码是使用栈模拟的前序遍历,如下:(详细注释) -```C++ -class Solution { +```cpp +class solution { public: - bool hasPathSum(TreeNode* root, int sum) { - if (root == NULL) return false; + bool haspathsum(treenode* root, int sum) { + if (root == null) return false; // 此时栈里要放的是pair<节点指针,路径数值> - stack> st; - st.push(pair(root, root->val)); + stack> st; + st.push(pair(root, root->val)); while (!st.empty()) { - pair node = st.top(); + pair node = st.top(); st.pop(); // 如果该节点是叶子节点了,同时该节点的路径数值等于sum,那么就返回true if (!node.first->left && !node.first->right && sum == node.second) return true; // 右节点,压进去一个节点的时候,将该节点的路径数值也记录下来 if (node.first->right) { - st.push(pair(node.first->right, node.second + node.first->right->val)); + st.push(pair(node.first->right, node.second + node.first->right->val)); } // 左节点,压进去一个节点的时候,将该节点的路径数值也记录下来 if (node.first->left) { - st.push(pair(node.first->left, node.second + node.first->left->val)); + st.push(pair(node.first->left, node.second + node.first->left->val)); } } return false; @@ -216,11 +214,11 @@ public: }; ``` -如果大家完全理解了本地的递归方法之后,就可以顺便把leetcode上113. 路径总和II做了。 +如果大家完全理解了本地的递归方法之后,就可以顺便把leetcode上113. 路径总和ii做了。 -## 113. 路径总和II +# 113. 路径总和ii -题目地址:https://leetcode-cn.com/problems/path-sum-ii/ +[力扣题目链接](https://leetcode-cn.com/problems/path-sum-ii/) 给定一个二叉树和一个目标和,找到所有从根节点到叶子节点路径总和等于给定目标和的路径。 @@ -230,26 +228,27 @@ public: 给定如下二叉树,以及目标和 sum = 22, -![113.路径总和II1.png](https://img-blog.csdnimg.cn/20210203160854654.png) +![113.路径总和ii1.png](https://img-blog.csdnimg.cn/20210203160854654.png) -### 思路 +## 思路 -113.路径总和II要遍历整个树,找到所有路径,**所以递归函数不要返回值!** + +113.路径总和ii要遍历整个树,找到所有路径,**所以递归函数不要返回值!** 如图: -![113.路径总和II](https://img-blog.csdnimg.cn/20210203160922745.png) +![113.路径总和ii](https://img-blog.csdnimg.cn/20210203160922745.png) 为了尽可能的把细节体现出来,我写出如下代码(**这份代码并不简洁,但是逻辑非常清晰**) -```C++ -class Solution { +```cpp +class solution { private: vector> result; vector path; // 递归函数不需要返回值,因为我们要遍历整个树 - void traversal(TreeNode* cur, int count) { + void traversal(treenode* cur, int count) { if (!cur->left && !cur->right && count == 0) { // 遇到了叶子节点且找到了和为sum的路径 result.push_back(path); return; @@ -275,10 +274,10 @@ private: } public: - vector> pathSum(TreeNode* root, int sum) { + vector> pathsum(treenode* root, int sum) { result.clear(); path.clear(); - if (root == NULL) return result; + if (root == null) return result; path.push_back(root->val); // 把根节点放进路径 traversal(root, sum - root->val); return result; @@ -286,11 +285,11 @@ public: }; ``` -至于113. 路径总和II 的迭代法我并没有写,用迭代方式记录所有路径比较麻烦,也没有必要,如果大家感兴趣的话,可以再深入研究研究。 +至于113. 路径总和ii 的迭代法我并没有写,用迭代方式记录所有路径比较麻烦,也没有必要,如果大家感兴趣的话,可以再深入研究研究。 -## 总结 +# 总结 -本篇通过leetcode上112. 路径总和 和 113. 路径总和II 详细的讲解了 递归函数什么时候需要返回值,什么不需要返回值。 +本篇通过leetcode上112. 路径总和 和 113. 路径总和ii 详细的讲解了 递归函数什么时候需要返回值,什么不需要返回值。 这两道题目是掌握这一知识点非常好的题目,大家看完本篇文章再去做题,就会感受到搜索整棵树和搜索某一路径的差别。 @@ -299,31 +298,30 @@ public: +# 其他语言版本 +## java -## 其他语言版本 - - -Java: -```Java -class Solution { - public boolean hasPathSum(TreeNode root, int targetSum) { +lc112 +```java +class solution { + public boolean haspathsum(treenode root, int targetsum) { if (root == null) { return false; } - targetSum -= root.val; + targetsum -= root.val; // 叶子结点 if (root.left == null && root.right == null) { - return targetSum == 0; + return targetsum == 0; } if (root.left != null) { - boolean left = hasPathSum(root.left, targetSum); + boolean left = haspathsum(root.left, targetsum); if (left) {// 已经找到 return true; } } if (root.right != null) { - boolean right = hasPathSum(root.right, targetSum); + boolean right = haspathsum(root.right, targetsum); if (right) {// 已经找到 return true; } @@ -332,34 +330,34 @@ class Solution { } } -// LC112 简洁方法 -class Solution { - public boolean hasPathSum(TreeNode root, int targetSum) { +// lc112 简洁方法 +class solution { + public boolean haspathsum(treenode root, int targetsum) { if (root == null) return false; // 为空退出 // 叶子节点判断是否符合 - if (root.left == null && root.right == null) return root.val == targetSum; + if (root.left == null && root.right == null) return root.val == targetsum; // 求两侧分支的路径和 - return hasPathSum(root.left, targetSum - root.val) || hasPathSum(root.right, targetSum - root.val); + return haspathsum(root.left, targetsum - root.val) || haspathsum(root.right, targetsum - root.val); } } ``` 迭代 ```java -class Solution { - public boolean hasPathSum(TreeNode root, int targetSum) { +class solution { + public boolean haspathsum(treenode root, int targetsum) { if(root==null)return false; - Stack stack1 = new Stack<>(); - Stack stack2 = new Stack<>(); + stack stack1 = new stack<>(); + stack stack2 = new stack<>(); stack1.push(root);stack2.push(root.val); - while(!stack1.isEmpty()){ + while(!stack1.isempty()){ int size = stack1.size(); for(int i=0;i> pathSum(TreeNode root, int targetSum) { - List> res = new ArrayList<>(); +class solution { + public list> pathsum(treenode root, int targetsum) { + list> res = new arraylist<>(); if (root == null) return res; // 非空判断 - List path = new LinkedList<>(); - preorderDFS(root, targetSum, res, path); + list path = new linkedlist<>(); + preorderdfs(root, targetsum, res, path); return res; } - public void preorderDFS(TreeNode root, int targetSum, List> res, List path) { + public void preorderdfs(treenode root, int targetsum, list> res, list path) { path.add(root.val); // 遇到了叶子节点 if (root.left == null && root.right == null) { - // 找到了和为 targetSum 的路径 - if (targetSum - root.val == 0) { - res.add(new ArrayList<>(path)); + // 找到了和为 targetsum 的路径 + if (targetsum - root.val == 0) { + res.add(new arraylist<>(path)); } - return; // 如果和不为 targetSum,返回 + return; // 如果和不为 targetsum,返回 } if (root.left != null) { - preorderDFS(root.left, targetSum - root.val, res, path); + preorderdfs(root.left, targetsum - root.val, res, path); path.remove(path.size() - 1); // 回溯 } if (root.right != null) { - preorderDFS(root.right, targetSum - root.val, res, path); + preorderdfs(root.right, targetsum - root.val, res, path); path.remove(path.size() - 1); // 回溯 } } } ``` -Python: +```java +// 解法2 +class Solution { + List> result; + LinkedList path; + public List> pathSum (TreeNode root,int targetSum) { + result = new LinkedList<>(); + path = new LinkedList<>(); + travesal(root, targetSum); + return result; + } + private void travesal(TreeNode root, int count) { + if (root == null) return; + path.offer(root.val); + count -= root.val; + if (root.left == null && root.right == null && count == 0) { + result.add(new LinkedList<>(path)); + } + travesal(root.left, count); + travesal(root.right, count); + path.removeLast(); // 回溯 + } +} +``` + +## python 0112.路径总和 + +**递归** ```python -# Definition for a binary tree node. -# class TreeNode: -# def __init__(self, val=0, left=None, right=None): -# self.val = val -# self.left = left -# self.right = right - -// 递归法 - -class Solution: - def hasPathSum(self, root: TreeNode, targetSum: int) -> bool: - def isornot(root,targetSum)->bool: - if (not root.left) and (not root.right) and targetSum == 0:return True // 遇到叶子节点,并且计数为0 - if (not root.left) and (not root.right):return False //遇到叶子节点,计数不为0 +class solution: + def haspathsum(self, root: treenode, targetsum: int) -> bool: + def isornot(root, targetsum) -> bool: + if (not root.left) and (not root.right) and targetsum == 0: + return true # 遇到叶子节点,并且计数为0 + if (not root.left) and (not root.right): + return false # 遇到叶子节点,计数不为0 if root.left: - targetSum -= root.left.val //左节点 - if isornot(root.left,targetSum):return True //递归,处理左节点 - targetSum += root.left.val //回溯 + targetsum -= root.left.val # 左节点 + if isornot(root.left, targetsum): return true # 递归,处理左节点 + targetsum += root.left.val # 回溯 if root.right: - targetSum -= root.right.val //右节点 - if isornot(root.right,targetSum):return True //递归,处理右节点 - targetSum += root.right.val //回溯 - return False - - if root == None:return False //别忘记处理空TreeNode - else:return isornot(root,targetSum-root.val) + targetsum -= root.right.val # 右节点 + if isornot(root.right, targetsum): return true # 递归,处理右节点 + targetsum += root.right.val # 回溯 + return false + + if root == none: + return false # 别忘记处理空treenode + else: + return isornot(root, targetsum - root.val) +``` + +**迭代 - 层序遍历** +```python +class solution: + def haspathsum(self, root: treenode, targetsum: int) -> bool: + if not root: + return false + + stack = [] # [(当前节点,路径数值), ...] + stack.append((root, root.val)) + + while stack: + cur_node, path_sum = stack.pop() + + if not cur_node.left and not cur_node.right and path_sum == targetsum: + return true + + if cur_node.right: + stack.append((cur_node.right, path_sum + cur_node.right.val)) + + if cur_node.left: + stack.append((cur_node.left, path_sum + cur_node.left.val)) + + return false ``` 0113.路径总和-ii + +**递归** ```python -# Definition for a binary tree node. -# class TreeNode: -# def __init__(self, val=0, left=None, right=None): -# self.val = val -# self.left = left -# self.right = right -//递归法 -class Solution: - def pathSum(self, root: TreeNode, targetSum: int) -> List[List[int]]: - path=[] - res=[] - def pathes(root,targetSum): - if (not root.left) and (not root.right) and targetSum == 0: // 遇到叶子节点,并且计数为0 - res.append(path[:]) //找到一种路径,记录到res中,注意必须是path[:]而不是path - return - if (not root.left) and (not root.right):return // 遇到叶子节点直接返回 - if root.left: //左 - targetSum -= root.left.val - path.append(root.left.val) //递归前记录节点 - pathes(root.left,targetSum) //递归 - targetSum += root.left.val //回溯 - path.pop() //回溯 - if root.right: //右 - targetSum -= root.right.val - path.append(root.right.val) //递归前记录节点 - pathes(root.right,targetSum) //递归 - targetSum += root.right.val //回溯 - path.pop() //回溯 - return - - if root == None:return [] //处理空TreeNode - else: - path.append(root.val) //首先处理根节点 - pathes(root,targetSum-root.val) - return res +class solution: + def pathsum(self, root: treenode, targetsum: int) -> list[list[int]]: + + def traversal(cur_node, remain): + if not cur_node.left and not cur_node.right and remain == 0: + result.append(path[:]) + return + + if not cur_node.left and not cur_node.right: return + + if cur_node.left: + path.append(cur_node.left.val) + remain -= cur_node.left.val + traversal(cur_node.left, remain) + path.pop() + remain += cur_node.left.val + + if cur_node.right: + path.append(cur_node.right.val) + remain -= cur_node.right.val + traversal(cur_node.right, remain) + path.pop() + remain += cur_node.right.val + + result, path = [], [] + if not root: + return [] + path.append(root.val) + traversal(root, targetsum - root.val) + return result ``` -Go: +## go -> 112. 路径总和 +112. 路径总和 ```go //递归法 /** - * Definition for a binary tree node. - * type TreeNode struct { - * Val int - * Left *TreeNode - * Right *TreeNode + * definition for a binary tree node. + * type treenode struct { + * val int + * left *treenode + * right *treenode * } */ -func hasPathSum(root *TreeNode, targetSum int) bool { +func haspathsum(root *treenode, targetsum int) bool { var flage bool //找没找到的标志 if root==nil{ return flage } - pathSum(root,0,targetSum,&flage) + pathsum(root,0,targetsum,&flage) return flage } -func pathSum(root *TreeNode, sum int,targetSum int,flage *bool){ - sum+=root.Val - if root.Left==nil&&root.Right==nil&&sum==targetSum{ +func pathsum(root *treenode, sum int,targetsum int,flage *bool){ + sum+=root.val + if root.left==nil&&root.right==nil&&sum==targetsum{ *flage=true return } - if root.Left!=nil&&!(*flage){//左节点不为空且还没找到 - pathSum(root.Left,sum,targetSum,flage) + if root.left!=nil&&!(*flage){//左节点不为空且还没找到 + pathsum(root.left,sum,targetsum,flage) } - if root.Right!=nil&&!(*flage){//右节点不为空且没找到 - pathSum(root.Right,sum,targetSum,flage) + if root.right!=nil&&!(*flage){//右节点不为空且没找到 + pathsum(root.right,sum,targetsum,flage) } } ``` - - -> 113 递归法 +113 递归法 ```go /** - * Definition for a binary tree node. - * type TreeNode struct { - * Val int - * Left *TreeNode - * Right *TreeNode + * definition for a binary tree node. + * type treenode struct { + * val int + * left *treenode + * right *treenode * } */ -func pathSum(root *TreeNode, targetSum int) [][]int { +func pathsum(root *treenode, targetsum int) [][]int { var result [][]int//最终结果 if root==nil{ return result } - var sumNodes []int//经过路径的节点集合 - hasPathSum(root,&sumNodes,targetSum,&result) + var sumnodes []int//经过路径的节点集合 + haspathsum(root,&sumnodes,targetsum,&result) return result } -func hasPathSum(root *TreeNode,sumNodes *[]int,targetSum int,result *[][]int){ - *sumNodes=append(*sumNodes,root.Val) - if root.Left==nil&&root.Right==nil{//叶子节点 - fmt.Println(*sumNodes) +func haspathsum(root *treenode,sumnodes *[]int,targetsum int,result *[][]int){ + *sumnodes=append(*sumnodes,root.val) + if root.left==nil&&root.right==nil{//叶子节点 + fmt.println(*sumnodes) var sum int var number int - for k,v:=range *sumNodes{//求该路径节点的和 + for k,v:=range *sumnodes{//求该路径节点的和 sum+=v number=k } - tempNodes:=make([]int,number+1)//新的nodes接受指针里的值,防止最终指针里的值发生变动,导致最后的结果都是最后一个sumNodes的值 - for k,v:=range *sumNodes{ - tempNodes[k]=v + tempnodes:=make([]int,number+1)//新的nodes接受指针里的值,防止最终指针里的值发生变动,导致最后的结果都是最后一个sumnodes的值 + for k,v:=range *sumnodes{ + tempnodes[k]=v } - if sum==targetSum{ - *result=append(*result,tempNodes) + if sum==targetsum{ + *result=append(*result,tempnodes) } } - if root.Left!=nil{ - hasPathSum(root.Left,sumNodes,targetSum,result) - *sumNodes=(*sumNodes)[:len(*sumNodes)-1]//回溯 + if root.left!=nil{ + haspathsum(root.left,sumnodes,targetsum,result) + *sumnodes=(*sumnodes)[:len(*sumnodes)-1]//回溯 } - if root.Right!=nil{ - hasPathSum(root.Right,sumNodes,targetSum,result) - *sumNodes=(*sumNodes)[:len(*sumNodes)-1]//回溯 + if root.right!=nil{ + haspathsum(root.right,sumnodes,targetsum,result) + *sumnodes=(*sumnodes)[:len(*sumnodes)-1]//回溯 } } ``` -JavaScript: +## javascript 0112.路径总和 ```javascript /** - * @param {TreeNode} root - * @param {number} targetSum + * @param {treenode} root + * @param {number} targetsum * @return {boolean} */ -let hasPathSum = function (root, targetSum) { +let haspathsum = function (root, targetsum) { // 递归法 const traversal = (node, cnt) => { // 遇到叶子节点,并且计数为0 @@ -597,19 +637,19 @@ let hasPathSum = function (root, targetSum) { return false; }; if (!root) return false; - return traversal(root, targetSum - root.val); + return traversal(root, targetsum - root.val); // 精简代码: // if (!root) return false; - // if (!root.left && !root.right && targetSum === root.val) return true; - // return hasPathSum(root.left, targetSum - root.val) || hasPathSum(root.right, targetSum - root.val); + // if (!root.left && !root.right && targetsum === root.val) return true; + // return haspathsum(root.left, targetsum - root.val) || haspathsum(root.right, targetsum - root.val); }; ``` 0113.路径总和-ii ```javascript -let pathSum = function (root, targetSum) { +let pathsum = function (root, targetsum) { // 递归法 // 要遍历整个树找到所有路径,所以递归函数不需要返回值, 与112不同 const res = []; @@ -635,62 +675,29 @@ let pathSum = function (root, targetSum) { return; }; if (!root) return res; - travelsal(root, targetSum - root.val, [root.val]); // 把根节点放进路径 + travelsal(root, targetsum - root.val, [root.val]); // 把根节点放进路径 return res; }; ``` - -0112 路径总和 +113 路径总和 精简版 ```javascript -var hasPathSum = function(root, targetSum) { +var pathsum = function(root, targetsum) { //递归方法 - // 1. 确定函数参数 - const traversal = function(node,count){ - // 2. 确定终止条件 - if(node.left===null&&node.right===null&&count===0){ - return true; - } - if(node.left===null&&node.right===null){ - return false; - } - //3. 单层递归逻辑 - if(node.left){ - if(traversal(node.left,count-node.left.val)){ - return true; - } - } - if(node.right){ - if(traversal(node.right,count-node.right.val)){ - return true; - } - } - return false; - } - if(root===null){ - return false; - } - return traversal(root,targetSum-root.val); -}; -``` -113 路径总和 -```javascript -var pathSum = function(root, targetSum) { - //递归方法 - let resPath = [],curPath = []; + let respath = [],curpath = []; // 1. 确定递归函数参数 - const travelTree = function(node,count){ - curPath.push(node.val); + const traveltree = function(node,count){ + curpath.push(node.val); count-=node.val; if(node.left===null&&node.right===null&&count===0){ - resPath.push([...curPath]); + respath.push([...curpath]); } - node.left&&travelTree(node.left,count); - node.right&&travelTree(node.right,count); - let cur = curPath.pop(); + node.left&&traveltree(node.left,count); + node.right&&traveltree(node.right,count); + let cur = curpath.pop(); count-=cur; } if(root===null){ - return resPath; + return respath; } travelTree(root,targetSum); return resPath; @@ -699,8 +706,9 @@ var pathSum = function(root, targetSum) { + ----------------------- * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0115.不同的子序列.md b/problems/0115.不同的子序列.md index 014eb3cd..908682dd 100644 --- a/problems/0115.不同的子序列.md +++ b/problems/0115.不同的子序列.md @@ -8,7 +8,7 @@ ## 115.不同的子序列 -题目链接:https://leetcode-cn.com/problems/distinct-subsequences/ +[力扣题目链接](https://leetcode-cn.com/problems/distinct-subsequences/) 给定一个字符串 s 和一个字符串 t ,计算在 s 的子序列中 t 出现的个数。 @@ -29,7 +29,7 @@ s 和 t 由英文字母组成 这道题目相对于72. 编辑距离,简单了不少,因为本题相当于只有删除操作,不用考虑替换增加之类的。 -但相对于刚讲过的[动态规划:392.判断子序列](https://mp.weixin.qq.com/s/2pjT4B4fjfOx5iB6N6xyng)就有难度了,这道题目双指针法可就做不了了,来看看动规五部曲分析如下: +但相对于刚讲过的[动态规划:392.判断子序列](https://programmercarl.com/0392.判断子序列.html)就有难度了,这道题目双指针法可就做不了了,来看看动规五部曲分析如下: 1. 确定dp数组(dp table)以及下标的含义 @@ -82,7 +82,7 @@ dp[0][0]应该是1,空字符串s,可以删除0个元素,变成空字符串 初始化分析完毕,代码如下: -```C++ +```CPP vector> dp(s.size() + 1, vector(t.size() + 1)); for (int i = 0; i <= s.size(); i++) dp[i][0] = 1; for (int j = 1; j <= t.size(); j++) dp[0][j] = 0; // 其实这行代码可以和dp数组初始化的时候放在一起,但我为了凸显初始化的逻辑,所以还是加上了。 @@ -97,7 +97,7 @@ for (int j = 1; j <= t.size(); j++) dp[0][j] = 0; // 其实这行代码可以和 代码如下: -```C++ +```CPP for (int i = 1; i <= s.size(); i++) { for (int j = 1; j <= t.size(); j++) { if (s[i - 1] == t[j - 1]) { @@ -120,7 +120,7 @@ for (int i = 1; i <= s.size(); i++) { 动规五部曲分析完毕,代码如下: -```C++ +```CPP class Solution { public: int numDistinct(string s, string t) { @@ -221,6 +221,30 @@ class SolutionDP2: ``` Go: +```go +func numDistinct(s string, t string) int { + dp:= make([][]int,len(s)+1) + for i:=0;i { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0116.填充每个节点的下一个右侧节点指针.md b/problems/0116.填充每个节点的下一个右侧节点指针.md index 28e6f645..e43d79b0 100644 --- a/problems/0116.填充每个节点的下一个右侧节点指针.md +++ b/problems/0116.填充每个节点的下一个右侧节点指针.md @@ -9,7 +9,7 @@ # 116. 填充每个节点的下一个右侧节点指针 -链接:https://leetcode-cn.com/problems/populating-next-right-pointers-in-each-node/ +[力扣题目链接](https://leetcode-cn.com/problems/populating-next-right-pointers-in-each-node/) 给定一个 完美二叉树 ,其所有叶子节点都在同一层,每个父节点都有两个子节点。二叉树定义如下: @@ -52,7 +52,7 @@ struct Node { 图中cur节点为元素4,那么搭线的逻辑代码:(**注意注释中操作1和操作2和图中的对应关系**) -```C++ +```CPP if (cur->left) cur->left->next = cur->right; // 操作1 if (cur->right) { if (cur->next) cur->right->next = cur->next->left; // 操作2 @@ -63,7 +63,7 @@ if (cur->right) { 理解到这里,使用前序遍历,那么不难写出如下代码: -```C++ +```CPP class Solution { private: void traversal(Node* cur) { @@ -87,13 +87,13 @@ public: ## 迭代(层序遍历) -本题使用层序遍历是最为直观的,如果对层序遍历不了解,看这篇:[二叉树:层序遍历登场!](https://mp.weixin.qq.com/s/4-bDKi7SdwfBGRm9FYduiA)。 +本题使用层序遍历是最为直观的,如果对层序遍历不了解,看这篇:[二叉树:层序遍历登场!](https://programmercarl.com/0102.二叉树的层序遍历.html)。 层序遍历本来就是一层一层的去遍历,记录一层的头结点(nodePre),然后让nodePre指向当前遍历的节点就可以了。 代码如下: -```C++ +```CPP class Solution { public: Node* connect(Node* root) { @@ -130,26 +130,134 @@ public: ## Java ```java +// 递归法 +class Solution { + public void traversal(Node cur) { + if (cur == null) return; + if (cur.left != null) cur.left.next = cur.right; // 操作1 + if (cur.right != null) { + if(cur.next != null) cur.right.next = cur.next.left; //操作2 + else cur.right.next = null; + } + traversal(cur.left); // 左 + traversal(cur.right); //右 + } + public Node connect(Node root) { + traversal(root); + return root; + } +} +``` +```java +// 迭代法 +class Solution { + public Node connect(Node root) { + if (root == null) return root; + Queue que = new LinkedList(); + que.offer(root); + Node nodePre = null; + Node node = null; + while (!que.isEmpty()) { + int size = que.size(); + for (int i=0; i 'Node': + def traversal(cur: 'Node') -> 'Node': + if not cur: return [] + if cur.left: cur.left.next = cur.right # 操作1 + if cur.right: + if cur.next: + cur.right.next = cur.next.left # 操作2 + else: + cur.right.next = None + traversal(cur.left) # 左 + traversal(cur.right) # 右 + traversal(root) + return root +``` +```python +# 迭代法 +class Solution: + def connect(self, root: 'Node') -> 'Node': + if not root: return + res = [] + queue = [root] + while queue: + size = len(queue) + for i in range(size): # 开始每一层的遍历 + if i==0: + nodePre = queue.pop(0) # 记录一层的头结点 + node = nodePre + else: + node = queue.pop(0) + nodePre.next = node # 本层前一个节点next指向本节点 + nodePre = nodePre.next + if node.left: queue.append(node.left) + if node.right: queue.append(node.right) + nodePre.next = None # 本层最后一个节点指向None + return root ``` - ## Go ```go + ``` ## JavaScript ```js +const connect = root => { + if (!root) return root; + // 根节点入队 + const Q = [root]; + while (Q.length) { + const len = Q.length; + // 遍历这一层的所有节点 + for (let i = 0; i < len; i++) { + // 队头出队 + const node = Q.shift(); + // 连接 + if (i < len - 1) { + // 新的队头是node的右边元素 + node.next = Q[0]; + } + // 队头左节点有值,放入队列 + node.left && Q.push(node.left); + // 队头右节点有值,放入队列 + node.right && Q.push(node.right); + } + } + return root; +}; ``` ----------------------- * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0121.买卖股票的最佳时机.md b/problems/0121.买卖股票的最佳时机.md index 259fff34..b08e4193 100644 --- a/problems/0121.买卖股票的最佳时机.md +++ b/problems/0121.买卖股票的最佳时机.md @@ -8,7 +8,7 @@ ## 121. 买卖股票的最佳时机 -题目链接:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock/ +[力扣题目链接](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock/) 给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。 @@ -33,7 +33,7 @@ 这道题目最直观的想法,就是暴力,找最优间距了。 -```C++ +```CPP class Solution { public: int maxProfit(vector& prices) { @@ -59,7 +59,7 @@ public: C++代码如下: -```C++ +```CPP class Solution { public: int maxProfit(vector& prices) { @@ -139,7 +139,7 @@ dp[5][1]就是最终结果。 以上分析完毕,C++代码如下: -```C++ +```CPP // 版本一 class Solution { public: @@ -169,7 +169,7 @@ dp[i][1] = max(dp[i - 1][1], prices[i] + dp[i - 1][0]); 那么我们只需要记录 当前天的dp状态和前一天的dp状态就可以了,可以使用滚动数组来节省空间,代码如下: -```C++ +```CPP // 版本二 class Solution { public: @@ -214,6 +214,26 @@ class Solution { } } ``` +```java +// 解法1 +class Solution { + public int maxProfit(int[] prices) { + if (prices == null || prices.length == 0) return 0; + int length = prices.length; + // dp[i][0]代表第i天持有股票的最大收益 + // dp[i][1]代表第i天不持有股票的最大收益 + int[][] dp = new int[length][2]; + int result = 0; + dp[0][0] = -prices[0]; + dp[0][1] = 0; + for (int i = 1; i < length; i++) { + dp[i][0] = Math.max(dp[i - 1][0], -prices[i]); + dp[i][1] = Math.max(dp[i - 1][0] + prices[i], dp[i - 1][1]); + } + return dp[length - 1][1]; + } +} +``` ``` java class Solution { // 动态规划解法 @@ -313,6 +333,26 @@ func max(a,b int)int { } ``` +JavaScript: + +```javascript +const maxProfit = prices => { + const len = prices.length; + // 创建dp数组 + const dp = new Array(len).fill([0, 0]); + // dp数组初始化 + dp[0] = [-prices[0], 0]; + for (let i = 1; i < len; i++) { + // 更新dp[i] + dp[i] = [ + Math.max(dp[i - 1][0], -prices[i]), + Math.max(dp[i - 1][1], prices[i] + dp[i - 1][0]), + ]; + } + return dp[len - 1][1]; +}; +``` + @@ -320,4 +360,4 @@ func max(a,b int)int { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0122.买卖股票的最佳时机II.md b/problems/0122.买卖股票的最佳时机II.md index 60d4591f..bd837eea 100644 --- a/problems/0122.买卖股票的最佳时机II.md +++ b/problems/0122.买卖股票的最佳时机II.md @@ -9,7 +9,7 @@ ## 122.买卖股票的最佳时机II -题目链接:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-ii/ +[力扣题目链接](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-ii/) 给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。 @@ -80,7 +80,7 @@ 对应C++代码如下: -```C++ +```CPP class Solution { public: int maxProfit(vector& prices) { @@ -99,7 +99,7 @@ public: 动态规划将在下一个系列详细讲解,本题解先给出我的C++代码(带详细注释),感兴趣的同学可以自己先学习一下。 -```C++ +```CPP class Solution { public: int maxProfit(vector& prices) { @@ -139,17 +139,11 @@ Java: // 贪心思路 class Solution { public int maxProfit(int[] prices) { - int sum = 0; - int profit = 0; - int buy = prices[0]; + int result = 0; for (int i = 1; i < prices.length; i++) { - profit = prices[i] - buy; - if (profit > 0) { - sum += profit; - } - buy = prices[i]; + result += Math.max(prices[i] - prices[i - 1], 0); } - return sum; + return result; } } ``` @@ -235,8 +229,23 @@ var maxProfit = function(prices) { }; ``` +C: +```c +int maxProfit(int* prices, int pricesSize){ + int result = 0; + int i; + //从第二个元素开始遍历数组,与之前的元素进行比较 + for(i = 1; i < pricesSize; ++i) { + //若该元素比前面元素大,则说明有利润。代表买入 + if(prices[i] > prices[i-1]) + result+= prices[i]-prices[i-1]; + } + return result; +} +``` + ----------------------- * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0122.买卖股票的最佳时机II(动态规划).md b/problems/0122.买卖股票的最佳时机II(动态规划).md index 8ed70063..5dfe3f0e 100644 --- a/problems/0122.买卖股票的最佳时机II(动态规划).md +++ b/problems/0122.买卖股票的最佳时机II(动态规划).md @@ -8,7 +8,7 @@ ## 122.买卖股票的最佳时机II -题目链接:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-ii/ +[力扣题目链接](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-ii/) 给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。 @@ -38,12 +38,12 @@ ## 思路 -本题我们在讲解贪心专题的时候就已经讲解过了[贪心算法:买卖股票的最佳时机II](https://mp.weixin.qq.com/s/VsTFA6U96l18Wntjcg3fcg),只不过没有深入讲解动态规划的解法,那么这次我们再好好分析一下动规的解法。 +本题我们在讲解贪心专题的时候就已经讲解过了[贪心算法:买卖股票的最佳时机II](https://programmercarl.com/0122.买卖股票的最佳时机II.html),只不过没有深入讲解动态规划的解法,那么这次我们再好好分析一下动规的解法。 -本题和[121. 买卖股票的最佳时机](https://mp.weixin.qq.com/s/keWo5qYJY4zmHn3amfXdfQ)的唯一区别本题股票可以买卖多次了(注意只有一只股票,所以再次购买前要出售掉之前的股票) +本题和[121. 买卖股票的最佳时机](https://programmercarl.com/0121.买卖股票的最佳时机.html)的唯一区别本题股票可以买卖多次了(注意只有一只股票,所以再次购买前要出售掉之前的股票) -**在动规五部曲中,这个区别主要是体现在递推公式上,其他都和[121. 买卖股票的最佳时机](https://mp.weixin.qq.com/s/keWo5qYJY4zmHn3amfXdfQ)一样一样的**。 +**在动规五部曲中,这个区别主要是体现在递推公式上,其他都和[121. 买卖股票的最佳时机](https://programmercarl.com/0121.买卖股票的最佳时机.html)一样一样的**。 所以我们重点讲一讲递推公式。 @@ -57,10 +57,9 @@ * 第i-1天就持有股票,那么就保持现状,所得现金就是昨天持有股票的所得现金 即:dp[i - 1][0] * 第i天买入股票,所得现金就是昨天不持有股票的所得现金减去 今天的股票价格 即:dp[i - 1][1] - prices[i] +**注意这里和[121. 买卖股票的最佳时机](https://programmercarl.com/0121.买卖股票的最佳时机.html)唯一不同的地方,就是推导dp[i][0]的时候,第i天买入股票的情况**。 -**注意这里和[121. 买卖股票的最佳时机](https://mp.weixin.qq.com/s/keWo5qYJY4zmHn3amfXdfQ)唯一不同的地方,就是推导dp[i][0]的时候,第i天买入股票的情况**。 - -在[121. 买卖股票的最佳时机](https://mp.weixin.qq.com/s/keWo5qYJY4zmHn3amfXdfQ)中,因为股票全程只能买卖一次,所以如果买入股票,那么第i天持有股票即dp[i][0]一定就是 -prices[i]。 +在[121. 买卖股票的最佳时机](https://programmercarl.com/0121.买卖股票的最佳时机.html)中,因为股票全程只能买卖一次,所以如果买入股票,那么第i天持有股票即dp[i][0]一定就是 -prices[i]。 而本题,因为一只股票可以买卖多次,所以当第i天买入股票的时候,所持有的现金可能有之前买卖过的利润。 @@ -70,11 +69,11 @@ * 第i-1天就不持有股票,那么就保持现状,所得现金就是昨天不持有股票的所得现金 即:dp[i - 1][1] * 第i天卖出股票,所得现金就是按照今天股票佳价格卖出后所得现金即:prices[i] + dp[i - 1][0] -**注意这里和[121. 买卖股票的最佳时机](https://mp.weixin.qq.com/s/keWo5qYJY4zmHn3amfXdfQ)就是一样的逻辑,卖出股票收获利润(可能是负值)天经地义!** +**注意这里和[121. 买卖股票的最佳时机](https://programmercarl.com/0121.买卖股票的最佳时机.html)就是一样的逻辑,卖出股票收获利润(可能是负值)天经地义!** 代码如下:(注意代码中的注释,标记了和121.买卖股票的最佳时机唯一不同的地方) -```C++ +```CPP class Solution { public: int maxProfit(vector& prices) { @@ -94,7 +93,7 @@ public: * 时间复杂度:O(n) * 空间复杂度:O(n) -大家可以本题和[121. 买卖股票的最佳时机](https://mp.weixin.qq.com/s/keWo5qYJY4zmHn3amfXdfQ)的代码几乎一样,唯一的区别在: +大家可以本题和[121. 买卖股票的最佳时机](https://programmercarl.com/0121.买卖股票的最佳时机.html)的代码几乎一样,唯一的区别在: ``` dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] - prices[i]); @@ -106,7 +105,7 @@ dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] - prices[i]); 这里我依然给出滚动数组的版本,C++代码如下: -```C++ +```CPP // 版本二 class Solution { public: @@ -122,7 +121,7 @@ public: return dp[(len - 1) % 2][1]; } }; -``` +``` * 时间复杂度:O(n) * 空间复杂度:O(1) @@ -231,4 +230,4 @@ const maxProfit = (prices) => { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0123.买卖股票的最佳时机III.md b/problems/0123.买卖股票的最佳时机III.md index 7ff1bfe2..6a849c80 100644 --- a/problems/0123.买卖股票的最佳时机III.md +++ b/problems/0123.买卖股票的最佳时机III.md @@ -8,7 +8,7 @@ ## 123.买卖股票的最佳时机III -题目链接:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-iii/ +[力扣题目链接](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-iii/) 给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。 @@ -44,7 +44,7 @@ ## 思路 -这道题目相对 [121.买卖股票的最佳时机](https://mp.weixin.qq.com/s/keWo5qYJY4zmHn3amfXdfQ) 和 [122.买卖股票的最佳时机II](https://mp.weixin.qq.com/s/d4TRWFuhaY83HPa6t5ZL-w) 难了不少。 +这道题目相对 [121.买卖股票的最佳时机](https://programmercarl.com/0121.买卖股票的最佳时机.html) 和 [122.买卖股票的最佳时机II](https://programmercarl.com/0122.买卖股票的最佳时机II.html) 难了不少。 关键在于至多买卖两次,这意味着可以买卖一次,可以买卖两次,也可以不买卖。 @@ -127,7 +127,7 @@ dp[i][4] = max(dp[i - 1][4], dp[i - 1][3] + prices[i]); 以上五部都分析完了,不难写出如下代码: -```C++ +```CPP // 版本一 class Solution { public: @@ -153,7 +153,7 @@ public: 当然,大家可以看到力扣官方题解里的一种优化空间写法,我这里给出对应的C++版本: -```C++ +```CPP // 版本二 class Solution { public: @@ -278,7 +278,44 @@ class Solution: return dp[4] ``` -Go: +JavaScript: + +> 版本一: + +```javascript +const maxProfit = prices => { + const len = prices.length; + const dp = new Array(len).fill(0).map(x => new Array(5).fill(0)); + dp[0][1] = -prices[0]; + dp[0][3] = -prices[0]; + for (let i = 1; i < len; i++) { + dp[i][0] = dp[i - 1][0]; + dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] - prices[i]); + dp[i][2] = Math.max(dp[i - 1][2], dp[i - 1][1] + prices[i]); + dp[i][3] = Math.max(dp[i - 1][3], dp[i - 1][2] - prices[i]); + dp[i][4] = Math.max(dp[i - 1][4], dp[i - 1][3] + prices[i]); + } + return dp[len - 1][4]; +}; +``` + +> 版本二: + +```javascript +const maxProfit = prices => { + const len = prices.length; + const dp = new Array(5).fill(0); + dp[1] = -prices[0]; + dp[3] = -prices[0]; + for (let i = 1; i < len; i++) { + dp[1] = Math.max(dp[1], dp[0] - prices[i]); + dp[2] = Math.max(dp[2], dp[1] + prices[i]); + dp[3] = Math.max(dp[3], dp[2] - prices[i]); + dp[4] = Math.max(dp[4], dp[3] + prices[i]); + } + return dp[4]; +}; +``` @@ -287,4 +324,4 @@ Go: * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0127.单词接龙.md b/problems/0127.单词接龙.md new file mode 100644 index 00000000..e38453ef --- /dev/null +++ b/problems/0127.单词接龙.md @@ -0,0 +1,150 @@ + +

+ + + + +

+

欢迎大家参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!

+ +# 127. 单词接龙 + +[力扣题目链接](https://leetcode-cn.com/problems/word-ladder/) + + +字典 wordList 中从单词 beginWord 和 endWord 的 转换序列 是一个按下述规格形成的序列: +* 序列中第一个单词是 beginWord 。 +* 序列中最后一个单词是 endWord 。 +* 每次转换只能改变一个字母。 +* 转换过程中的中间单词必须是字典 wordList 中的单词。 +* 给你两个单词 beginWord 和 endWord 和一个字典 wordList ,找到从 beginWord 到 endWord 的 最短转换序列 中的 单词数目 。如果不存在这样的转换序列,返回 0。 + +  +示例 1: + +* 输入:beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log","cog"] +* 输出:5 +* 解释:一个最短转换序列是 "hit" -> "hot" -> "dot" -> "dog" -> "cog", 返回它的长度 5。 + +示例 2: +* 输入:beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log"] +* 输出:0 +* 解释:endWord "cog" 不在字典中,所以无法进行转换。 + + +# 思路 + +以示例1为例,从这个图中可以看出 hit 到 cog的路线,不止一条,有三条,两条是最短的长度为5,一条长度为6。 + +![](https://code-thinking-1253855093.file.myqcloud.com/pics/20210827175432.png) + +本题只需要求出最短长度就可以了,不用找出路径。 + +所以这道题要解决两个问题: + +* 图中的线是如何连在一起的 +* 起点和终点的最短路径长度 + + +首先题目中并没有给出点与点之间的连线,而是要我们自己去连,条件是字符只能差一个,所以判断点与点之间的关系,要自己判断是不是差一个字符,如果差一个字符,那就是有链接。 + +然后就是求起点和终点的最短路径长度,**这里无向图求最短路,广搜最为合适,广搜只要搜到了终点,那么一定是最短的路径**。因为广搜就是以起点中心向四周扩散的搜索。 + +本题如果用深搜,会非常麻烦。 + +另外需要有一个注意点: + +* 本题是一个无向图,需要用标记位,标记着节点是否走过,否则就会死循环! +* 本题给出集合是数组型的,可以转成set结构,查找更快一些 + +C++代码如下:(详细注释) + +```CPP +class Solution { +public: + int ladderLength(string beginWord, string endWord, vector& wordList) { + // 将vector转成unordered_set,提高查询速度 + unordered_set wordSet(wordList.begin(), wordList.end()); + // 如果endWord没有在wordSet出现,直接返回0 + if (wordSet.find(endWord) == wordSet.end()) return 0; + // 记录word是否访问过 + unordered_map visitMap; // + // 初始化队列 + queue que; + que.push(beginWord); + // 初始化visitMap + visitMap.insert(pair(beginWord, 1)); + + while(!que.empty()) { + string word = que.front(); + que.pop(); + int path = visitMap[word]; // 这个word的路径长度 + for (int i = 0; i < word.size(); i++) { + string newWord = word; // 用一个新单词替换word,因为每次置换一个字母 + for (int j = 0 ; j < 26; j++) { + newWord[i] = j + 'a'; + if (newWord == endWord) return path + 1; // 找到了end,返回path+1 + // wordSet出现了newWord,并且newWord没有被访问过 + if (wordSet.find(newWord) != wordSet.end() + && visitMap.find(newWord) == visitMap.end()) { + // 添加访问信息 + visitMap.insert(pair(newWord, path + 1)); + que.push(newWord); + } + } + } + } + return 0; + } +}; +``` + +# 其他语言版本 + +## Java + +```java +public int ladderLength(String beginWord, String endWord, List wordList) { + HashSet wordSet = new HashSet<>(wordList); //转换为hashset 加快速度 + if (wordSet.size() == 0 || !wordSet.contains(endWord)) { //特殊情况判断 + return 0; + } + Queue queue = new LinkedList<>(); //bfs 队列 + queue.offer(beginWord); + Map map = new HashMap<>(); //记录单词对应路径长度 + map.put(beginWord, 1); + + while (!queue.isEmpty()) { + String word = queue.poll(); //取出队头单词 + int path = map.get(word); //获取到该单词的路径长度 + for (int i = 0; i < word.length(); i++) { //遍历单词的每个字符 + char[] chars = word.toCharArray(); //将单词转换为char array,方便替换 + for (char k = 'a'; k <= 'z'; k++) { //从'a' 到 'z' 遍历替换 + chars[i] = k; //替换第i个字符 + String newWord = String.valueOf(chars); //得到新的字符串 + if (newWord.equals(endWord)) { //如果新的字符串值与endWord一致,返回当前长度+1 + return path + 1; + } + if (wordSet.contains(newWord) && !map.containsKey(newWord)) { //如果新单词在set中,但是没有访问过 + map.put(newWord, path + 1); //记录单词对应的路径长度 + queue.offer(newWord);//加入队尾 + } + } + } + } + return 0; //未找到 +} +``` + +## Python + +## Go + +## JavaScript + + +----------------------- +* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) +* B站视频:[代码随想录](https://space.bilibili.com/525438321) +* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) +
diff --git a/problems/0129.求根到叶子节点数字之和.md b/problems/0129.求根到叶子节点数字之和.md index f8c93382..7f50c885 100644 --- a/problems/0129.求根到叶子节点数字之和.md +++ b/problems/0129.求根到叶子节点数字之和.md @@ -1,13 +1,13 @@ ## 链接 -https://leetcode-cn.com/problems/sum-root-to-leaf-numbers/ +[力扣题目链接](https://leetcode-cn.com/problems/sum-root-to-leaf-numbers/) ## 思路 -本题和[113.路径总和II](https://mp.weixin.qq.com/s/6TWAVjxQ34kVqROWgcRFOg)是类似的思路,做完这道题,可以顺便把[113.路径总和II](https://mp.weixin.qq.com/s/6TWAVjxQ34kVqROWgcRFOg) 和 [112.路径总和](https://mp.weixin.qq.com/s/6TWAVjxQ34kVqROWgcRFOg) 做了。 +本题和[113.路径总和II](https://programmercarl.com/0112.路径总和.html#_113-路径总和ii)是类似的思路,做完这道题,可以顺便把[113.路径总和II](https://programmercarl.com/0112.路径总和.html#_113-路径总和ii) 和 [112.路径总和](https://programmercarl.com/0112.路径总和.html#_112-路径总和) 做了。 -结合112.路径总和 和 113.路径总和II,我在讲了[二叉树:递归函数究竟什么时候需要返回值,什么时候不要返回值?](https://mp.weixin.qq.com/s/6TWAVjxQ34kVqROWgcRFOg),如果大家对二叉树递归函数什么时候需要返回值很迷茫,可以看一下。 +结合112.路径总和 和 113.路径总和II,我在讲了[二叉树:递归函数究竟什么时候需要返回值,什么时候不要返回值?](https://programmercarl.com/0112.路径总和.html),如果大家对二叉树递归函数什么时候需要返回值很迷茫,可以看一下。 -接下来在看本题,就简单多了,本题其实需要使用回溯,但一些同学可能都不知道自己用了回溯,在[二叉树:以为使用了递归,其实还隐藏着回溯](https://mp.weixin.qq.com/s/ivLkHzWdhjQQD1rQWe6zWA)中,我详细讲解了二叉树的递归中,如何使用了回溯。 +接下来在看本题,就简单多了,本题其实需要使用回溯,但一些同学可能都不知道自己用了回溯,在[二叉树:以为使用了递归,其实还隐藏着回溯](https://programmercarl.com/二叉树中递归带着回溯.html)中,我详细讲解了二叉树的递归中,如何使用了回溯。 接下来我们来看题: @@ -17,11 +17,11 @@ https://leetcode-cn.com/problems/sum-root-to-leaf-numbers/ ### 递归三部曲 -如果对递归三部曲不了解的话,可以看这里:[二叉树:前中后递归详解](https://mp.weixin.qq.com/s/PwVIfxDlT3kRgMASWAMGhA) +如果对递归三部曲不了解的话,可以看这里:[二叉树:前中后递归详解](https://programmercarl.com/二叉树的递归遍历.html) * 确定递归函数返回值及其参数 -这里我们要遍历整个二叉树,且需要要返回值做逻辑处理,所有返回值为void,在[二叉树:递归函数究竟什么时候需要返回值,什么时候不要返回值?](https://mp.weixin.qq.com/s/6TWAVjxQ34kVqROWgcRFOg)中,详细讲解了返回值问题。 +这里我们要遍历整个二叉树,且需要要返回值做逻辑处理,所有返回值为void,在[二叉树:递归函数究竟什么时候需要返回值,什么时候不要返回值?](https://programmercarl.com/0112.路径总和.html)中,详细讲解了返回值问题。 参数只需要把根节点传入,此时还需要定义两个全局遍历,一个是result,记录最终结果,一个是vector path。 @@ -52,7 +52,7 @@ if (!cur->left && !cur->right) { // 遇到了叶子节点 这里vectorToInt函数就是把数组转成int,代码如下: -```C++ +```CPP int vectorToInt(const vector& vec) { int sum = 0; for (int i = 0; i < vec.size(); i++) { @@ -78,7 +78,7 @@ int vectorToInt(const vector& vec) { 代码如下: -```C++ +```CPP // 中 if (cur->left) { // 左 (空节点不遍历) path.push_back(cur->left->val); @@ -94,7 +94,7 @@ if (cur->right) { // 右 (空节点不遍历) 这里要注意回溯和递归要永远在一起,一个递归,对应一个回溯,是一对一的关系,有的同学写成如下代码: -```C++ +```CPP if (cur->left) { // 左 (空节点不遍历) path.push_back(cur->left->val); traversal(cur->left); // 递归 @@ -111,7 +111,7 @@ path.pop_back(); // 回溯 关键逻辑分析完了,整体C++代码如下: -```C++ +```CPP class Solution { private: int result; @@ -164,8 +164,79 @@ public: Java: -Python: +```java +class Solution { + List path = new ArrayList<>(); + int res = 0; + public int sumNumbers(TreeNode root) { + // 如果节点为0,那么就返回0 + if (root == null) return 0; + // 首先将根节点放到集合中 + path.add(root.val); + // 开始递归 + recur(root); + return res; + } + public void recur(TreeNode root){ + if (root.left == null && root.right == null) { + // 当是叶子节点的时候,开始处理 + res += listToInt(path); + return; + } + + if (root.left != null){ + // 注意有回溯 + path.add(root.left.val); + recur(root.left); + path.remove(path.size() - 1); + } + if (root.right != null){ + // 注意有回溯 + path.add(root.right.val); + recur(root.right); + path.remove(path.size() - 1); + } + return; + } + public int listToInt(List path){ + int sum = 0; + for (Integer num:path){ + // sum * 10 表示进位 + sum = sum * 10 + num; + } + return sum; + } +} +``` + +Python: +```python3 +class Solution: + def sumNumbers(self, root: TreeNode) -> int: + res = 0 + path = [] + def backtrace(root): + nonlocal res + if not root: return # 节点空则返回 + path.append(root.val) + if not root.left and not root.right: # 遇到了叶子节点 + res += get_sum(path) + if root.left: # 左子树不空 + backtrace(root.left) + if root.right: # 右子树不空 + backtrace(root.right) + path.pop() + + def get_sum(arr): + s = 0 + for i in range(len(arr)): + s = s * 10 + arr[i] + return s + + backtrace(root) + return res +``` Go: JavaScript: @@ -176,4 +247,4 @@ JavaScript: * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0131.分割回文串.md b/problems/0131.分割回文串.md index 43453409..f2e108e8 100644 --- a/problems/0131.分割回文串.md +++ b/problems/0131.分割回文串.md @@ -11,7 +11,7 @@ ## 131.分割回文串 -题目链接:https://leetcode-cn.com/problems/palindrome-partitioning/ +[力扣题目链接](https://leetcode-cn.com/problems/palindrome-partitioning/) 给定一个字符串 s,将 s 分割成一些子串,使每个子串都是回文串。 @@ -66,11 +66,11 @@ 本题递归函数参数还需要startIndex,因为切割过的地方,不能重复切割,和组合问题也是保持一致的。 -在[回溯算法:求组合总和(二)](https://mp.weixin.qq.com/s/FLg8G6EjVcxBjwCbzpACPw)中我们深入探讨了组合问题什么时候需要startIndex,什么时候不需要startIndex。 +在[回溯算法:求组合总和(二)](https://programmercarl.com/0039.组合总和.html)中我们深入探讨了组合问题什么时候需要startIndex,什么时候不需要startIndex。 代码如下: -```C++ +```CPP vector> result; vector path; // 放已经回文的子串 void backtracking (const string& s, int startIndex) { @@ -88,7 +88,7 @@ void backtracking (const string& s, int startIndex) { 所以终止条件代码如下: -```C++ +```CPP void backtracking (const string& s, int startIndex) { // 如果起始位置已经大于s的大小,说明已经找到了一组分割方案了 if (startIndex >= s.size()) { @@ -108,7 +108,7 @@ void backtracking (const string& s, int startIndex) { 代码如下: -```C++ +```CPP for (int i = startIndex; i < s.size(); i++) { if (isPalindrome(s, startIndex, i)) { // 是回文子串 // 获取[startIndex,i]在s中的子串 @@ -132,7 +132,7 @@ for (int i = startIndex; i < s.size(); i++) { 那么判断回文的C++代码如下: -```C++ +```CPP bool isPalindrome(const string& s, int start, int end) { for (int i = start, j = end; i < j; i++, j--) { if (s[i] != s[j]) { @@ -143,7 +143,7 @@ for (int i = startIndex; i < s.size(); i++) { } ``` -如果大家对双指针法有生疏了,传送门:[双指针法:总结篇!](https://mp.weixin.qq.com/s/_p7grwjISfMh0U65uOyCjA) +如果大家对双指针法有生疏了,传送门:[双指针法:总结篇!](https://programmercarl.com/双指针总结.html) 此时关键代码已经讲解完毕,整体代码如下(详细注释了) @@ -151,7 +151,7 @@ for (int i = startIndex; i < s.size(); i++) { 根据Carl给出的回溯算法模板: -```C++ +```CPP void backtracking(参数) { if (终止条件) { 存放结果; @@ -169,7 +169,7 @@ void backtracking(参数) { 不难写出如下代码: -```C++ +```CPP class Solution { private: vector> result; @@ -292,7 +292,8 @@ class Solution { ``` Python: -```py +```python +# 版本一 class Solution: def partition(self, s: str) -> List[List[str]]: res = [] @@ -310,7 +311,36 @@ class Solution: return res ``` - +```python +# 版本二 +class Solution: + def partition(self, s: str) -> List[List[str]]: + res = [] + path = [] #放已经回文的子串 + # 双指针法判断是否是回文串 + def isPalindrome(s): + n = len(s) + i, j = 0, n - 1 + while i < j: + if s[i] != s[j]:return False + i += 1 + j -= 1 + return True + + def backtrack(s, startIndex): + if startIndex >= len(s): # 如果起始位置已经大于s的大小,说明已经找到了一组分割方案了 + res.append(path[:]) + return + for i in range(startIndex, len(s)): + p = s[startIndex:i+1] # 获取[startIndex,i+1]在s中的子串 + if isPalindrome(p): # 是回文子串 + path.append(p) + else: continue #不是回文,跳过 + backtrack(s, i + 1) + path.pop() #回溯过程,弹出本次已经填在path的子串 + backtrack(s, 0) + return res +``` Go: > 注意切片(go切片是披着值类型外衣的引用类型) @@ -395,4 +425,4 @@ var partition = function(s) { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0132.分割回文串II.md b/problems/0132.分割回文串II.md index 856262d6..dbd830d0 100644 --- a/problems/0132.分割回文串II.md +++ b/problems/0132.分割回文串II.md @@ -10,7 +10,7 @@ # 132. 分割回文串 II -链接:https://leetcode-cn.com/problems/palindrome-partitioning-ii/ +[力扣题目链接](https://leetcode-cn.com/problems/palindrome-partitioning-ii/) 给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是回文。 @@ -29,7 +29,7 @@ 示例 3: 输入:s = "ab" 输出:1 -  + 提示: @@ -38,7 +38,7 @@ # 思路 -我们在讲解回溯法系列的时候,讲过了这道题目[回溯算法:131.分割回文串](https://mp.weixin.qq.com/s/Pb1epUTbU8fHIht-g_MS5Q)。 +我们在讲解回溯法系列的时候,讲过了这道题目[回溯算法:131.分割回文串](https://programmercarl.com/0131.分割回文串.html)。 本题呢其实也可以使用回溯法,只不过会超时!(通过记忆化回溯,也可以过,感兴趣的同学可以自行研究一下) @@ -46,7 +46,7 @@ 关于回文子串,两道题目题目大家是一定要掌握的。 -* [动态规划:647. 回文子串](https://mp.weixin.qq.com/s/2WetyP6IYQ6VotegepVpEw) +* [动态规划:647. 回文子串](https://programmercarl.com/0647.回文子串.html) * 5.最长回文子串 和 647.回文子串基本一样的 这两道题目是回文子串的基础题目,本题也要用到相关的知识点。 @@ -101,7 +101,7 @@ dp[i]: 范围是[0, i]的回文子串,最少分割次数是dp[i]。 代码如下: -```C++ +```CPP vector dp(s.size(), INT_MAX); dp[0] = 0; ``` @@ -109,7 +109,7 @@ dp[0] = 0; 其实也可以这样初始化,更具dp[i]的定义,dp[i]的最大值其实就是i,也就是把每个字符分割出来。 所以初始化代码也可以为: -```C++ +```CPP vector dp(s.size()); for (int i = 0; i < s.size(); i++) dp[i] = i; ``` @@ -122,7 +122,7 @@ j是在[0,i]之间,所以遍历i的for循环一定在外层,这里遍历j 代码如下: -```C++ +```CPP for (int i = 1; i < s.size(); i++) { if (isPalindromic[0][i]) { // 判断是不是回文子串 dp[i] = 0; @@ -149,7 +149,7 @@ for (int i = 1; i < s.size(); i++) { 代码如下: -```C++ +```CPP vector> isPalindromic(s.size(), vector(s.size(), false)); for (int i = s.size() - 1; i >= 0; i--) { for (int j = i; j < s.size(); j++) { @@ -168,7 +168,7 @@ for (int i = s.size() - 1; i >= 0; i--) { 以上分析完毕,代码如下: -```C++ +```CPP class Solution { public: int minCut(string s) { @@ -252,5 +252,5 @@ class Solution: * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0134.加油站.md b/problems/0134.加油站.md index 9b660ea0..526efb14 100644 --- a/problems/0134.加油站.md +++ b/problems/0134.加油站.md @@ -9,7 +9,7 @@ ## 134. 加油站 -题目链接:https://leetcode-cn.com/problems/gas-station/ +[力扣题目链接](https://leetcode-cn.com/problems/gas-station/) 在一条环路上有 N 个加油站,其中第 i 个加油站有汽油 gas[i] 升。 @@ -65,7 +65,7 @@ cost = [3,4,3] C++代码如下: -```C++ +```CPP class Solution { public: int canCompleteCircuit(vector& gas, vector& cost) { @@ -99,7 +99,7 @@ C++暴力解法在leetcode上提交也可以过。 C++代码如下: -```C++ +```CPP class Solution { public: int canCompleteCircuit(vector& gas, vector& cost) { @@ -160,7 +160,7 @@ i从0开始累加rest[i],和记为curSum,一旦curSum小于零,说明[0, i C++代码如下: -```C++ +```CPP class Solution { public: int canCompleteCircuit(vector& gas, vector& cost) { @@ -200,6 +200,7 @@ public: Java: ```java +// 解法1 class Solution { public int canCompleteCircuit(int[] gas, int[] cost) { int sum = 0; @@ -221,7 +222,26 @@ class Solution { } } ``` - +```java +// 解法2 +class Solution { + public int canCompleteCircuit(int[] gas, int[] cost) { + int curSum = 0; + int totalSum = 0; + int index = 0; + for (int i = 0; i < gas.length; i++) { + curSum += gas[i] - cost[i]; + totalSum += gas[i] - cost[i]; + if (curSum < 0) { + index = (i + 1) % gas.length ; + curSum = 0; + } + } + if (totalSum < 0) return -1; + return index; + } +} +``` Python: ```python class Solution: @@ -261,6 +281,48 @@ func canCompleteCircuit(gas []int, cost []int) int { ``` Javascript: +暴力: +```js +var canCompleteCircuit = function(gas, cost) { + for(let i = 0; i < cost.length; i++) { + let rest = gas[i] - cost[i] //记录剩余油量 + // 以i为起点行驶一圈,index为下一个目的地 + let index = (i + 1) % cost.length + while(rest > 0 && index !== i) { + rest += gas[index] - cost[index] + index = (index + 1) % cost.length + } + if(rest >= 0 && index === i) return i + } + return -1 +}; +``` +解法一: +```js +var canCompleteCircuit = function(gas, cost) { + let curSum = 0 + let min = Infinity + for(let i = 0; i < gas.length; i++) { + let rest = gas[i] - cost[i] + curSum += rest + if(curSum < min) { + min = curSum + } + } + if(curSum < 0) return -1 //1.总油量 小于 总消耗量 + if(min >= 0) return 0 //2. 说明油箱里油没断过 + //3. 从后向前,看哪个节点能这个负数填平,能把这个负数填平的节点就是出发节点 + for(let i = gas.length -1; i >= 0; i--) { + let rest = gas[i] - cost[i] + min += rest + if(min >= 0) { + return i + } + } + return -1 +} +``` +解法二: ```Javascript var canCompleteCircuit = function(gas, cost) { const gasLen = gas.length @@ -283,9 +345,38 @@ var canCompleteCircuit = function(gas, cost) { }; ``` +C: +```c +int canCompleteCircuit(int* gas, int gasSize, int* cost, int costSize){ + int curSum = 0; + int i; + int min = INT_MAX; + //遍历整个数组。计算出每站的用油差。并将其与最小累加量比较 + for(i = 0; i < gasSize; i++) { + int diff = gas[i] - cost[i]; + curSum += diff; + if(curSum < min) + min = curSum; + } + //若汽油总数为负数,代表无法跑完一环。返回-1 + if(curSum < 0) + return -1; + //若min大于等于0,说明每一天加油量比用油量多。因此从0出发即可 + if(min >= 0) + return 0; + //若累加最小值为负,则找到一个非零元素(加油量大于出油量)出发。返回坐标 + for(i = gasSize - 1; i >= 0; i--) { + min+=(gas[i]-cost[i]); + if(min >= 0) + return i; + } + //逻辑上不会返回这个0 + return 0; +} +``` ----------------------- * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0135.分发糖果.md b/problems/0135.分发糖果.md index 2d3fca84..526b5870 100644 --- a/problems/0135.分发糖果.md +++ b/problems/0135.分发糖果.md @@ -9,7 +9,7 @@ ## 135. 分发糖果 -链接:https://leetcode-cn.com/problems/candy/ +[力扣题目链接](https://leetcode-cn.com/problems/candy/) 老师想给孩子们分发糖果,有 N 个孩子站成了一条直线,老师会根据每个孩子的表现,预先给他们评分。 @@ -47,7 +47,7 @@ 代码如下: -```C++ +```CPP // 从前向后 for (int i = 1; i < ratings.size(); i++) { if (ratings[i] > ratings[i - 1]) candyVec[i] = candyVec[i - 1] + 1; @@ -80,7 +80,7 @@ for (int i = 1; i < ratings.size(); i++) { 所以该过程代码如下: -```C++ +```CPP // 从后向前 for (int i = ratings.size() - 2; i >= 0; i--) { if (ratings[i] > ratings[i + 1] ) { @@ -90,7 +90,7 @@ for (int i = ratings.size() - 2; i >= 0; i--) { ``` 整体代码如下: -```C++ +```CPP class Solution { public: int candy(vector& ratings) { @@ -242,4 +242,4 @@ var candy = function(ratings) { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0139.单词拆分.md b/problems/0139.单词拆分.md index 4ba08aa9..969d2ce7 100644 --- a/problems/0139.单词拆分.md +++ b/problems/0139.单词拆分.md @@ -10,7 +10,7 @@ ## 139.单词拆分 -题目链接:https://leetcode-cn.com/problems/word-break/ +[力扣题目链接](https://leetcode-cn.com/problems/word-break/) 给定一个非空字符串 s 和一个包含非空单词的列表 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。 @@ -37,15 +37,15 @@ ## 思路 -看到这道题目的时候,大家应该回想起我们之前讲解回溯法专题的时候,讲过的一道题目[回溯算法:分割回文串](https://mp.weixin.qq.com/s/Pb1epUTbU8fHIht-g_MS5Q),就是枚举字符串的所有分割情况。 +看到这道题目的时候,大家应该回想起我们之前讲解回溯法专题的时候,讲过的一道题目[回溯算法:分割回文串](https://programmercarl.com/0131.分割回文串.html),就是枚举字符串的所有分割情况。 -[回溯算法:分割回文串](https://mp.weixin.qq.com/s/Pb1epUTbU8fHIht-g_MS5Q):是枚举分割后的所有子串,判断是否回文。 +[回溯算法:分割回文串](https://programmercarl.com/0131.分割回文串.html):是枚举分割后的所有子串,判断是否回文。 本道是枚举分割所有字符串,判断是否在字典里出现过。 那么这里我也给出回溯法C++代码: -```C++ +```CPP class Solution { private: bool backtracking (const string& s, const unordered_set& wordSet, int startIndex) { @@ -86,7 +86,7 @@ public: C++代码如下: -```C++ +```CPP class Solution { private: bool backtracking (const string& s, @@ -161,11 +161,11 @@ dp[0]表示如果字符串为空的话,说明出现在字典里。 **如果求排列数就是外层for遍历背包,内层for循环遍历物品**。 -对这个结论还有疑问的同学可以看这篇[本周小结!(动态规划系列五)](https://mp.weixin.qq.com/s/znj-9j8mWymRFaPjJN2Qnw),这篇本周小节中,我做了如下总结: +对这个结论还有疑问的同学可以看这篇[本周小结!(动态规划系列五)](https://programmercarl.com/%E5%91%A8%E6%80%BB%E7%BB%93/20210204动规周末总结.html),这篇本周小节中,我做了如下总结: -求组合数:[动态规划:518.零钱兑换II](https://mp.weixin.qq.com/s/PlowDsI4WMBOzf3q80AksQ) -求排列数:[动态规划:377. 组合总和 Ⅳ](https://mp.weixin.qq.com/s/Iixw0nahJWQgbqVNk8k6gA)、[动态规划:70. 爬楼梯进阶版(完全背包)](https://mp.weixin.qq.com/s/e_wacnELo-2PG76EjrUakA) -求最小数:[动态规划:322. 零钱兑换](https://mp.weixin.qq.com/s/dyk-xNilHzNtVdPPLObSeQ)、[动态规划:279.完全平方数](https://mp.weixin.qq.com/s/VfJT78p7UGpDZsapKF_QJQ) +求组合数:[动态规划:518.零钱兑换II](https://programmercarl.com/0518.零钱兑换II.html) +求排列数:[动态规划:377. 组合总和 Ⅳ](https://programmercarl.com/0377.组合总和.html)、[动态规划:70. 爬楼梯进阶版(完全背包)](https://programmercarl.com/0070.爬楼梯完全背包版本.html) +求最小数:[动态规划:322. 零钱兑换](https://programmercarl.com/0322.零钱兑换.html)、[动态规划:279.完全平方数](https://programmercarl.com/0279.完全平方数.html) 本题最终要求的是是否都出现过,所以对出现单词集合里的元素是组合还是排列,并不在意! @@ -190,7 +190,7 @@ dp[s.size()]就是最终结果。 动规五部曲分析完毕,C++代码如下: -```C++ +```CPP class Solution { public: bool wordBreak(string s, vector& wordDict) { @@ -215,7 +215,7 @@ public: ## 总结 -本题和我们之前讲解回溯专题的[回溯算法:分割回文串](https://mp.weixin.qq.com/s/Pb1epUTbU8fHIht-g_MS5Q)非常像,所以我也给出了对应的回溯解法。 +本题和我们之前讲解回溯专题的[回溯算法:分割回文串](https://programmercarl.com/0131.分割回文串.html)非常像,所以我也给出了对应的回溯解法。 稍加分析,便可知道本题是完全背包,而且是求能否组成背包,所以遍历顺序理论上来讲 两层for循环谁先谁后都可以! @@ -319,4 +319,4 @@ const wordBreak = (s, wordDict) => { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0141.环形链表.md b/problems/0141.环形链表.md index 0871202d..34b8d25f 100644 --- a/problems/0141.环形链表.md +++ b/problems/0141.环形链表.md @@ -45,7 +45,7 @@ fast和slow各自再走一步, fast和slow就相遇了 C++代码如下 -```C++ +```CPP class Solution { public: bool hasCycle(ListNode *head) { @@ -66,7 +66,7 @@ public: 做完这道题目,可以在做做142.环形链表II,不仅仅要找环,还要找环的入口。 -142.环形链表II题解:[链表:环找到了,那入口呢?](https://mp.weixin.qq.com/s/gt_VH3hQTqNxyWcl1ECSbQ) +142.环形链表II题解:[链表:环找到了,那入口呢?](https://programmercarl.com/0142.环形链表II.html) # 其他语言版本 @@ -120,5 +120,5 @@ class Solution: * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0142.环形链表II.md b/problems/0142.环形链表II.md index 9deb1e0c..bfa779a2 100644 --- a/problems/0142.环形链表II.md +++ b/problems/0142.环形链表II.md @@ -13,7 +13,7 @@ ## 142.环形链表II -https://leetcode-cn.com/problems/linked-list-cycle-ii/ +[力扣题目链接](https://leetcode-cn.com/problems/linked-list-cycle-ii/) 题意: 给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。 @@ -109,7 +109,7 @@ fast指针走过的节点数:` x + y + n (y + z)`,n为fast指针在环内走 代码如下: -```C++ +```CPP /** * Definition for singly-linked list. * struct ListNode { @@ -146,7 +146,7 @@ public: 在推理过程中,大家可能有一个疑问就是:**为什么第一次在环中相遇,slow的 步数 是 x+y 而不是 x + 若干环的长度 + y 呢?** -即文章[链表:环找到了,那入口呢?](https://mp.weixin.qq.com/s/_QVP3IkRZWx9zIpQRgajzA)中如下的地方: +即文章[链表:环找到了,那入口呢?](https://programmercarl.com/0142.环形链表II.html)中如下的地方: ![142环形链表5](https://img-blog.csdnimg.cn/20210318165123581.png) @@ -175,7 +175,7 @@ public: 那有同学又说了,为什么fast不能跳过去呢? 在刚刚已经说过一次了,**fast相对于slow是一次移动一个节点,所以不可能跳过去**。 -好了,这次把为什么第一次在环中相遇,slow的 步数 是 x+y 而不是 x + 若干环的长度 + y ,用数学推理了一下,算是对[链表:环找到了,那入口呢?](https://mp.weixin.qq.com/s/_QVP3IkRZWx9zIpQRgajzA)的补充。 +好了,这次把为什么第一次在环中相遇,slow的 步数 是 x+y 而不是 x + 若干环的长度 + y ,用数学推理了一下,算是对[链表:环找到了,那入口呢?](https://programmercarl.com/0142.环形链表II.html)的补充。 ## 总结 @@ -296,9 +296,42 @@ var detectCycle = function(head) { }; ``` +Swift: +```swift +class Solution { + func detectCycle(_ head: ListNode?) -> ListNode? { + var slow: ListNode? = head + var fast: ListNode? = head + while fast != nil && fast?.next != nil { + slow = slow?.next + fast = fast?.next?.next + if slow == fast { + // 环内相遇 + var list1: ListNode? = slow + var list2: ListNode? = head + while list1 != list2 { + list1 = list1?.next + list2 = list2?.next + } + return list2 + } + } + return nil + } +} +extension ListNode: Equatable { + public func hash(into hasher: inout Hasher) { + hasher.combine(val) + hasher.combine(ObjectIdentifier(self)) + } + public static func == (lhs: ListNode, rhs: ListNode) -> Bool { + return lhs === rhs + } +} +``` ----------------------- * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0143.重排链表.md b/problems/0143.重排链表.md index c4e8d8f7..2b4e68b7 100644 --- a/problems/0143.重排链表.md +++ b/problems/0143.重排链表.md @@ -1,4 +1,3 @@ -

@@ -25,7 +24,7 @@ 代码如下: -```C++ +```CPP class Solution { public: void reorderList(ListNode* head) { @@ -63,7 +62,8 @@ public: ## 方法二 把链表放进双向队列,然后通过双向队列一前一后弹出数据,来构造新的链表。这种方法比操作数组容易一些,不用双指针模拟一前一后了 -```C++ + +```CPP class Solution { public: void reorderList(ListNode* head) { @@ -108,7 +108,7 @@ public: 代码如下: -```C++ +```CPP class Solution { private: // 反转链表 @@ -176,15 +176,187 @@ public: Java: -Python: +```java +// 方法三 +public class ReorderList { + public void reorderList(ListNode head) { + ListNode fast = head, slow = head; + //求出中点 + while (fast.next != null && fast.next.next != null) { + slow = slow.next; + fast = fast.next.next; + } + //right就是右半部分 12345 就是45 1234 就是34 + ListNode right = slow.next; + //断开左部分和右部分 + slow.next = null; + //反转右部分 right就是反转后右部分的起点 + right = reverseList(right); + //左部分的起点 + ListNode left = head; + //进行左右部分来回连接 + //这里左部分的节点个数一定大于等于右部分的节点个数 因此只判断right即可 + while (right != null) { + ListNode curLeft = left.next; + left.next = right; + left = curLeft; + ListNode curRight = right.next; + right.next = left; + right = curRight; + } + } + + public ListNode reverseList(ListNode head) { + ListNode headNode = new ListNode(0); + ListNode cur = head; + ListNode next = null; + while (cur != null) { + next = cur.next; + cur.next = headNode.next; + headNode.next = cur; + cur = next; + } + return headNode.next; + } +} + +------------------------------------------------------------------------- +// 方法一 Java实现,使用数组存储节点 + class Solution { + public void reorderList(ListNode head) { + // 双指针的做法 + ListNode cur = head; + // ArrayList底层是数组,可以使用下标随机访问 + List list = new ArrayList<>(); + while (cur != null){ + list.add(cur); + cur = cur.next; + } + cur = head; // 重新回到头部 + int l = 1, r = list.size() - 1; // 注意左边是从1开始 + int count = 0; + while (l <= r){ + if (count % 2 == 0){ + // 偶数 + cur.next = list.get(r); + r--; + }else { + // 奇数 + cur.next = list.get(l); + l++; + } + // 每一次指针都需要移动 + cur = cur.next; + count++; + } + // 当是偶数的话,需要做额外处理 + if (list.size() % 2== 0){ + cur.next = list.get(l); + cur = cur.next; + } + + // 注意结尾要结束一波 + cur.next = null; + } +} +------------------------------------------------------------------------- +// 方法二:使用双端队列,简化了数组的操作,代码相对于前者更简洁(避免一些边界条件) +class Solution { + public void reorderList(ListNode head) { + // 使用双端队列的方法来解决 + Deque de = new LinkedList<>(); + // 这里是取head的下一个节点,head不需要再入队了,避免造成重复 + ListNode cur = head.next; + while (cur != null){ + de.offer(cur); + cur = cur.next; + } + cur = head; // 回到头部 + + int count = 0; + while (!de.isEmpty()){ + if (count % 2 == 0){ + // 偶数,取出队列右边尾部的值 + cur.next = de.pollLast(); + }else { + // 奇数,取出队列左边头部的值 + cur.next = de.poll(); + } + cur = cur.next; + count++; + } + cur.next = null; + } +} + +``` + +Python: +```python3 +# 方法二 双向队列 +class Solution: + def reorderList(self, head: ListNode) -> None: + """ + Do not return anything, modify head in-place instead. + """ + d = collections.deque() + tmp = head + while tmp.next: # 链表除了首元素全部加入双向队列 + d.append(tmp.next) + tmp = tmp.next + tmp = head + while len(d): # 一后一前加入链表 + tmp.next = d.pop() + tmp = tmp.next + if len(d): + tmp.next = d.popleft() + tmp = tmp.next + tmp.next = None # 尾部置空 + +# 方法三 反转链表 +class Solution: + def reorderList(self, head: ListNode) -> None: + if head == None or head.next == None: + return True + slow, fast = head, head + while fast and fast.next: + slow = slow.next + fast = fast.next.next + right = slow.next # 分割右半边 + slow.next = None # 切断 + right = self.reverseList(right) #反转右半边 + left = head + # 左半边一定比右半边长, 因此判断右半边即可 + while right: + curLeft = left.next + left.next = right + left = curLeft + + curRight = right.next + right.next = left + right = curRight + + + def reverseList(self, head: ListNode) -> ListNode: + cur = head + pre = None + while(cur!=None): + temp = cur.next # 保存一下cur的下一个节点 + cur.next = pre # 反转 + pre = cur + cur = temp + return pre +``` Go: JavaScript: ----------------------- + * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -

+ +
diff --git a/problems/0150.逆波兰表达式求值.md b/problems/0150.逆波兰表达式求值.md index aceb91b4..36652109 100644 --- a/problems/0150.逆波兰表达式求值.md +++ b/problems/0150.逆波兰表达式求值.md @@ -13,7 +13,7 @@ # 150. 逆波兰表达式求值 -https://leetcode-cn.com/problems/evaluate-reverse-polish-notation/ +[力扣题目链接](https://leetcode-cn.com/problems/evaluate-reverse-polish-notation/) 根据 逆波兰表示法,求表达式的值。 @@ -23,7 +23,7 @@ https://leetcode-cn.com/problems/evaluate-reverse-polish-notation/ 整数除法只保留整数部分。 给定逆波兰表达式总是有效的。换句话说,表达式总会得出有效数值且不存在除数为 0 的情况。 -  + 示例 1: * 输入: ["2", "1", "+", "3", " * "] @@ -37,16 +37,21 @@ https://leetcode-cn.com/problems/evaluate-reverse-polish-notation/ 示例 3: * 输入: ["10", "6", "9", "3", "+", "-11", " * ", "/", " * ", "17", "+", "5", "+"] + * 输出: 22 + * 解释:该算式转化为常见的中缀算术表达式为: + + ``` ((10 * (6 / ((9 + 3) * -11))) + 17) + 5 -= ((10 * (6 / (12 * -11))) + 17) + 5 -= ((10 * (6 / -132)) + 17) + 5 -= ((10 * 0) + 17) + 5 -= (0 + 17) + 5 -= 17 + 5 -= 22 -  + = ((10 * (6 / (12 * -11))) + 17) + 5 + = ((10 * (6 / -132)) + 17) + 5 + = ((10 * 0) + 17) + 5 + = (0 + 17) + 5 + = 17 + 5 + = 22 + ``` + 逆波兰表达式:是一种后缀表达式,所谓后缀就是指算符写在后面。 @@ -62,7 +67,7 @@ https://leetcode-cn.com/problems/evaluate-reverse-polish-notation/ # 思路 -在上一篇文章中[1047.删除字符串中的所有相邻重复项](https://mp.weixin.qq.com/s/1-x6r1wGA9mqIHW5LrMvBg)提到了 递归就是用栈来实现的。 +在上一篇文章中[1047.删除字符串中的所有相邻重复项](https://programmercarl.com/1047.删除字符串中的所有相邻重复项.html)提到了 递归就是用栈来实现的。 所以**栈与递归之间在某种程度上是可以转换的!** 这一点我们在后续讲解二叉树的时候,会更详细的讲解到。 @@ -70,17 +75,17 @@ https://leetcode-cn.com/problems/evaluate-reverse-polish-notation/ 但我们没有必要从二叉树的角度去解决这个问题,只要知道逆波兰表达式是用后续遍历的方式把二叉树序列化了,就可以了。 -在进一步看,本题中每一个子表达式要得出一个结果,然后拿这个结果再进行运算,那么**这岂不就是一个相邻字符串消除的过程,和[1047.删除字符串中的所有相邻重复项](https://mp.weixin.qq.com/s/1-x6r1wGA9mqIHW5LrMvBg)中的对对碰游戏是不是就非常像了。** +在进一步看,本题中每一个子表达式要得出一个结果,然后拿这个结果再进行运算,那么**这岂不就是一个相邻字符串消除的过程,和[1047.删除字符串中的所有相邻重复项](https://programmercarl.com/1047.删除字符串中的所有相邻重复项.html)中的对对碰游戏是不是就非常像了。** 如动画所示: ![150.逆波兰表达式求值](https://code-thinking.cdn.bcebos.com/gifs/150.逆波兰表达式求值.gif) -相信看完动画大家应该知道,这和[1047. 删除字符串中的所有相邻重复项](https://mp.weixin.qq.com/s/1-x6r1wGA9mqIHW5LrMvBg)是差不错的,只不过本题不要相邻元素做消除了,而是做运算! +相信看完动画大家应该知道,这和[1047. 删除字符串中的所有相邻重复项](https://programmercarl.com/1047.删除字符串中的所有相邻重复项.html)是差不错的,只不过本题不要相邻元素做消除了,而是做运算! C++代码如下: -```C++ +```CPP class Solution { public: int evalRPN(vector& tokens) { @@ -223,17 +228,19 @@ var evalRPN = function(tokens) { python3 ```python -def evalRPN(tokens) -> int: - stack = list() - for i in range(len(tokens)): - if tokens[i] not in ["+", "-", "*", "/"]: - stack.append(tokens[i]) - else: - tmp1 = stack.pop() - tmp2 = stack.pop() - res = eval(tmp2+tokens[i]+tmp1) - stack.append(str(int(res))) - return stack[-1] +class Solution: + def evalRPN(self, tokens: List[str]) -> int: + stack = [] + for item in tokens: + if item not in {"+", "-", "*", "/"}: + stack.append(item) + else: + first_num, second_num = stack.pop(), stack.pop() + stack.append( + int(eval(f'{second_num} {item} {first_num}')) # 第一个出来的在运算符后面 + ) + return int(stack.pop()) # 如果一开始只有一个数,那么会是字符串形式的 + ``` @@ -241,4 +248,4 @@ def evalRPN(tokens) -> int: * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0151.翻转字符串里的单词.md b/problems/0151.翻转字符串里的单词.md index 32d9deef..c4a9c7e0 100644 --- a/problems/0151.翻转字符串里的单词.md +++ b/problems/0151.翻转字符串里的单词.md @@ -12,7 +12,7 @@ # 151.翻转字符串里的单词 -https://leetcode-cn.com/problems/reverse-words-in-a-string/ +[力扣题目链接](https://leetcode-cn.com/problems/reverse-words-in-a-string/) 给定一个字符串,逐个翻转字符串中的每个单词。 @@ -61,7 +61,7 @@ https://leetcode-cn.com/problems/reverse-words-in-a-string/ 思路很明确了,我们说一说代码的实现细节,就拿移除多余空格来说,一些同学会上来写如下代码: -```C++ +```CPP void removeExtraSpaces(string& s) { for (int i = s.size() - 1; i > 0; i--) { if (s[i] == s[i - 1] && s[i] == ' ') { @@ -83,17 +83,17 @@ void removeExtraSpaces(string& s) { 如果不仔细琢磨一下erase的时间复杂读,还以为以上的代码是O(n)的时间复杂度呢。 -想一下真正的时间复杂度是多少,一个erase本来就是O(n)的操作,erase实现原理题目:[数组:就移除个元素很难么?](https://mp.weixin.qq.com/s/RMkulE4NIb6XsSX83ra-Ww),最优的算法来移除元素也要O(n)。 +想一下真正的时间复杂度是多少,一个erase本来就是O(n)的操作,erase实现原理题目:[数组:就移除个元素很难么?](https://programmercarl.com/0027.移除元素.html),最优的算法来移除元素也要O(n)。 erase操作上面还套了一个for循环,那么以上代码移除冗余空格的代码时间复杂度为O(n^2)。 那么使用双指针法来去移除空格,最后resize(重新设置)一下字符串的大小,就可以做到O(n)的时间复杂度。 -如果对这个操作比较生疏了,可以再看一下这篇文章:[数组:就移除个元素很难么?](https://mp.weixin.qq.com/s/RMkulE4NIb6XsSX83ra-Ww)是如何移除元素的。 +如果对这个操作比较生疏了,可以再看一下这篇文章:[数组:就移除个元素很难么?](https://programmercarl.com/0027.移除元素.html)是如何移除元素的。 那么使用双指针来移除冗余空格代码如下: fastIndex走的快,slowIndex走的慢,最后slowIndex就标记着移除多余空格后新字符串的长度。 -```C++ +```CPP void removeExtraSpaces(string& s) { int slowIndex = 0, fastIndex = 0; // 定义快指针,慢指针 // 去掉字符串前面的空格 @@ -125,7 +125,7 @@ void removeExtraSpaces(string& s) { 此时我们已经实现了removeExtraSpaces函数来移除冗余空格。 -还做实现反转字符串的功能,支持反转字符串子区间,这个实现我们分别在[344.反转字符串](https://mp.weixin.qq.com/s/_rNm66OJVl92gBDIbGpA3w)和[541.反转字符串II](https://mp.weixin.qq.com/s/pzXt6PQ029y7bJ9YZB2mVQ)里已经讲过了。 +还做实现反转字符串的功能,支持反转字符串子区间,这个实现我们分别在[344.反转字符串](https://programmercarl.com/0344.反转字符串.html)和[541.反转字符串II](https://programmercarl.com/0541.反转字符串II.html)里已经讲过了。 代码如下: @@ -141,7 +141,7 @@ void reverse(string& s, int start, int end) { 本题C++整体代码 -```C++ +```CPP // 版本一 class Solution { public: @@ -467,6 +467,85 @@ function reverse(strArr, start, end) { } ``` +Swift: + +```swift +func reverseWords(_ s: String) -> String { + var stringArr = removeSpace(s) + reverseString(&stringArr, startIndex: 0, endIndex: stringArr.count - 1) + reverseWord(&stringArr) + return String(stringArr) +} + +/// 1、移除多余的空格(前后所有的空格,中间只留一个空格) +func removeSpace(_ s: String) -> [Character] { + let ch = Array(s) + var left = 0 + var right = ch.count - 1 + // 忽略字符串前面的所有空格 + while ch[left] == " " { + left += 1 + } + // 忽略字符串后面的所有空格 + while ch[right] == " " { + right -= 1 + } + + // 接下来就是要处理中间的多余空格 + var lastArr = Array() + while left <= right { + // 准备加到新字符串当中的字符 + let char = ch[left] + // 新的字符串的最后一个字符;或者原字符串中,准备加到新字符串的那个字符;这两个字符当中,只要有一个不是空格,就可以加到新的字符串当中 + if char != " " || lastArr[lastArr.count - 1] != " " { + lastArr.append(char) + } + + left += 1 + } + return lastArr +} + +/// 2、反转整个字符串 +func reverseString(_ s: inout [Character], startIndex: Int, endIndex: Int) { + var start = startIndex + var end = endIndex + while start < end { + (s[start], s[end]) = (s[end], s[start]) + start += 1 + end -= 1 + } +} + +/// 3、再次将字符串里面的单词反转 +func reverseWord(_ s: inout [Character]) { + var start = 0 + var end = 0 + var entry = false + + for i in 0.. +
diff --git a/problems/0160.相交链表.md b/problems/0160.相交链表.md index d26f66fd..42b2ee56 100644 --- a/problems/0160.相交链表.md +++ b/problems/0160.相交链表.md @@ -1,2 +1,3 @@ -同:[链表:链表相交](./面试题02.07.链表相交.md) +同:[链表:链表相交](https://programmercarl.com/面试题02.07.链表相交.html) +
diff --git a/problems/0188.买卖股票的最佳时机IV.md b/problems/0188.买卖股票的最佳时机IV.md index 46c6f7f0..bcb8a1ab 100644 --- a/problems/0188.买卖股票的最佳时机IV.md +++ b/problems/0188.买卖股票的最佳时机IV.md @@ -8,7 +8,7 @@ ## 188.买卖股票的最佳时机IV -题目链接:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-iv/ +[力扣题目链接](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-iv/) 给定一个整数数组 prices ,它的第 i 个元素 prices[i] 是一支给定的股票在第 i 天的价格。 @@ -35,13 +35,13 @@ ## 思路 -这道题目可以说是[动态规划:123.买卖股票的最佳时机III](https://mp.weixin.qq.com/s/Sbs157mlVDtAR0gbLpdKzg)的进阶版,这里要求至多有k次交易。 +这道题目可以说是[动态规划:123.买卖股票的最佳时机III](https://programmercarl.com/0123.买卖股票的最佳时机III.html)的进阶版,这里要求至多有k次交易。 动规五部曲,分析如下: 1. 确定dp数组以及下标的含义 -在[动态规划:123.买卖股票的最佳时机III](https://mp.weixin.qq.com/s/Sbs157mlVDtAR0gbLpdKzg)中,我是定义了一个二维dp数组,本题其实依然可以用一个二维dp数组。 +在[动态规划:123.买卖股票的最佳时机III](https://programmercarl.com/0123.买卖股票的最佳时机III.html)中,我是定义了一个二维dp数组,本题其实依然可以用一个二维dp数组。 使用二维数组 dp[i][j] :第i天的状态为j,所剩下的最大现金是dp[i][j] @@ -84,14 +84,14 @@ vector> dp(prices.size(), vector(2 * k + 1, 0)); 同理可以类比剩下的状态,代码如下: -```C++ +```CPP for (int j = 0; j < 2 * k - 1; j += 2) { dp[i][j + 1] = max(dp[i - 1][j + 1], dp[i - 1][j] - prices[i]); dp[i][j + 2] = max(dp[i - 1][j + 2], dp[i - 1][j + 1] + prices[i]); } ``` -**本题和[动态规划:123.买卖股票的最佳时机III](https://mp.weixin.qq.com/s/Sbs157mlVDtAR0gbLpdKzg)最大的区别就是这里要类比j为奇数是买,偶数是卖剩的状态**。 +**本题和[动态规划:123.买卖股票的最佳时机III](https://programmercarl.com/0123.买卖股票的最佳时机III.html)最大的区别就是这里要类比j为奇数是买,偶数是卖剩的状态**。 3. dp数组如何初始化 @@ -117,7 +117,7 @@ for (int j = 0; j < 2 * k - 1; j += 2) { 代码如下: -```C++ +```CPP for (int j = 1; j < 2 * k; j += 2) { dp[0][j] = -prices[0]; } @@ -139,7 +139,7 @@ for (int j = 1; j < 2 * k; j += 2) { 以上分析完毕,C++代码如下: -```C++ +```CPP class Solution { public: int maxProfit(int k, vector& prices) { @@ -196,7 +196,7 @@ class Solution { } } -// 版本二: 空间优化 +// 版本二: 二维 dp数组 class Solution { public int maxProfit(int k, int[] prices) { if (prices.length == 0) return 0; @@ -220,6 +220,25 @@ class Solution { return dp[len - 1][k*2]; } } + +//版本三:一维 dp数组 +class Solution { + public int maxProfit(int k, int[] prices) { + //在版本二的基础上,由于我们只关心前一天的股票买入情况,所以只存储前一天的股票买入情况 + if(prices.length==0)return 0; + int[] dp=new int[2*k+1]; + for (int i = 1; i <2*k ; i+=2) { + dp[i]=-prices[0]; + } + for (int i = 0; i { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0189.旋转数组.md b/problems/0189.旋转数组.md index 9f565c1d..70aec5fe 100644 --- a/problems/0189.旋转数组.md +++ b/problems/0189.旋转数组.md @@ -37,17 +37,17 @@ 这道题目在字符串里其实很常见,我把字符串反转相关的题目列一下: -* [字符串:力扣541.反转字符串II](https://mp.weixin.qq.com/s/pzXt6PQ029y7bJ9YZB2mVQ) -* [字符串:力扣151.翻转字符串里的单词](https://mp.weixin.qq.com/s/4j6vPFHkFAXnQhmSkq2X9g) -* [字符串:剑指Offer58-II.左旋转字符串](https://mp.weixin.qq.com/s/Px_L-RfT2b_jXKcNmccPsw) +* [字符串:力扣541.反转字符串II](https://programmercarl.com/0541.反转字符串II.html) +* [字符串:力扣151.翻转字符串里的单词](https://programmercarl.com/0151.翻转字符串里的单词.html) +* [字符串:剑指Offer58-II.左旋转字符串](https://programmercarl.com/剑指Offer58-II.左旋转字符串.html) -本题其实和[字符串:剑指Offer58-II.左旋转字符串](https://mp.weixin.qq.com/s/Px_L-RfT2b_jXKcNmccPsw)就非常像了,剑指offer上左旋转,本题是右旋转。 +本题其实和[字符串:剑指Offer58-II.左旋转字符串](https://programmercarl.com/剑指Offer58-II.左旋转字符串.html)就非常像了,剑指offer上左旋转,本题是右旋转。 注意题目要求是**要求使用空间复杂度为 O(1) 的 原地 算法** 那么我来提供一种旋转的方式哈。 -在[字符串:剑指Offer58-II.左旋转字符串](https://mp.weixin.qq.com/s/Px_L-RfT2b_jXKcNmccPsw)中,我们提到,如下步骤就可以坐旋转字符串: +在[字符串:剑指Offer58-II.左旋转字符串](https://programmercarl.com/剑指Offer58-II.左旋转字符串.html)中,我们提到,如下步骤就可以坐旋转字符串: 1. 反转区间为前n的子串 2. 反转区间为n到末尾的子串 @@ -69,7 +69,7 @@ C++代码如下: -```C++ +```CPP class Solution { public: void rotate(vector& nums, int k) { @@ -131,12 +131,28 @@ class Solution: ## JavaScript ```js +var rotate = function (nums, k) { + function reverse(nums, i, j) { + while (i < j) { + [nums[i],nums[j]] = [nums[j],nums[i]]; // 解构赋值 + i++; + j--; + } + } + let n = nums.length; + k %= n; + if (k) { + reverse(nums, 0, n - 1); + reverse(nums, 0, k - 1); + reverse(nums, k, n - 1); + } +}; ``` ----------------------- * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0198.打家劫舍.md b/problems/0198.打家劫舍.md index 63a68c36..c8645c48 100644 --- a/problems/0198.打家劫舍.md +++ b/problems/0198.打家劫舍.md @@ -8,7 +8,7 @@ ## 198.打家劫舍 -题目链接:https://leetcode-cn.com/problems/house-robber/ +[力扣题目链接](https://leetcode-cn.com/problems/house-robber/) 你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。 @@ -25,7 +25,7 @@ 输出:12 解释:偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。   偷窃到的最高金额 = 2 + 9 + 1 = 12 。 -  + 提示: @@ -59,7 +59,7 @@ 代码如下: -```C++ +```CPP vector dp(nums.size()); dp[0] = nums[0]; dp[1] = max(nums[0], nums[1]); @@ -70,7 +70,7 @@ dp[1] = max(nums[0], nums[1]); dp[i] 是根据dp[i - 2] 和 dp[i - 1] 推导出来的,那么一定是从前到后遍历! 代码如下: -```C++ +```CPP for (int i = 2; i < nums.size(); i++) { dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]); } @@ -86,7 +86,7 @@ for (int i = 2; i < nums.size(); i++) { 以上分析完毕,C++代码如下: -```C++ +```CPP class Solution { public: int rob(vector& nums) { @@ -175,6 +175,22 @@ func max(a, b int) int { } ``` +JavaScript: + +```javascript +const rob = nums => { + // 数组长度 + const len = nums.length; + // dp数组初始化 + const dp = [nums[0], Math.max(nums[0], nums[1])]; + // 从下标2开始遍历 + for (let i = 2; i < len; i++) { + dp[i] = Math.max(dp[i - 2] + nums[i], dp[i - 1]); + } + return dp[len - 1]; +}; +``` + @@ -182,4 +198,4 @@ func max(a, b int) int { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0202.快乐数.md b/problems/0202.快乐数.md index d1bccd64..710c824d 100644 --- a/problems/0202.快乐数.md +++ b/problems/0202.快乐数.md @@ -12,7 +12,7 @@ # 第202题. 快乐数 -https://leetcode-cn.com/problems/happy-number/ +[力扣题目链接](https://leetcode-cn.com/problems/happy-number/) 编写一个算法来判断一个数 n 是不是快乐数。 @@ -36,7 +36,7 @@ https://leetcode-cn.com/problems/happy-number/ 题目中说了会 **无限循环**,那么也就是说**求和的过程中,sum会重复出现,这对解题很重要!** -正如:[关于哈希表,你该了解这些!](https://mp.weixin.qq.com/s/RSUANESA_tkhKhYe3ZR8Jg)中所说,**当我们遇到了要快速判断一个元素是否出现集合里的时候,就要考虑哈希法了。** +正如:[关于哈希表,你该了解这些!](https://programmercarl.com/哈希表理论基础.html)中所说,**当我们遇到了要快速判断一个元素是否出现集合里的时候,就要考虑哈希法了。** 所以这道题目使用哈希法,来判断这个sum是否重复出现,如果重复了就是return false, 否则一直找到sum为1为止。 @@ -46,7 +46,7 @@ https://leetcode-cn.com/problems/happy-number/ C++代码如下: -```C++ +```CPP class Solution { public: // 取数值各个位上的单数之和 @@ -111,25 +111,29 @@ Python: ```python class Solution: def isHappy(self, n: int) -> bool: - set_ = set() - while 1: - sum_ = self.getSum(n) - if sum_ == 1: + def calculate_happy(num): + sum_ = 0 + + # 从个位开始依次取,平方求和 + while num: + sum_ += (num % 10) ** 2 + num = num // 10 + return sum_ + + # 记录中间结果 + record = set() + + while True: + n = calculate_happy(n) + if n == 1: return True - #如果这个sum曾经出现过,说明已经陷入了无限循环了,立刻return false - if sum_ in set_: + + # 如果中间结果重复出现,说明陷入死循环了,该数不是快乐数 + if n in record: return False else: - set_.add(sum_) - n = sum_ - - #取数值各个位上的单数之和 - def getSum(self, n): - sum_ = 0 - while n > 0: - sum_ += (n%10) * (n%10) - n //= 10 - return sum_ + record.add(n) + ``` Go: @@ -187,10 +191,71 @@ var isHappy = function(n) { }; ``` +Swift: +```swift +// number 每个位置上的数字的平方和 +func getSum(_ number: Int) -> Int { + var sum = 0 + var num = number + while num > 0 { + let temp = num % 10 + sum += (temp * temp) + num /= 10 + } + return sum +} +func isHappy(_ n: Int) -> Bool { + var set = Set() + var num = n + while true { + let sum = self.getSum(num) + if sum == 1 { + return true + } + // 如果这个sum曾经出现过,说明已经陷入了无限循环了 + if set.contains(sum) { + return false + } else { + set.insert(sum) + } + num = sum + } +} +``` +PHP: +```php +class Solution { + /** + * @param Integer $n + * @return Boolean + */ + function isHappy($n) { + // use a set to record sum + // whenever there is a duplicated, stop + // == 1 return true, else false + $table = []; + while ($n != 1 && !isset($table[$n])) { + $table[$n] = 1; + $n = self::getNextN($n); + } + return $n == 1; + } + + function getNextN(int $n) { + $res = 0; + while ($n > 0) { + $temp = $n % 10; + $res += $temp * $temp; + $n = floor($n / 10); + } + return $res; + } +} +``` ----------------------- * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0203.移除链表元素.md b/problems/0203.移除链表元素.md index ea1d705a..c4f187e8 100644 --- a/problems/0203.移除链表元素.md +++ b/problems/0203.移除链表元素.md @@ -11,7 +11,7 @@ # 203.移除链表元素 -https://leetcode-cn.com/problems/remove-linked-list-elements/ +[力扣题目链接](https://leetcode-cn.com/problems/remove-linked-list-elements/) 题意:删除链表中等于给定值 val 的所有节点。 @@ -89,7 +89,7 @@ https://leetcode-cn.com/problems/remove-linked-list-elements/ **直接使用原来的链表来进行移除节点操作:** -```C++ +```CPP class Solution { public: ListNode* removeElements(ListNode* head, int val) { @@ -118,7 +118,7 @@ public: **设置一个虚拟头结点在进行移除节点操作:** -```C++ +```CPP class Solution { public: ListNode* removeElements(ListNode* head, int val) { @@ -304,11 +304,62 @@ var removeElements = function(head, val) { }; ``` +Swift: +```swift +/** + * Definition for singly-linked list. + * public class ListNode { + * public var val: Int + * public var next: ListNode? + * public init() { self.val = 0; self.next = nil; } + * public init(_ val: Int) { self.val = val; self.next = nil; } + * public init(_ val: Int, _ next: ListNode?) { self.val = val; self.next = next; } + * } + */ +func removeElements(_ head: ListNode?, _ val: Int) -> ListNode? { + let dummyNode = ListNode() + dummyNode.next = head + var currentNode = dummyNode + while let curNext = currentNode.next { + if curNext.val == val { + currentNode.next = curNext.next + } else { + currentNode = curNext + } + } + return dummyNode.next +} +``` - +PHP: +```php +/** + * Definition for singly-linked list. + * type ListNode struct { + * Val int + * Next *ListNode + * } + */ + // 虚拟头+双指针 +func removeElements(head *ListNode, val int) *ListNode { + dummyHead := &ListNode{} + dummyHead.Next = head + pred := dummyHead + cur := head + for cur != nil { + if cur.Val == val { + pred.Next = cur.Next + } else { + pred = cur + } + cur = cur.Next + } + return dummyHead.Next +} +``` ----------------------- * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0205.同构字符串.md b/problems/0205.同构字符串.md index 4e963ece..5d20aa4a 100644 --- a/problems/0205.同构字符串.md +++ b/problems/0205.同构字符串.md @@ -9,7 +9,7 @@ # 205. 同构字符串 -题目地址:https://leetcode-cn.com/problems/isomorphic-strings/ +[力扣题目链接](https://leetcode-cn.com/problems/isomorphic-strings/) 给定两个字符串 s 和 t,判断它们是否是同构的。 @@ -39,7 +39,7 @@ C++代码 如下: -```C++ +```CPP class Solution { public: bool isIsomorphic(string s, string t) { @@ -68,6 +68,25 @@ public: ## Java ```java +class Solution { + public boolean isIsomorphic(String s, String t) { + Map map1 = new HashMap<>(); + Map map2 = new HashMap<>(); + for (int i = 0, j = 0; i < s.length(); i++, j++) { + if (!map1.containsKey(s.charAt(i))) { + map1.put(s.charAt(i), t.charAt(j)); // map1保存 s[i] 到 t[j]的映射 + } + if (!map2.containsKey(t.charAt(j))) { + map2.put(t.charAt(j), s.charAt(i)); // map2保存 t[j] 到 s[i]的映射 + } + // 无法映射,返回 false + if (map1.get(s.charAt(i)) != t.charAt(j) || map2.get(t.charAt(j)) != s.charAt(i)) { + return false; + } + } + return true; + } +} ``` ## Python @@ -78,6 +97,23 @@ public: ## Go ```go +func isIsomorphic(s string, t string) bool { + map1 := make(map[byte]byte) + map2 := make(map[byte]byte) + for i := range s { + if _, ok := map1[s[i]]; !ok { + map1[s[i]] = t[i] // map1保存 s[i] 到 t[j]的映射 + } + if _, ok := map2[t[i]]; !ok { + map2[t[i]] = s[i] // map2保存 t[i] 到 s[j]的映射 + } + // 无法映射,返回 false + if (map1[s[i]] != t[i]) || (map2[t[i]] != s[i]) { + return false + } + } + return true +} ``` ## JavaScript @@ -89,5 +125,5 @@ public: * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0206.翻转链表.md b/problems/0206.翻转链表.md index 963d7916..45196386 100644 --- a/problems/0206.翻转链表.md +++ b/problems/0206.翻转链表.md @@ -11,7 +11,7 @@ # 206.反转链表 -https://leetcode-cn.com/problems/reverse-linked-list/ +[力扣题目链接](https://leetcode-cn.com/problems/reverse-linked-list/) 题意:反转一个单链表。 @@ -48,7 +48,7 @@ https://leetcode-cn.com/problems/reverse-linked-list/ # C++代码 ## 双指针法 -```C++ +```CPP class Solution { public: ListNode* reverseList(ListNode* head) { @@ -74,7 +74,7 @@ public: 关键是初始化的地方,可能有的同学会不理解, 可以看到双指针法中初始化 cur = head,pre = NULL,在递归法中可以从如下代码看出初始化的逻辑也是一样的,只不过写法变了。 具体可以看代码(已经详细注释),**双指针法写出来之后,理解如下递归写法就不难了,代码逻辑都是一样的。** -```C++ +```CPP class Solution { public: ListNode* reverse(ListNode* pre,ListNode* cur){ @@ -275,11 +275,104 @@ var reverseList = function(head) { }; ``` +Ruby: +```ruby +# 双指针 +# Definition for singly-linked list. +# class ListNode +# attr_accessor :val, :next +# def initialize(val = 0, _next = nil) +# @val = val +# @next = _next +# end +# end +def reverse_list(head) + # return nil if head.nil? # 循环判断条件亦能起到相同作用因此不必单独判断 + cur, per = head, nil + until cur.nil? + tem = cur.next + cur.next = per + per = cur + cur = tem + end + per +end +# 递归 +# Definition for singly-linked list. +# class ListNode +# attr_accessor :val, :next +# def initialize(val = 0, _next = nil) +# @val = val +# @next = _next +# end +# end +def reverse_list(head) + reverse(nil, head) +end + +def reverse(pre, cur) + return pre if cur.nil? + tem = cur.next + cur.next = pre + reverse(cur, tem) # 通过递归实现双指针法中的更新操作 +end +``` +Kotlin: +```Kotlin +fun reverseList(head: ListNode?): ListNode? { + var pre: ListNode? = null + var cur = head + while (cur != null) { + val temp = cur.next + cur.next = pre + pre = cur + cur = temp + } + return pre +} +``` + +Swift: +```swift +/// 双指针法 (迭代) +/// - Parameter head: 头结点 +/// - Returns: 翻转后的链表头结点 +func reverseList(_ head: ListNode?) -> ListNode? { + if head == nil || head?.next == nil { + return head + } + var pre: ListNode? = nil + var cur: ListNode? = head + var temp: ListNode? = nil + while cur != nil { + temp = cur?.next + cur?.next = pre + pre = cur + cur = temp + } + return pre +} + +/// 递归 +/// - Parameter head: 头结点 +/// - Returns: 翻转后的链表头结点 +func reverseList2(_ head: ListNode?) -> ListNode? { + return reverse(pre: nil, cur: head) +} +func reverse(pre: ListNode?, cur: ListNode?) -> ListNode? { + if cur == nil { + return pre + } + let temp: ListNode? = cur?.next + cur?.next = pre + return reverse(pre: cur, cur: temp) +} +``` ----------------------- * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0209.长度最小的子数组.md b/problems/0209.长度最小的子数组.md index 42687514..7c3fd0e7 100644 --- a/problems/0209.长度最小的子数组.md +++ b/problems/0209.长度最小的子数组.md @@ -9,7 +9,7 @@ ## 209.长度最小的子数组 -题目链接: https://leetcode-cn.com/problems/minimum-size-subarray-sum/ +[力扣题目链接](https://leetcode-cn.com/problems/minimum-size-subarray-sum/) 给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的 连续 子数组,并返回其长度。如果不存在符合条件的子数组,返回 0。 @@ -26,7 +26,7 @@ 代码如下: -```C++ +```CPP class Solution { public: int minSubArrayLen(int s, vector& nums) { @@ -86,7 +86,7 @@ public: C++代码如下: -```C++ +```CPP class Solution { public: int minSubArrayLen(int s, vector& nums) { @@ -216,8 +216,85 @@ var minSubArrayLen = function(target, nums) { }; ``` +Swift: + +```swift +func minSubArrayLen(_ target: Int, _ nums: [Int]) -> Int { + var result = Int.max + var sum = 0 + var starIndex = 0 + for endIndex in 0..= target { + result = min(result, endIndex - starIndex + 1) + sum -= nums[starIndex] + starIndex += 1 + } + } + + return result == Int.max ? 0 : result +} +``` + +Rust: + +```rust +impl Solution { + pub fn min_sub_array_len(target: i32, nums: Vec) -> i32 { + let (mut result, mut subLength): (i32, i32) = (i32::MAX, 0); + let (mut sum, mut i) = (0, 0); + + for (pos, val) in nums.iter().enumerate() { + sum += val; + while sum >= target { + subLength = (pos - i + 1) as i32; + if result > subLength { + result = subLength; + } + sum -= nums[i]; + i += 1; + } + } + if result == i32::MAX { + return 0; + } + result + } +} +``` + +PHP: +```php +// 双指针 - 滑动窗口 +class Solution { + /** + * @param Integer $target + * @param Integer[] $nums + * @return Integer + */ + function minSubArrayLen($target, $nums) { + if (count($nums) < 1) { + return 0; + } + $sum = 0; + $res = PHP_INT_MAX; + $left = 0; + for ($right = 0; $right < count($nums); $right++) { + $sum += $nums[$right]; + while ($sum >= $target) { + $res = min($res, $right - $left + 1); + $sum -= $nums[$left]; + $left++; + } + } + return $res == PHP_INT_MAX ? 0 : $res; + } +} +``` + ----------------------- * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0213.打家劫舍II.md b/problems/0213.打家劫舍II.md index 12951117..332d3218 100644 --- a/problems/0213.打家劫舍II.md +++ b/problems/0213.打家劫舍II.md @@ -8,7 +8,7 @@ ## 213.打家劫舍II -题目链接:https://leetcode-cn.com/problems/house-robber-ii/ +[力扣题目链接](https://leetcode-cn.com/problems/house-robber-ii/) 你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都 围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 。 @@ -28,14 +28,14 @@ 示例 3: 输入:nums = [0] 输出:0 -  + 提示: * 1 <= nums.length <= 100 * 0 <= nums[i] <= 1000 ## 思路 -这道题目和[198.打家劫舍](https://mp.weixin.qq.com/s/UZ31WdLEEFmBegdgLkJ8Dw)是差不多的,唯一区别就是成环了。 +这道题目和[198.打家劫舍](https://programmercarl.com/0198.打家劫舍.html)是差不多的,唯一区别就是成环了。 对于一个数组,成环的话主要有如下三种情况: @@ -55,11 +55,11 @@ **而情况二 和 情况三 都包含了情况一了,所以只考虑情况二和情况三就可以了**。 -分析到这里,本题其实比较简单了。 剩下的和[198.打家劫舍](https://mp.weixin.qq.com/s/UZ31WdLEEFmBegdgLkJ8Dw)就是一样的了。 +分析到这里,本题其实比较简单了。 剩下的和[198.打家劫舍](https://programmercarl.com/0198.打家劫舍.html)就是一样的了。 代码如下: -```C++ +```CPP // 注意注释中的情况二情况三,以及把198.打家劫舍的代码抽离出来了 class Solution { public: @@ -174,4 +174,4 @@ Go: * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0216.组合总和III.md b/problems/0216.组合总和III.md index 15220464..10491553 100644 --- a/problems/0216.组合总和III.md +++ b/problems/0216.组合总和III.md @@ -13,7 +13,7 @@ # 216.组合总和III -链接:https://leetcode-cn.com/problems/combination-sum-iii/ +[力扣题目链接](https://leetcode-cn.com/problems/combination-sum-iii/) 找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。 @@ -34,9 +34,9 @@ 本题就是在[1,2,3,4,5,6,7,8,9]这个集合中找到和为n的k个数的组合。 -相对于[77. 组合](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ),无非就是多了一个限制,本题是要找到和为n的k个数的组合,而整个集合已经是固定的了[1,...,9]。 +相对于[77. 组合](https://programmercarl.com/0077.组合.html),无非就是多了一个限制,本题是要找到和为n的k个数的组合,而整个集合已经是固定的了[1,...,9]。 -想到这一点了,做过[77. 组合](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)之后,本题是简单一些了。 +想到这一点了,做过[77. 组合](https://programmercarl.com/0077.组合.html)之后,本题是简单一些了。 本题k相当于了树的深度,9(因为整个集合就是9个数)就是树的宽度。 @@ -53,7 +53,7 @@ * **确定递归函数参数** -和[77. 组合](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)一样,依然需要一维数组path来存放符合条件的结果,二维数组result来存放结果集。 +和[77. 组合](https://programmercarl.com/0077.组合.html)一样,依然需要一维数组path来存放符合条件的结果,二维数组result来存放结果集。 这里我依然定义path 和 result为全局变量。 @@ -94,7 +94,7 @@ void backtracking(int targetSum, int k, int sum, int startIndex) 所以 终止代码如下: -```C++ +```CPP if (path.size() == k) { if (sum == targetSum) result.push_back(path); return; // 如果path.size() == k 但sum != targetSum 直接返回 @@ -103,7 +103,7 @@ if (path.size() == k) { * **单层搜索过程** -本题和[77. 组合](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)区别之一就是集合固定的就是9个数[1,...,9],所以for循环固定i<=9 +本题和[77. 组合](https://programmercarl.com/0077.组合.html)区别之一就是集合固定的就是9个数[1,...,9],所以for循环固定i<=9 如图: ![216.组合总和III](https://img-blog.csdnimg.cn/20201123195717975.png) @@ -112,7 +112,7 @@ if (path.size() == k) { 代码如下: -```C++ +```CPP for (int i = startIndex; i <= 9; i++) { sum += i; path.push_back(i); @@ -124,9 +124,9 @@ for (int i = startIndex; i <= 9; i++) { **别忘了处理过程 和 回溯过程是一一对应的,处理有加,回溯就要有减!** -参照[关于回溯算法,你该了解这些!](https://mp.weixin.qq.com/s/gjSgJbNbd1eAA5WkA-HeWw)中的模板,不难写出如下C++代码: +参照[关于回溯算法,你该了解这些!](https://programmercarl.com/回溯算法理论基础.html)中的模板,不难写出如下C++代码: -```C++ +```CPP class Solution { private: vector> result; // 存放结果集 @@ -176,7 +176,7 @@ if (sum > targetSum) { // 剪枝操作 } ``` -和[回溯算法:组合问题再剪剪枝](https://mp.weixin.qq.com/s/Ri7spcJMUmph4c6XjPWXQA) 一样,for循环的范围也可以剪枝,i <= 9 - (k - path.size()) + 1就可以了。 +和[回溯算法:组合问题再剪剪枝](https://programmercarl.com/0077.组合优化.html) 一样,for循环的范围也可以剪枝,i <= 9 - (k - path.size()) + 1就可以了。 最后C++代码如下: @@ -214,7 +214,7 @@ public: # 总结 -开篇就介绍了本题与[回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)的区别,相对来说加了元素总和的限制,如果做完[回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)再做本题在合适不过。 +开篇就介绍了本题与[回溯算法:求组合问题!](https://programmercarl.com/0077.组合.html)的区别,相对来说加了元素总和的限制,如果做完[回溯算法:求组合问题!](https://programmercarl.com/0077.组合.html)再做本题在合适不过。 分析完区别,依然把问题抽象为树形结构,按照回溯三部曲进行讲解,最后给出剪枝的优化。 @@ -392,10 +392,66 @@ var combinationSum3 = function(k, n) { }; ``` +C: +```c +int* path; +int pathTop; +int** ans; +int ansTop; +int getPathSum() { + int i; + int sum = 0; + for(i = 0; i < pathTop; i++) { + sum += path[i]; + } + return sum; +} +void backtracking(int targetSum, int k, int sum, int startIndex) { + if(pathTop == k) { + if(sum == targetSum) { + int* tempPath = (int*)malloc(sizeof(int) * k); + int j; + for(j = 0; j < k; j++) + tempPath[j] = path[j]; + ans[ansTop++] = tempPath; + } + // 如果path.size() == k 但sum != targetSum 直接返回 + return; + } + int i; + //从startIndex开始遍历,一直遍历到9 + for (i = startIndex; i <= 9; i++) { + sum += i; // 处理 + path[pathTop++] = i; // 处理 + backtracking(targetSum, k, sum, i + 1); // 注意i+1调整startIndex + sum -= i; // 回溯 + pathTop--;; // 回溯 + } +} + +int** combinationSum3(int k, int n, int* returnSize, int** returnColumnSizes){ + //初始化辅助变量 + path = (int*)malloc(sizeof(int) * k); + ans = (int**)malloc(sizeof(int*) * 20); + pathTop = ansTop = 0; + + backtracking(n, k, 0, 1); + + //设置返回的二维数组中元素个数为ansTop + *returnSize = ansTop; + //设置二维数组中每个元素个数的大小为k + *returnColumnSizes = (int*)malloc(sizeof(int) * ansTop); + int i; + for(i = 0; i < ansTop; i++) { + (*returnColumnSizes)[i] = k; + } + return ans; +} +``` ----------------------- * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0222.完全二叉树的节点个数.md b/problems/0222.完全二叉树的节点个数.md index ec68b6c6..13017f7f 100644 --- a/problems/0222.完全二叉树的节点个数.md +++ b/problems/0222.完全二叉树的节点个数.md @@ -7,24 +7,23 @@

欢迎大家参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!

-## 222.完全二叉树的节点个数 +# 222.完全二叉树的节点个数 -题目地址:https://leetcode-cn.com/problems/count-complete-tree-nodes/ +[力扣题目链接](https://leetcode-cn.com/problems/count-complete-tree-nodes/) 给出一个完全二叉树,求出该树的节点个数。 -示例: 示例 1: -输入:root = [1,2,3,4,5,6] -输出:6 +* 输入:root = [1,2,3,4,5,6] +* 输出:6 示例 2: -输入:root = [] -输出:0 +* 输入:root = [] +* 输出:0 示例 3: -输入:root = [1] -输出:1 +* 输入:root = [1] +* 输出:1 提示: @@ -33,21 +32,22 @@ * 题目数据保证输入的树是 完全二叉树 -## 思路 +# 思路 本篇给出按照普通二叉树的求法以及利用完全二叉树性质的求法。 + ## 普通二叉树 首先按照普通二叉树的逻辑来求。 -这道题目的递归法和求二叉树的深度写法类似, 而迭代法,[二叉树:层序遍历登场!](https://mp.weixin.qq.com/s/Gb3BjakIKGNpup2jYtTzog)遍历模板稍稍修改一下,记录遍历的节点数量就可以了。 +这道题目的递归法和求二叉树的深度写法类似, 而迭代法,[二叉树:层序遍历登场!](https://programmercarl.com/0102.二叉树的层序遍历.html)遍历模板稍稍修改一下,记录遍历的节点数量就可以了。 递归遍历的顺序依然是后序(左右中)。 ### 递归 -如果对求二叉树深度还不熟悉的话,看这篇:[二叉树:看看这些树的最大深度](https://mp.weixin.qq.com/s/guKwV-gSNbA1CcbvkMtHBg)。 +如果对求二叉树深度还不熟悉的话,看这篇:[二叉树:看看这些树的最大深度](https://programmercarl.com/0104.二叉树的最大深度.html)。 1. 确定递归函数的参数和返回值:参数就是传入树的根节点,返回就返回以该节点为根节点二叉树的节点数量,所以返回值为int类型。 @@ -77,7 +77,7 @@ return treeNum; 所以整体C++代码如下: -```C++ +```CPP // 版本一 class Solution { private: @@ -96,7 +96,7 @@ public: ``` 代码精简之后C++代码如下: -```C++ +```CPP // 版本二 class Solution { public: @@ -107,19 +107,19 @@ public: }; ``` -时间复杂度:O(n) -空间复杂度:O(logn),算上了递归系统栈占用的空间 +* 时间复杂度:O(n) +* 空间复杂度:O(logn),算上了递归系统栈占用的空间 **网上基本都是这个精简的代码版本,其实不建议大家照着这个来写,代码确实精简,但隐藏了一些内容,连遍历的顺序都看不出来,所以初学者建议学习版本一的代码,稳稳的打基础**。 ### 迭代法 -如果对求二叉树层序遍历还不熟悉的话,看这篇:[二叉树:层序遍历登场!](https://mp.weixin.qq.com/s/Gb3BjakIKGNpup2jYtTzog)。 +如果对求二叉树层序遍历还不熟悉的话,看这篇:[二叉树:层序遍历登场!](https://programmercarl.com/0102.二叉树的层序遍历.html)。 那么只要模板少做改动,加一个变量result,统计节点数量就可以了 -```C++ +```CPP class Solution { public: int countNodes(TreeNode* root) { @@ -140,12 +140,12 @@ public: } }; ``` -时间复杂度:O(n) -空间复杂度:O(n) +* 时间复杂度:O(n) +* 空间复杂度:O(n) ## 完全二叉树 -以上方法都是按照普通二叉树来做的,对于完全二叉树特性不了解的同学可以看这篇 [关于二叉树,你该了解这些!](https://mp.weixin.qq.com/s/_ymfWYvTNd2GvWvC5HOE4A),这篇详细介绍了各种二叉树的特性。 +以上方法都是按照普通二叉树来做的,对于完全二叉树特性不了解的同学可以看这篇 [关于二叉树,你该了解这些!](https://programmercarl.com/二叉树理论基础.html),这篇详细介绍了各种二叉树的特性。 完全二叉树只有两种情况,情况一:就是满二叉树,情况二:最后一层叶子节点没有满。 @@ -163,7 +163,7 @@ public: C++代码如下: -```C++ +```CPP class Solution { public: int countNodes(TreeNode* root) { @@ -187,13 +187,12 @@ public: }; ``` -时间复杂度:O(logn * logn) -空间复杂度:O(logn) +* 时间复杂度:O(logn * logn) +* 空间复杂度:O(logn) -## 其他语言版本 +# 其他语言版本 - -Java: +## Java ```java class Solution { // 通用递归解法 @@ -205,7 +204,27 @@ class Solution { } } ``` - +```java +class Solution { + // 迭代法 + public int countNodes(TreeNode root) { + if (root == null) return 0; + Queue queue = new LinkedList<>(); + queue.offer(root); + int result = 0; + while (!queue.isEmpty()) { + int size = queue.size(); + while (size -- > 0) { + TreeNode cur = queue.poll(); + result++; + if (cur.left != null) queue.offer(cur.left); + if (cur.right != null) queue.offer(cur.right); + } + } + return result; + } +} +``` ```java class Solution { /** @@ -238,9 +257,9 @@ class Solution { } ``` -Python: +## Python -> 递归法: +递归法: ```python class Solution: def countNodes(self, root: TreeNode) -> int: @@ -255,7 +274,7 @@ class Solution: return treeNum ``` -> 递归法:精简版 +递归法:精简版 ```python class Solution: def countNodes(self, root: TreeNode) -> int: @@ -264,7 +283,7 @@ class Solution: return 1 + self.countNodes(root.left) + self.countNodes(root.right) ``` -> 迭代法: +迭代法: ```python import collections class Solution: @@ -285,7 +304,7 @@ class Solution: return result ``` -> 完全二叉树 +完全二叉树 ```python class Solution: def countNodes(self, root: TreeNode) -> int: @@ -306,7 +325,7 @@ class Solution: return self.countNodes(root.left) + self.countNodes(root.right) + 1 ``` -Go: +## Go 递归版本 @@ -361,7 +380,7 @@ func countNodes(root *TreeNode) int { -JavaScript: +## JavaScript: 递归版本 ```javascript @@ -436,4 +455,4 @@ var countNodes = function(root) { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0225.用队列实现栈.md b/problems/0225.用队列实现栈.md index b3e851a1..8d4db953 100644 --- a/problems/0225.用队列实现栈.md +++ b/problems/0225.用队列实现栈.md @@ -12,7 +12,7 @@ # 225. 用队列实现栈 -https://leetcode-cn.com/problems/implement-stack-using-queues/ +[力扣题目链接](https://leetcode-cn.com/problems/implement-stack-using-queues/) 使用队列实现栈的下列操作: @@ -34,7 +34,7 @@ https://leetcode-cn.com/problems/implement-stack-using-queues/ 有的同学可能疑惑这种题目有什么实际工程意义,**其实很多算法题目主要是对知识点的考察和教学意义远大于其工程实践的意义,所以面试题也是这样!** -刚刚做过[栈与队列:我用栈来实现队列怎么样?](https://mp.weixin.qq.com/s/Cj6R0qu8rFA7Et9V_ZMjCA)的同学可能依然想着用一个输入队列,一个输出队列,就可以模拟栈的功能,仔细想一下还真不行! +刚刚做过[栈与队列:我用栈来实现队列怎么样?](https://programmercarl.com/0232.用栈实现队列.html)的同学可能依然想着用一个输入队列,一个输出队列,就可以模拟栈的功能,仔细想一下还真不行! **队列模拟栈,其实一个队列就够了**,那么我们先说一说两个队列来实现栈的思路。 @@ -65,7 +65,7 @@ queue.empty(); 详细如代码注释所示: -```C++ +```CPP class MyStack { public: queue que1; @@ -118,7 +118,7 @@ public: C++优化代码 -```C++ +```CPP class MyStack { public: queue que; @@ -294,58 +294,136 @@ Python: ```python from collections import deque + class MyStack: + def __init__(self): """ - Initialize your data structure here. + Python普通的Queue或SimpleQueue没有类似于peek的功能 + 也无法用索引访问,在实现top的时候较为困难。 + + 用list可以,但是在使用pop(0)的时候时间复杂度为O(n) + 因此这里使用双向队列,我们保证只执行popleft()和append(),因为deque可以用索引访问,可以实现和peek相似的功能 + + in - 存所有数据 + out - 仅在pop的时候会用到 """ - #使用两个队列来实现 - self.que1 = deque() - self.que2 = deque() + self.queue_in = deque() + self.queue_out = deque() def push(self, x: int) -> None: """ - Push element x onto stack. + 直接append即可 """ - self.que1.append(x) + self.queue_in.append(x) + def pop(self) -> int: """ - Removes the element on top of the stack and returns that element. + 1. 首先确认不空 + 2. 因为队列的特殊性,FIFO,所以我们只有在pop()的时候才会使用queue_out + 3. 先把queue_in中的所有元素(除了最后一个),依次出列放进queue_out + 4. 交换in和out,此时out里只有一个元素 + 5. 把out中的pop出来,即是原队列的最后一个 + + tip:这不能像栈实现队列一样,因为另一个queue也是FIFO,如果执行pop()它不能像 + stack一样从另一个pop(),所以干脆in只用来存数据,pop()的时候两个进行交换 """ - size = len(self.que1) - size -= 1#这里先减一是为了保证最后面的元素 - while size > 0: - size -= 1 - self.que2.append(self.que1.popleft()) + if self.empty(): + return None - - result = self.que1.popleft() - self.que1, self.que2= self.que2, self.que1#将que2和que1交换 que1经过之前的操作应该是空了 - #一定注意不能直接使用que1 = que2 这样que2的改变会影响que1 可以用浅拷贝 - return result + for i in range(len(self.queue_in) - 1): + self.queue_out.append(self.queue_in.popleft()) + + self.queue_in, self.queue_out = self.queue_out, self.queue_in # 交换in和out,这也是为啥in只用来存 + return self.queue_out.popleft() def top(self) -> int: """ - Get the top element. + 1. 首先确认不空 + 2. 我们仅有in会存放数据,所以返回第一个即可 """ - return self.que1[-1] + if self.empty(): + return None + + return self.queue_in[-1] + def empty(self) -> bool: """ - Returns whether the stack is empty. + 因为只有in存了数据,只要判断in是不是有数即可 """ - #print(self.que1) - if len(self.que1) == 0: - return True - else: - return False - + return len(self.queue_in) == 0 ``` Go: +```go +type MyStack struct { + queue []int//创建一个队列 +} + + +/** Initialize your data structure here. */ +func Constructor() MyStack { + return MyStack{ //初始化 + queue:make([]int,0), + } +} + + +/** Push element x onto stack. */ +func (this *MyStack) Push(x int) { + //添加元素 + this.queue=append(this.queue,x) +} + + +/** Removes the element on top of the stack and returns that element. */ +func (this *MyStack) Pop() int { + n:=len(this.queue)-1//判断长度 + for n!=0{ //除了最后一个,其余的都重新添加到队列里 + val:=this.queue[0] + this.queue=this.queue[1:] + this.queue=append(this.queue,val) + n-- + } + //弹出元素 + val:=this.queue[0] + this.queue=this.queue[1:] + return val + +} + + +/** Get the top element. */ +func (this *MyStack) Top() int { + //利用Pop函数,弹出来的元素重新添加 + val:=this.Pop() + this.queue=append(this.queue,val) + return val +} + + +/** Returns whether the stack is empty. */ +func (this *MyStack) Empty() bool { + return len(this.queue)==0 +} + + +/** + * Your MyStack object will be instantiated and called as such: + * obj := Constructor(); + * obj.Push(x); + * param_2 := obj.Pop(); + * param_3 := obj.Top(); + * param_4 := obj.Empty(); + */ +``` + + + javaScript: 使用数组(push, shift)模拟队列 @@ -460,4 +538,4 @@ MyStack.prototype.empty = function() { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0226.翻转二叉树.md b/problems/0226.翻转二叉树.md index 44f0d3b4..fb5d831a 100644 --- a/problems/0226.翻转二叉树.md +++ b/problems/0226.翻转二叉树.md @@ -9,7 +9,7 @@ # 226.翻转二叉树 -题目地址:https://leetcode-cn.com/problems/invert-binary-tree/ +[力扣题目链接](https://leetcode-cn.com/problems/invert-binary-tree/) 翻转一棵二叉树。 @@ -43,7 +43,7 @@ **注意只要把每一个节点的左右孩子翻转一下,就可以达到整体翻转的效果** -**这道题目使用前序遍历和后序遍历都可以,唯独中序遍历不行,因为中序遍历会把某些节点的左右孩子翻转了两次!建议拿纸画一画,就理解了** +**这道题目使用前序遍历和后序遍历都可以,唯独中序遍历不方便,因为中序遍历会把某些节点的左右孩子翻转了两次!建议拿纸画一画,就理解了** 那么层序遍历可以不可以呢?**依然可以的!只要把每一个节点的左右孩子翻转一下的遍历方式都是可以的!** @@ -51,7 +51,7 @@ -对于二叉树的递归法的前中后序遍历,已经在[二叉树:前中后序递归遍历](https://mp.weixin.qq.com/s/Ww60X5mIKWdMQV4cN3ejOA)详细讲解了。 +对于二叉树的递归法的前中后序遍历,已经在[二叉树:前中后序递归遍历](https://programmercarl.com/二叉树的递归遍历.html)详细讲解了。 我们下文以前序遍历为例,通过动画来看一下翻转的过程: @@ -89,7 +89,7 @@ invertTree(root->right); 基于这递归三步法,代码基本写完,C++代码如下: -```C++ +```CPP class Solution { public: TreeNode* invertTree(TreeNode* root) { @@ -106,12 +106,11 @@ public: ### 深度优先遍历 - -[二叉树:听说递归能做的,栈也能做!](https://mp.weixin.qq.com/s/OH7aCVJ5-Gi32PkNCoZk4A)中给出了前中后序迭代方式的写法,所以本地可以很轻松的切出如下迭代法的代码: +[二叉树:听说递归能做的,栈也能做!](https://programmercarl.com/二叉树的迭代遍历.html)中给出了前中后序迭代方式的写法,所以本地可以很轻松的切出如下迭代法的代码: C++代码迭代法(前序遍历) -```C++ +```CPP class Solution { public: TreeNode* invertTree(TreeNode* root) { @@ -129,14 +128,14 @@ public: } }; ``` -如果这个代码看不懂的话可以在回顾一下[二叉树:听说递归能做的,栈也能做!](https://mp.weixin.qq.com/s/OH7aCVJ5-Gi32PkNCoZk4A)。 +如果这个代码看不懂的话可以在回顾一下[二叉树:听说递归能做的,栈也能做!](https://programmercarl.com/二叉树的迭代遍历.html)。 -我们在[二叉树:前中后序迭代方式的统一写法](https://mp.weixin.qq.com/s/ATQMPCpBlaAgrqdLDMVPZA)中介绍了统一的写法,所以,本题也只需将文中的代码少做修改便可。 +我们在[二叉树:前中后序迭代方式的统一写法](https://programmercarl.com/二叉树的统一迭代法.html)中介绍了统一的写法,所以,本题也只需将文中的代码少做修改便可。 C++代码如下迭代法(前序遍历) -```C++ +```CPP class Solution { public: TreeNode* invertTree(TreeNode* root) { @@ -162,13 +161,13 @@ public: }; ``` -如果上面这个代码看不懂,回顾一下文章[二叉树:前中后序迭代方式的统一写法](https://mp.weixin.qq.com/s/ATQMPCpBlaAgrqdLDMVPZA)。 +如果上面这个代码看不懂,回顾一下文章[二叉树:前中后序迭代方式的统一写法](https://programmercarl.com/二叉树的统一迭代法.html)。 ### 广度优先遍历 也就是层序遍历,层数遍历也是可以翻转这棵树的,因为层序遍历也可以把每个节点的左右孩子都翻转一遍,代码如下: -```C++ +```CPP class Solution { public: TreeNode* invertTree(TreeNode* root) { @@ -188,7 +187,7 @@ public: } }; ``` -如果对以上代码不理解,或者不清楚二叉树的层序遍历,可以看这篇[二叉树:层序遍历登场!](https://mp.weixin.qq.com/s/4-bDKi7SdwfBGRm9FYduiA) +如果对以上代码不理解,或者不清楚二叉树的层序遍历,可以看这篇[二叉树:层序遍历登场!](https://programmercarl.com/0102.二叉树的层序遍历.html) ## 拓展 @@ -196,7 +195,7 @@ public: 如果非要使用递归中序的方式写,也可以,如下代码就可以避免节点左右孩子翻转两次的情况: -```C++ +```CPP class Solution { public: TreeNode* invertTree(TreeNode* root) { @@ -215,7 +214,7 @@ public: 代码如下: -```C++ +```CPP class Solution { public: TreeNode* invertTree(TreeNode* root) { @@ -364,7 +363,9 @@ class Solution: return root ``` -### Go +### Go + +递归版本的前序遍历 ```Go func invertTree(root *TreeNode) *TreeNode { @@ -382,6 +383,96 @@ func invertTree(root *TreeNode) *TreeNode { } ``` +递归版本的后序遍历 + +```go +func invertTree(root *TreeNode) *TreeNode { + if root==nil{ + return root + } + invertTree(root.Left)//遍历左节点 + invertTree(root.Right)//遍历右节点 + root.Left,root.Right=root.Right,root.Left//交换 + return root +} +``` + +迭代版本的前序遍历 + +```go +func invertTree(root *TreeNode) *TreeNode { + stack:=[]*TreeNode{} + node:=root + for node!=nil||len(stack)>0{ + for node!=nil{ + node.Left,node.Right=node.Right,node.Left//交换 + stack=append(stack,node) + node=node.Left + } + node=stack[len(stack)-1] + stack=stack[:len(stack)-1] + node=node.Right + } + return root +} +``` + +迭代版本的后序遍历 + +```go +func invertTree(root *TreeNode) *TreeNode { + stack:=[]*TreeNode{} + node:=root + var prev *TreeNode + for node!=nil||len(stack)>0{ + for node!=nil{ + stack=append(stack,node) + node=node.Left + } + node=stack[len(stack)-1] + stack=stack[:len(stack)-1] + if node.Right==nil||node.Right==prev{ + node.Left,node.Right=node.Right,node.Left//交换 + prev=node + node=nil + }else { + stack=append(stack,node) + node=node.Right + } + } + return root +} +``` + +层序遍历 + +```go +func invertTree(root *TreeNode) *TreeNode { + if root==nil{ + return root + } + queue:=list.New() + node:=root + queue.PushBack(node) + for queue.Len()>0{ + length:=queue.Len() + for i:=0;i +
diff --git a/problems/0232.用栈实现队列.md b/problems/0232.用栈实现队列.md index 6890fc2b..9f6bb90f 100644 --- a/problems/0232.用栈实现队列.md +++ b/problems/0232.用栈实现队列.md @@ -11,7 +11,7 @@ # 232.用栈实现队列 -https://leetcode-cn.com/problems/implement-queue-using-stacks/ +[力扣题目链接](https://leetcode-cn.com/problems/implement-queue-using-stacks/) 使用栈实现队列的下列操作: @@ -67,7 +67,7 @@ queue.empty(); C++代码如下: -```C++ +```CPP class MyQueue { public: stack stIn; @@ -129,101 +129,6 @@ public: Java: -使用Stack(堆栈)同名方法: -```java -class MyQueue { - // java中的 Stack 有设计上的缺陷,官方推荐使用 Deque(双端队列) 代替 Stack - Deque stIn; - Deque stOut; - /** Initialize your data structure here. */ - public MyQueue() { - stIn = new ArrayDeque<>(); - stOut = new ArrayDeque<>(); - } - - /** Push element x to the back of queue. */ - public void push(int x) { - stIn.push(x); - } - - /** Removes the element from in front of queue and returns that element. */ - public int pop() { - // 只要 stOut 为空,那么就应该将 stIn 中所有的元素倒腾到 stOut 中 - if (stOut.isEmpty()) { - while (!stIn.isEmpty()) { - stOut.push(stIn.pop()); - } - } - // 再返回 stOut 中的元素 - return stOut.pop(); - } - - /** Get the front element. */ - public int peek() { - // 直接使用已有的pop函数 - int res = this.pop(); - // 因为pop函数弹出了元素res,所以再添加回去 - stOut.push(res); - return res; - } - - /** Returns whether the queue is empty. */ - public boolean empty() { - // 当 stIn 栈为空时,说明没有元素可以倒腾到 stOut 栈了 - // 并且 stOut 栈也为空时,说明没有以前从 stIn 中倒腾到的元素了 - return stIn.isEmpty() && stOut.isEmpty(); - } -} -``` - -个人习惯写法,使用Deque通用api: -```java -class MyQueue { - // java中的 Stack 有设计上的缺陷,官方推荐使用 Deque(双端队列) 代替 Stack - // Deque 中的 addFirst、removeFirst、peekFirst 等方法等效于 Stack(堆栈) 中的 push、pop、peek - Deque stIn; - Deque stOut; - /** Initialize your data structure here. */ - public MyQueue() { - stIn = new ArrayDeque<>(); - stOut = new ArrayDeque<>(); - } - - /** Push element x to the back of queue. */ - public void push(int x) { - stIn.addLast(x); - } - - /** Removes the element from in front of queue and returns that element. */ - public int pop() { - // 只要 stOut 为空,那么就应该将 stIn 中所有的元素倒腾到 stOut 中 - if (stOut.isEmpty()) { - while (!stIn.isEmpty()) { - stOut.addLast(stIn.pollLast()); - } - } - // 再返回 stOut 中的元素 - return stOut.pollLast(); - } - - /** Get the front element. */ - public int peek() { - // 直接使用已有的pop函数 - int res = this.pop(); - // 因为pop函数弹出了元素res,所以再添加回去 - stOut.addLast(res); - return res; - } - - /** Returns whether the queue is empty. */ - public boolean empty() { - // 当 stIn 栈为空时,说明没有元素可以倒腾到 stOut 栈了 - // 并且 stOut 栈也为空时,说明没有以前从 stIn 中倒腾到的元素了 - return stIn.isEmpty() && stOut.isEmpty(); - } -} -``` - ```java class MyQueue { @@ -281,48 +186,53 @@ class MyQueue { Python: ```python -# 使用两个栈实现先进先出的队列 class MyQueue: + def __init__(self): """ - Initialize your data structure here. + in主要负责push,out主要负责pop """ - self.stack1 = list() - self.stack2 = list() + self.stack_in = [] + self.stack_out = [] + def push(self, x: int) -> None: """ - Push element x to the back of queue. + 有新元素进来,就往in里面push """ - # self.stack1用于接受元素 - self.stack1.append(x) + self.stack_in.append(x) + def pop(self) -> int: """ Removes the element from in front of queue and returns that element. """ - # self.stack2用于弹出元素,如果self.stack2为[],则将self.stack1中元素全部弹出给self.stack2 - if self.stack2 == []: - while self.stack1: - tmp = self.stack1.pop() - self.stack2.append(tmp) - return self.stack2.pop() + if self.empty(): + return None + + if self.stack_out: + return self.stack_out.pop() + else: + for i in range(len(self.stack_in)): + self.stack_out.append(self.stack_in.pop()) + return self.stack_out.pop() + def peek(self) -> int: """ Get the front element. """ - if self.stack2 == []: - while self.stack1: - tmp = self.stack1.pop() - self.stack2.append(tmp) - return self.stack2[-1] + ans = self.pop() + self.stack_out.append(ans) + return ans + def empty(self) -> bool: """ - Returns whether the queue is empty. + 只要in或者out有元素,说明队列不为空 """ - return self.stack1 == [] and self.stack2 == [] + return not (self.stack_in or self.stack_out) + ``` @@ -384,7 +294,7 @@ func (this *MyQueue) Peek() int { func (this *MyQueue) Empty() bool { return len(this.stack) == 0 && len(this.back) == 0 } - +``` javaScript: @@ -442,10 +352,8 @@ MyQueue.prototype.empty = function() { ``` - - ----------------------- * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0234.回文链表.md b/problems/0234.回文链表.md index 945d2ef4..631d2f6b 100644 --- a/problems/0234.回文链表.md +++ b/problems/0234.回文链表.md @@ -9,7 +9,7 @@ # 234.回文链表 -题目链接:https://leetcode-cn.com/problems/palindrome-linked-list/ +[力扣题目链接](https://leetcode-cn.com/problems/palindrome-linked-list/) 请判断一个链表是否为回文链表。 @@ -30,7 +30,7 @@ 代码也比较简单。如下: -```C++ +```CPP class Solution { public: bool isPalindrome(ListNode* head) { @@ -51,7 +51,7 @@ public: 上面代码可以在优化,就是先求出链表长度,然后给定vector的初始长度,这样避免vector每次添加节点重新开辟空间 -```C++ +```CPP class Solution { public: bool isPalindrome(ListNode* head) { @@ -95,7 +95,7 @@ public: 代码如下: -```C++ +```CPP class Solution { public: bool isPalindrome(ListNode* head) { @@ -144,21 +144,147 @@ public: ## Java ```java +// 方法一,使用数组 +class Solution { + public boolean isPalindrome(ListNode head) { + int len = 0; + // 统计链表长度 + ListNode cur = head; + while (cur != null) { + len++; + cur = cur.next; + } + cur = head; + int[] res = new int[len]; + // 将元素加到数组之中 + for (int i = 0; i < res.length; i++){ + res[i] = cur.val; + cur = cur.next; + } + // 比较回文 + for (int i = 0, j = len - 1; i < j; i++, j--){ + if (res[i] != res[j]){ + return false; + } + } + return true; + } +} + +// 方法二,快慢指针 +class Solution { + public boolean isPalindrome(ListNode head) { + // 如果为空或者仅有一个节点,返回true + if (head == null && head.next == null) return true; + ListNode slow = head; + ListNode fast = head; + ListNode pre = head; + while (fast != null && fast.next != null){ + pre = slow; // 记录slow的前一个结点 + slow = slow.next; + fast = fast.next.next; + } + pre.next = null; // 分割两个链表 + + // 前半部分 + ListNode cur1 = head; + // 后半部分。这里使用了反转链表 + ListNode cur2 = reverseList(slow); + + while (cur1 != null){ + if (cur1.val != cur2.val) return false; + + // 注意要移动两个结点 + cur1 = cur1.next; + cur2 = cur2.next; + } + return true; + } + ListNode reverseList(ListNode head){ + // 反转链表 + ListNode tmp = null; + ListNode pre = null; + while (head != null){ + tmp = head.next; + head.next = pre; + pre = head; + head = tmp; + } + return pre; + } +} ``` ## Python -```python +```python3 +#数组模拟 +class Solution: + def isPalindrome(self, head: ListNode) -> bool: + length = 0 + tmp = head + while tmp: #求链表长度 + length += 1 + tmp = tmp.next + + result = [0] * length + tmp = head + index = 0 + while tmp: #链表元素加入数组 + result[index] = tmp.val + index += 1 + tmp = tmp.next + + i, j = 0, length - 1 + while i < j: # 判断回文 + if result[i] != result[j]: + return False + i += 1 + j -= 1 + return True + +#反转后半部分链表 +class Solution: + def isPalindrome(self, head: ListNode) -> bool: + if head == None or head.next == None: + return True + slow, fast = head, head + while fast and fast.next: + pre = slow + slow = slow.next + fast = fast.next.next + + pre.next = None # 分割链表 + cur1 = head # 前半部分 + cur2 = self.reverseList(slow) # 反转后半部分,总链表长度如果是奇数,cur2比cur1多一个节点 + while cur1: + if cur1.val != cur2.val: + return False + cur1 = cur1.next + cur2 = cur2.next + return True + + def reverseList(self, head: ListNode) -> ListNode: + cur = head + pre = None + while(cur!=None): + temp = cur.next # 保存一下cur的下一个节点 + cur.next = pre # 反转 + pre = cur + cur = temp + return pre ``` ## Go ```go + ``` ## JavaScript ```js + ``` @@ -166,5 +292,5 @@ public: * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0235.二叉搜索树的最近公共祖先.md b/problems/0235.二叉搜索树的最近公共祖先.md index dffc89e6..25bbf7e4 100644 --- a/problems/0235.二叉搜索树的最近公共祖先.md +++ b/problems/0235.二叉搜索树的最近公共祖先.md @@ -7,9 +7,9 @@

欢迎大家参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!

-## 235. 二叉搜索树的最近公共祖先 +# 235. 二叉搜索树的最近公共祖先 -链接:https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-search-tree/ +[力扣题目链接](https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-search-tree/) 给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。 @@ -21,14 +21,15 @@ 示例 1: -输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8 -输出: 6 -解释: 节点 2 和节点 8 的最近公共祖先是 6。 +* 输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8 +* 输出: 6 +* 解释: 节点 2 和节点 8 的最近公共祖先是 6。 + 示例 2: -输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 4 -输出: 2 -解释: 节点 2 和节点 4 的最近公共祖先是 2, 因为根据定义最近公共祖先节点可以为节点本身。 +* 输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 4 +* 输出: 2 +* 解释: 节点 2 和节点 4 的最近公共祖先是 2, 因为根据定义最近公共祖先节点可以为节点本身。 说明: @@ -36,9 +37,11 @@ * 所有节点的值都是唯一的。 * p、q 为不同节点且均存在于给定的二叉搜索树中。 -## 思路 +# 思路 -做过[二叉树:公共祖先问题](https://mp.weixin.qq.com/s/n6Rk3nc_X3TSkhXHrVmBTQ)题目的同学应该知道,利用回溯从底向上搜索,遇到一个节点的左子树里有p,右子树里有q,那么当前节点就是最近公共祖先。 + + +做过[二叉树:公共祖先问题](https://programmercarl.com/0236.二叉树的最近公共祖先.html)题目的同学应该知道,利用回溯从底向上搜索,遇到一个节点的左子树里有p,右子树里有q,那么当前节点就是最近公共祖先。 那么本题是二叉搜索树,二叉搜索树是有序的,那得好好利用一下这个特点。 @@ -48,7 +51,7 @@ 理解这一点,本题就很好解了。 -和[二叉树:公共祖先问题](https://mp.weixin.qq.com/s/n6Rk3nc_X3TSkhXHrVmBTQ)不同,普通二叉树求最近公共祖先需要使用回溯,从底向上来查找,二叉搜索树就不用了,因为搜索树有序(相当于自带方向),那么只要从上向下遍历就可以了。 +和[二叉树:公共祖先问题](https://programmercarl.com/0236.二叉树的最近公共祖先.html)不同,普通二叉树求最近公共祖先需要使用回溯,从底向上来查找,二叉搜索树就不用了,因为搜索树有序(相当于自带方向),那么只要从上向下遍历就可以了。 那么我们可以采用前序遍历(其实这里没有中节点的处理逻辑,遍历顺序无所谓了)。 @@ -58,6 +61,7 @@ 可以看出直接按照指定的方向,就可以找到节点4,为最近公共祖先,而且不需要遍历整棵树,找到结果直接返回! +## 递归法 递归三部曲如下: @@ -93,7 +97,7 @@ if (cur == NULL) return cur; 代码如下: -```C++ +```CPP if (cur->val > p->val && cur->val > q->val) { TreeNode* left = traversal(cur->left, p, q); if (left != NULL) { @@ -105,13 +109,12 @@ if (cur->val > p->val && cur->val > q->val) { **细心的同学会发现,在这里调用递归函数的地方,把递归函数的返回值left,直接return**。 -在[二叉树:公共祖先问题](https://mp.weixin.qq.com/s/n6Rk3nc_X3TSkhXHrVmBTQ)中,如果递归函数有返回值,如何区分要搜索一条边,还是搜索整个树。 +在[二叉树:公共祖先问题](https://programmercarl.com/0236.二叉树的最近公共祖先.html)中,如果递归函数有返回值,如何区分要搜索一条边,还是搜索整个树。 搜索一条边的写法: ``` if (递归函数(root->left)) return ; - if (递归函数(root->right)) return ; ``` @@ -128,7 +131,7 @@ left与right的逻辑处理; 如果 cur->val 小于 p->val,同时 cur->val 小于 q->val,那么就应该向右遍历(目标区间在右子树)。 -``` +```CPP if (cur->val < p->val && cur->val < q->val) { TreeNode* right = traversal(cur->right, p, q); if (right != NULL) { @@ -140,14 +143,14 @@ if (cur->val < p->val && cur->val < q->val) { 剩下的情况,就是cur节点在区间(p->val <= cur->val && cur->val <= q->val)或者 (q->val <= cur->val && cur->val <= p->val)中,那么cur就是最近公共祖先了,直接返回cur。 代码如下: + ``` return cur; - ``` 那么整体递归代码如下: -```C++ +```CPP class Solution { private: TreeNode* traversal(TreeNode* cur, TreeNode* p, TreeNode* q) { @@ -177,7 +180,7 @@ public: 精简后代码如下: -```C++ +```CPP class Solution { public: TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) { @@ -192,13 +195,13 @@ public: ## 迭代法 -对于二叉搜索树的迭代法,大家应该在[二叉树:二叉搜索树登场!](https://mp.weixin.qq.com/s/vsKrWRlETxCVsiRr8v_hHg)就了解了。 +对于二叉搜索树的迭代法,大家应该在[二叉树:二叉搜索树登场!](https://programmercarl.com/0700.二叉搜索树中的搜索.html)就了解了。 利用其有序性,迭代的方式还是比较简单的,解题思路在递归中已经分析了。 迭代代码如下: -```C++ +```CPP class Solution { public: TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) { @@ -216,19 +219,32 @@ public: 灵魂拷问:是不是又被简单的迭代法感动到痛哭流涕? -## 总结 +# 总结 -对于二叉搜索树的最近祖先问题,其实要比[普通二叉树公共祖先问题](https://mp.weixin.qq.com/s/n6Rk3nc_X3TSkhXHrVmBTQ)简单的多。 +对于二叉搜索树的最近祖先问题,其实要比[普通二叉树公共祖先问题](https://programmercarl.com/0236.二叉树的最近公共祖先.html)简单的多。 不用使用回溯,二叉搜索树自带方向性,可以方便的从上向下查找目标区间,遇到目标区间内的节点,直接返回。 最后给出了对应的迭代法,二叉搜索树的迭代法甚至比递归更容易理解,也是因为其有序性(自带方向性),按照目标区间找就行了。 -## 其他语言版本 +# 其他语言版本 -Java: +## Java + +递归法: +```java +class Solution { + public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { + if (root.val > p.val && root.val > q.val) return lowestCommonAncestor(root.left, p, q); + if (root.val < p.val && root.val < q.val) return lowestCommonAncestor(root.right, p, q); + return root; + } +} +``` + +迭代法: ```java class Solution { public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { @@ -246,15 +262,11 @@ class Solution { } ``` -Python: -```python -# Definition for a binary tree node. -# class TreeNode: -# def __init__(self, x): -# self.val = x -# self.left = None -# self.right = None +## Python + +递归法: +```python class Solution: def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode': if not root: return root //中 @@ -264,18 +276,14 @@ class Solution: return self.lowestCommonAncestor(root.right,p,q) //右 else: return root ``` -Go: -> BSL法 +迭代法: + + +## Go + +递归法: ```go -/** - * Definition for a binary tree node. - * type TreeNode struct { - * Val int - * Left *TreeNode - * Right *TreeNode - * } - */ //利用BSL的性质(前序遍历有序) func lowestCommonAncestor(root, p, q *TreeNode) *TreeNode { if root==nil{return nil} @@ -287,34 +295,10 @@ func lowestCommonAncestor(root, p, q *TreeNode) *TreeNode { } ``` -> 普通法 -```go -/** - * Definition for a binary tree node. - * type TreeNode struct { - * Val int - * Left *TreeNode - * Right *TreeNode - * } - */ -//递归会将值层层返回 -func lowestCommonAncestor(root, p, q *TreeNode) *TreeNode { - //终止条件 - if root==nil||root.Val==p.Val||root.Val==q.Val{return root}//最后为空或者找到一个值时,就返回这个值 - //后序遍历 - findLeft:=lowestCommonAncestor(root.Left,p,q) - findRight:=lowestCommonAncestor(root.Right,p,q) - //处理单层逻辑 - if findLeft!=nil&&findRight!=nil{return root}//说明在root节点的两边 - if findLeft==nil{//左边没找到,就说明在右边找到了 - return findRight - }else {return findLeft} -} -``` +## JavaScript -JavaScript版本: -1. 使用递归的方法 +递归法: ```javascript var lowestCommonAncestor = function(root, p, q) { // 使用递归的方法 @@ -336,7 +320,8 @@ var lowestCommonAncestor = function(root, p, q) { return root; }; ``` -2. 使用迭代的方法 + +迭代法 ```javascript var lowestCommonAncestor = function(root, p, q) { // 使用迭代的方法 @@ -355,9 +340,8 @@ var lowestCommonAncestor = function(root, p, q) { ``` - ----------------------- * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0236.二叉树的最近公共祖先.md b/problems/0236.二叉树的最近公共祖先.md index 7b5deb56..59345a24 100644 --- a/problems/0236.二叉树的最近公共祖先.md +++ b/problems/0236.二叉树的最近公共祖先.md @@ -9,9 +9,9 @@ > 本来是打算将二叉树和二叉搜索树的公共祖先问题一起讲,后来发现篇幅过长了,只能先说一说二叉树的公共祖先问题。 -## 236. 二叉树的最近公共祖先 +# 236. 二叉树的最近公共祖先 -题目链接:https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-tree/ +[力扣题目链接](https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-tree/) 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。 @@ -35,7 +35,7 @@ * 所有节点的值都是唯一的。 * p、q 为不同节点且均存在于给定的二叉树中。 -## 思路 +# 思路 遇到这个题目首先想的是要是能自底向上查找就好了,这样就可以找到公共祖先了。 @@ -79,7 +79,7 @@ if (root == q || root == p || root == NULL) return root; 值得注意的是 本题函数有返回值,是因为回溯的过程需要递归函数的返回值做判断,但本题我们依然要遍历树的所有节点。 -我们在[二叉树:递归函数究竟什么时候需要返回值,什么时候不要返回值?](https://mp.weixin.qq.com/s/6TWAVjxQ34kVqROWgcRFOg)中说了 递归函数有返回值就是要遍历某一条边,但有返回值也要看如何处理返回值! +我们在[二叉树:递归函数究竟什么时候需要返回值,什么时候不要返回值?](https://programmercarl.com/0112.路径总和.html)中说了 递归函数有返回值就是要遍历某一条边,但有返回值也要看如何处理返回值! 如果递归函数有返回值,如何区分要搜索一条边,还是搜索整个树呢? @@ -150,7 +150,7 @@ TreeNode* right = lowestCommonAncestor(root->right, p, q); 代码如下: -```C++ +```CPP if (left == NULL && right != NULL) return right; else if (left != NULL && right == NULL) return left; else { // (left == NULL && right == NULL) @@ -167,7 +167,7 @@ else { // (left == NULL && right == NULL) 整体代码如下: -```C++ +```CPP class Solution { public: TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) { @@ -188,7 +188,7 @@ public: 稍加精简,代码如下: -```C++ +```CPP class Solution { public: TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) { @@ -202,7 +202,7 @@ public: }; ``` -## 总结 +# 总结 这道题目刷过的同学未必真正了解这里面回溯的过程,以及结果是如何一层一层传上去的。 @@ -219,10 +219,10 @@ public: 本题没有给出迭代法,因为迭代法不适合模拟回溯的过程。理解递归的解法就够了。 -## 其他语言版本 +# 其他语言版本 -Java: +## Java ```Java class Solution { public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { @@ -249,7 +249,6 @@ class Solution { ```java // 代码精简版 class Solution { - TreeNode pre; public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { if (root == null || root.val == p.val ||root.val == q.val) return root; TreeNode left = lowestCommonAncestor(root.left,p,q); @@ -262,14 +261,9 @@ class Solution { } ``` -Python: +## Python + ```python -# Definition for a binary tree node. -# class TreeNode: -# def __init__(self, x): -# self.val = x -# self.left = None -# self.right = None //递归 class Solution: def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode': @@ -281,7 +275,9 @@ class Solution: elif not left and right: return right //目标节点是通过right返回的 else: return None //没找到 ``` -Go: + +## Go + ```Go func lowestCommonAncestor(root, p, q *TreeNode) *TreeNode { // check @@ -311,7 +307,8 @@ func lowestCommonAncestor(root, p, q *TreeNode) *TreeNode { } ``` -JavaScript版本: +## JavaScript + ```javascript var lowestCommonAncestor = function(root, p, q) { // 使用递归的方法 @@ -343,4 +340,4 @@ var lowestCommonAncestor = function(root, p, q) { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0239.滑动窗口最大值.md b/problems/0239.滑动窗口最大值.md index dedc3247..a61e4ca8 100644 --- a/problems/0239.滑动窗口最大值.md +++ b/problems/0239.滑动窗口最大值.md @@ -12,7 +12,7 @@ # 239. 滑动窗口最大值 -https://leetcode-cn.com/problems/sliding-window-maximum/ +[力扣题目链接](https://leetcode-cn.com/problems/sliding-window-maximum/) 给定一个数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。 @@ -21,7 +21,7 @@ https://leetcode-cn.com/problems/sliding-window-maximum/ 进阶: 你能在线性时间复杂度内解决此题吗? -  + 提示: @@ -104,11 +104,11 @@ public: 那么我们用什么数据结构来实现这个单调队列呢? -使用deque最为合适,在文章[栈与队列:来看看栈和队列不为人知的一面](https://mp.weixin.qq.com/s/HCXfQ_Bhpi63YaX0ZRSnAQ)中,我们就提到了常用的queue在没有指定容器的情况下,deque就是默认底层容器。 +使用deque最为合适,在文章[栈与队列:来看看栈和队列不为人知的一面](https://programmercarl.com/栈与队列理论基础.html)中,我们就提到了常用的queue在没有指定容器的情况下,deque就是默认底层容器。 基于刚刚说过的单调队列pop和push的规则,代码不难实现,如下: -```C++ +```CPP class MyQueue { //单调队列(从大到小) public: deque que; // 使用deque来实现单调队列 @@ -140,7 +140,7 @@ public: C++代码如下: -```C++ +```CPP class Solution { private: class MyQueue { //单调队列(从大到小) @@ -425,4 +425,4 @@ var maxSlidingWindow = function (nums, k) { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0242.有效的字母异位词.md b/problems/0242.有效的字母异位词.md index 93bba44c..0828d360 100644 --- a/problems/0242.有效的字母异位词.md +++ b/problems/0242.有效的字母异位词.md @@ -11,7 +11,7 @@ ## 242.有效的字母异位词 -https://leetcode-cn.com/problems/valid-anagram/ +[力扣题目链接](https://leetcode-cn.com/problems/valid-anagram/) 给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。 @@ -35,7 +35,7 @@ https://leetcode-cn.com/problems/valid-anagram/ **数组其实就是一个简单哈希表**,而且这道题目中字符串只有小写字符,那么就可以定义一个数组,来记录字符串s里字符出现的次数。 -如果对哈希表的理论基础关于数组,set,map不了解的话可以看这篇:[关于哈希表,你该了解这些!](https://mp.weixin.qq.com/s/g8N6WmoQmsCUw3_BaWxHZA) +如果对哈希表的理论基础关于数组,set,map不了解的话可以看这篇:[关于哈希表,你该了解这些!](https://programmercarl.com/哈希表理论基础.html) 需要定义一个多大的数组呢,定一个数组叫做record,大小为26 就可以了,初始化为0,因为字符a到字符z的ASCII也是26个连续的数值。 @@ -61,7 +61,7 @@ https://leetcode-cn.com/problems/valid-anagram/ C++ 代码如下: -```C++ +```CPP class Solution { public: bool isAnagram(string s, string t) { @@ -198,6 +198,83 @@ var isAnagram = function(s, t) { }; ``` +Swift: +```Swift +func isAnagram(_ s: String, _ t: String) -> Bool { + if s.count != t.count { + return false + } + var record = Array(repeating: 0, count: 26) + let aUnicodeScalar = "a".unicodeScalars.first!.value + for c in s.unicodeScalars { + record[Int(c.value - aUnicodeScalar)] += 1 + } + for c in t.unicodeScalars { + record[Int(c.value - aUnicodeScalar)] -= 1 + } + for value in record { + if value != 0 { + return false + } + } + return true +} +``` + +PHP: +```php +class Solution { + /** + * @param String $s + * @param String $t + * @return Boolean + */ + function isAnagram($s, $t) { + if (strlen($s) != strlen($t)) { + return false; + } + $table = []; + for ($i = 0; $i < strlen($s); $i++) { + if (!isset($table[$s[$i]])) { + $table[$s[$i]] = 1; + } else { + $table[$s[$i]]++; + } + if (!isset($table[$t[$i]])) { + $table[$t[$i]] = -1; + } else { + $table[$t[$i]]--; + } + } + foreach ($table as $record) { + if ($record != 0) { + return false; + } + } + return true; + } +} +``` + +Rust: +```rust +impl Solution { + pub fn is_anagram(s: String, t: String) -> bool { + let mut record = vec![0; 26]; + + let baseChar = 'a'; + + for byte in s.bytes() { + record[byte as usize - baseChar as usize] += 1; + } + for byte in t.bytes() { + record[byte as usize - baseChar as usize] -= 1; + } + + record.iter().filter(|x| **x != 0).count() == 0 + } +} +``` ## 相关题目 * 383.赎金信 @@ -209,4 +286,4 @@ var isAnagram = function(s, t) { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0257.二叉树的所有路径.md b/problems/0257.二叉树的所有路径.md index ce596396..e8a98527 100644 --- a/problems/0257.二叉树的所有路径.md +++ b/problems/0257.二叉树的所有路径.md @@ -9,9 +9,9 @@ > 以为只用了递归,其实还用了回溯 -## 257. 二叉树的所有路径 +# 257. 二叉树的所有路径 -题目地址:https://leetcode-cn.com/problems/binary-tree-paths/ +[力扣题目链接](https://leetcode-cn.com/problems/binary-tree-paths/) 给定一个二叉树,返回所有从根节点到叶子节点的路径。 @@ -20,7 +20,7 @@ 示例: ![257.二叉树的所有路径1](https://img-blog.csdnimg.cn/2021020415161576.png) -## 思路 +# 思路 这道题目要求从根节点到叶子的路径,所以需要前序遍历,这样才方便让父节点指向孩子节点,找到对应的路径。 @@ -77,7 +77,7 @@ if (cur->left == NULL && cur->right == NULL) { 这里我们先使用vector结构的path容器来记录路径,那么终止处理逻辑如下: -```C++ +```CPP if (cur->left == NULL && cur->right == NULL) { // 遇到叶子节点 string sPath; for (int i = 0; i < path.size() - 1; i++) { // 将path里记录的路径转为string格式 @@ -113,7 +113,7 @@ if (cur->right) { 那么回溯要怎么回溯呢,一些同学会这么写,如下: -```C++ +```CPP if (cur->left) { traversal(cur->left, path, result); } @@ -129,7 +129,7 @@ path.pop_back(); 那么代码应该这么写: -```C++ +```CPP if (cur->left) { traversal(cur->left, path, result); path.pop_back(); // 回溯 @@ -142,7 +142,7 @@ if (cur->right) { 那么本题整体代码如下: -```C++ +```CPP class Solution { private: @@ -183,7 +183,7 @@ public: 那么如上代码可以精简成如下代码: -```C++ +```CPP class Solution { private: @@ -215,8 +215,52 @@ public: 那么在如上代码中,**貌似没有看到回溯的逻辑,其实不然,回溯就隐藏在`traversal(cur->left, path + "->", result);`中的 `path + "->"`。** 每次函数调用完,path依然是没有加上"->" 的,这就是回溯了。 -**如果这里还不理解的话,可以看这篇[二叉树:以为使用了递归,其实还隐藏着回溯](https://mp.weixin.qq.com/s/ivLkHzWdhjQQD1rQWe6zWA),我这这篇中详细的解释了递归中如何隐藏着回溯。 ** +为了把这份精简代码的回溯过程展现出来,大家可以试一试把: +```CPP +if (cur->left) traversal(cur->left, path + "->", result); // 左 回溯就隐藏在这里 +``` + +改成如下代码: + +```CPP +path += "->"; +traversal(cur->left, path, result); // 左 +``` + +即: + +```CPP +if (cur->left) { + path += "->"; + traversal(cur->left, path, result); // 左 +} +if (cur->right) { + path += "->"; + traversal(cur->right, path, result); // 右 +} +``` + +此时就没有回溯了,这个代码就是通过不了的了。 + +如果想把回溯加上,就要 在上面代码的基础上,加上回溯,就可以AC了。 + +```CPP +if (cur->left) { + path += "->"; + traversal(cur->left, path, result); // 左 + path.pop_back(); // 回溯 + path.pop_back(); +} +if (cur->right) { + path += "->"; + traversal(cur->right, path, result); // 右 + path.pop_back(); // 回溯 + path.pop_back(); +} +``` + +**大家应该可以感受出来,如果把 `path + "->"`作为函数参数就是可以的,因为并有没有改变path的数值,执行完递归函数之后,path依然是之前的数值(相当于回溯了)** **综合以上,第二种递归的代码虽然精简但把很多重要的点隐藏在了代码细节里,第一种递归写法虽然代码多一些,但是把每一个逻辑处理都完整的展现了出来了。** @@ -225,13 +269,14 @@ public: ## 迭代法 -至于非递归的方式,我们可以依然可以使用前序遍历的迭代方式来模拟遍历路径的过程,对该迭代方式不了解的同学,可以看文章[二叉树:听说递归能做的,栈也能做!](https://mp.weixin.qq.com/s/c_zCrGHIVlBjUH_hJtghCg)和[二叉树:前中后序迭代方式的写法就不能统一一下么?](https://mp.weixin.qq.com/s/WKg0Ty1_3SZkztpHubZPRg)。 + +至于非递归的方式,我们可以依然可以使用前序遍历的迭代方式来模拟遍历路径的过程,对该迭代方式不了解的同学,可以看文章[二叉树:听说递归能做的,栈也能做!](https://programmercarl.com/二叉树的迭代遍历.html)和[二叉树:前中后序迭代方式统一写法](https://programmercarl.com/二叉树的统一迭代法.html)。 这里除了模拟递归需要一个栈,同时还需要一个栈来存放对应的遍历路径。 C++代码如下: -```C++ +```CPP class Solution { public: vector binaryTreePaths(TreeNode* root) { @@ -262,7 +307,7 @@ public: ``` 当然,使用java的同学,可以直接定义一个成员变量为object的栈`Stack stack = new Stack<>();`,这样就不用定义两个栈了,都放到一个栈里就可以了。 -## 总结 +# 总结 **本文我们开始初步涉及到了回溯,很多同学过了这道题目,可能都不知道自己其实使用了回溯,回溯和递归都是相伴相生的。** @@ -278,7 +323,7 @@ public: -## 其他语言版本 +# 其他语言版本 Java: @@ -321,83 +366,100 @@ class Solution { } } } - -//解法二(常规前序遍历,不用回溯),更容易理解 +``` +```java +// 解法2 class Solution { + /** + * 迭代法 + */ public List binaryTreePaths(TreeNode root) { - List res = new ArrayList<>(); - helper(root, new StringBuilder(), res); - return res; - } - - public void helper(TreeNode root, StringBuilder sb, List res) { - if (root == null) {return;} - // 遇到叶子结点就放入当前路径到res集合中 - if (root.left == null && root.right ==null) { - sb.append(root.val); - res.add(sb.toString()); - // 记得结束当前方法 - return; + List result = new ArrayList<>(); + if (root == null) + return result; + Stack stack = new Stack<>(); + // 节点和路径同时入栈 + stack.push(root); + stack.push(root.val + ""); + while (!stack.isEmpty()) { + // 节点和路径同时出栈 + String path = (String) stack.pop(); + TreeNode node = (TreeNode) stack.pop(); + // 若找到叶子节点 + if (node.left == null && node.right == null) { + result.add(path); + } + //右子节点不为空 + if (node.right != null) { + stack.push(node.right); + stack.push(path + "->" + node.right.val); + } + //左子节点不为空 + if (node.left != null) { + stack.push(node.left); + stack.push(path + "->" + node.left.val); + } } - helper(root.left,new StringBuilder(sb).append(root.val + "->"),res); - helper(root.right,new StringBuilder(sb).append(root.val + "->"),res); + return result; } } - -//针对解法二优化,思路本质是一样的 -class Solution { - public List binaryTreePaths(TreeNode root) { - List res = new ArrayList<>(); - helper(root, "", res); - return res; - } - - public void helper(TreeNode root, String path, List res) { - if (root == null) {return;} - // 由原始解法二可以知道,root的值肯定会下面某一个条件加入到path中,那么干脆直接在这一步加入即可 - StringBuilder sb = new StringBuilder(path); - sb.append(root.val); - if (root.left == null && root.right ==null) { - res.add(sb.toString()); - }else{ - // 如果是非叶子结点则还需要跟上一个 “->” - sb.append("->"); - helper(root.left,sb.toString(),res); - helper(root.right,sb.toString(),res); - } - } -} - ``` Python: ```Python -# class TreeNode: -# def __init__(self, val=0, left=None, right=None): -# self.val = val -# self.left = left -# self.right = right class Solution: + """二叉树的所有路径 递归法""" + def binaryTreePaths(self, root: TreeNode) -> List[str]: - path=[] - res=[] - def backtrace(root, path): - if not root:return - path.append(root.val) - if (not root.left)and (not root.right): - res.append(path[:]) - ways=[] - if root.left:ways.append(root.left) - if root.right:ways.append(root.right) - for way in ways: - backtrace(way,path) - path.pop() - backtrace(root,path) - return ["->".join(list(map(str,i))) for i in res] + path, result = '', [] + self.traversal(root, path, result) + return result + + def traversal(self, cur: TreeNode, path: List, result: List): + path += str(cur.val) + # 如果当前节点为叶子节点,添加路径到结果中 + if not (cur.left or cur.right): + result.append(path) + return + + if cur.left: + self.traversal(cur.left, path + '->', result) + + if cur.right: + self.traversal(cur.right, path + '->', result) ``` -Go: +```python +from collections import deque + + +class Solution: + """二叉树的所有路径 迭代法""" + + def binaryTreePaths(self, root: TreeNode) -> List[str]: + # 题目中节点数至少为1 + stack, path_st, result = deque([root]), deque(), [] + path_st.append(str(root.val)) + + while stack: + cur = stack.pop() + path = path_st.pop() + # 如果当前节点为叶子节点,添加路径到结果中 + if not (cur.left or cur.right): + result.append(path) + if cur.right: + stack.append(cur.right) + path_st.append(path + '->' + str(cur.right.val)) + if cur.left: + stack.append(cur.left) + path_st.append(path + '->' + str(cur.left.val)) + + return result +``` + + +Go: ```go func binaryTreePaths(root *TreeNode) []string { res := make([]string, 0) @@ -422,7 +484,9 @@ func binaryTreePaths(root *TreeNode) []string { ``` JavaScript: + 1.递归版本 + ```javascript var binaryTreePaths = function(root) { //递归遍历+递归三部曲 @@ -452,4 +516,4 @@ var binaryTreePaths = function(root) { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0279.完全平方数.md b/problems/0279.完全平方数.md index d0922de1..865669c2 100644 --- a/problems/0279.完全平方数.md +++ b/problems/0279.完全平方数.md @@ -9,7 +9,7 @@ ## 279.完全平方数 -题目地址:https://leetcode-cn.com/problems/perfect-squares/ +[力扣题目链接](https://leetcode-cn.com/problems/perfect-squares/) 给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, ...)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。 @@ -36,7 +36,7 @@ **我来把题目翻译一下:完全平方数就是物品(可以无限件使用),凑个正整数n就是背包,问凑满这个背包最少有多少物品?** -感受出来了没,这么浓厚的完全背包氛围,而且和昨天的题目[动态规划:322. 零钱兑换](https://mp.weixin.qq.com/s/dyk-xNilHzNtVdPPLObSeQ)就是一样一样的! +感受出来了没,这么浓厚的完全背包氛围,而且和昨天的题目[动态规划:322. 零钱兑换](https://programmercarl.com/0322.零钱兑换.html)就是一样一样的! 动规五部曲分析如下: @@ -70,13 +70,13 @@ dp[0]表示 和为0的完全平方数的最小数量,那么dp[0]一定是0。 如果求排列数就是外层for遍历背包,内层for循环遍历物品。 -在[动态规划:322. 零钱兑换](https://mp.weixin.qq.com/s/dyk-xNilHzNtVdPPLObSeQ)中我们就深入探讨了这个问题,本题也是一样的,是求最小数! +在[动态规划:322. 零钱兑换](https://programmercarl.com/0322.零钱兑换.html)中我们就深入探讨了这个问题,本题也是一样的,是求最小数! **所以本题外层for遍历背包,里层for遍历物品,还是外层for遍历物品,内层for遍历背包,都是可以的!** 我这里先给出外层遍历背包,里层遍历物品的代码: -```C++ +```CPP vector dp(n + 1, INT_MAX); dp[0] = 0; for (int i = 0; i <= n; i++) { // 遍历背包 @@ -106,7 +106,7 @@ dp[5] = min(dp[4] + 1, dp[1] + 1) = 2 以上动规五部曲分析完毕C++代码如下: -```C++ +```CPP // 版本一 class Solution { public: @@ -125,7 +125,7 @@ public: 同样我在给出先遍历物品,在遍历背包的代码,一样的可以AC的。 -```C++ +```CPP // 版本二 class Solution { public: @@ -146,7 +146,7 @@ public: ## 总结 -如果大家认真做了昨天的题目[动态规划:322. 零钱兑换](https://mp.weixin.qq.com/s/dyk-xNilHzNtVdPPLObSeQ),今天这道就非常简单了,一样的套路一样的味道。 +如果大家认真做了昨天的题目[动态规划:322. 零钱兑换](https://programmercarl.com/0322.零钱兑换.html),今天这道就非常简单了,一样的套路一样的味道。 但如果没有按照「代码随想录」的题目顺序来做的话,做动态规划或者做背包问题,上来就做这道题,那还是挺难的! @@ -161,6 +161,7 @@ public: Java: ```Java class Solution { + // 版本一,先遍历物品, 再遍历背包 public int numSquares(int n) { int max = Integer.MAX_VALUE; int[] dp = new int[n + 1]; @@ -170,7 +171,9 @@ class Solution { } //当和为0时,组合的个数为0 dp[0] = 0; + // 遍历物品 for (int i = 1; i * i <= n; i++) { + // 遍历背包 for (int j = i * i; j <= n; j++) { if (dp[j - i * i] != max) { dp[j] = Math.min(dp[j], dp[j - i * i] + 1); @@ -180,6 +183,28 @@ class Solution { return dp[n]; } } + +class Solution { + // 版本二, 先遍历背包, 再遍历物品 + public int numSquares(int n) { + int max = Integer.MAX_VALUE; + int[] dp = new int[n + 1]; + // 初始化 + for (int j = 0; j <= n; j++) { + dp[j] = max; + } + // 当和为0时,组合的个数为0 + dp[0] = 0; + // 遍历背包 + for (int j = 1; j <= n; j++) { + // 遍历物品 + for (int i = 1; i * i <= j; i++) { + dp[j] = Math.min(dp[j], dp[j - i * i] + 1); + } + } + return dp[n]; + } +} ``` Python: @@ -187,7 +212,7 @@ Python: ```python3 class Solution: def numSquares(self, n: int) -> int: - '''版本一''' + '''版本一,先遍历背包, 再遍历物品''' # 初始化 nums = [i**2 for i in range(1, n + 1) if i**2 <= n] dp = [10**4]*(n + 1) @@ -201,7 +226,7 @@ class Solution: return dp[n] def numSquares1(self, n: int) -> int: - '''版本二''' + '''版本二, 先遍历物品, 再遍历背包''' # 初始化 nums = [i**2 for i in range(1, n + 1) if i**2 <= n] dp = [10**4]*(n + 1) @@ -217,6 +242,22 @@ class Solution: Python3: ```python class Solution: + '''版本一,先遍历背包, 再遍历物品''' + def numSquares(self, n: int) -> int: + dp = [n] * (n + 1) + dp[0] = 0 + # 遍历背包 + for j in range(1, n+1): + for i in range(1, n): + num = i ** 2 + if num > j: break + # 遍历物品 + if j - num >= 0: + dp[j] = min(dp[j], dp[j - num] + 1) + return dp[n] + +class Solution: + '''版本二, 先遍历物品, 再遍历背包''' def numSquares(self, n: int) -> int: # 初始化 # 组成和的完全平方数的最多个数,就是只用1构成 @@ -286,10 +327,38 @@ func min(a, b int) int { } ``` +Javascript: +```Javascript +// 先遍历物品,再遍历背包 +var numSquares1 = function(n) { + let dp = new Array(n + 1).fill(Infinity) + dp[0] = 0 + for(let i = 0; i <= n; i++) { + let val = i * i + for(let j = val; j <= n; j++) { + dp[j] = Math.min(dp[j], dp[j - val] + 1) + } + } + return dp[n] +}; +// 先遍历背包,再遍历物品 +var numSquares2 = function(n) { + let dp = new Array(n + 1).fill(Infinity) + dp[0] = 0 + + for(let i = 1; i <= n; i++) { + for(let j = 1; j * j <= i; j++) { + dp[i] = Math.min(dp[i - j * j] + 1, dp[i]) + } + } + + return dp[n] +}; +``` ----------------------- * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0283.移动零.md b/problems/0283.移动零.md index 8db42a0a..3909bcd5 100644 --- a/problems/0283.移动零.md +++ b/problems/0283.移动零.md @@ -10,7 +10,7 @@ # 283. 移动零 -题目链接:https://leetcode-cn.com/problems/move-zeroes/ +[力扣题目链接](https://leetcode-cn.com/problems/move-zeroes/) 给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。 @@ -26,13 +26,13 @@ # 思路 -做这道题目之前,大家可以做一做[27.移除元素](https://mp.weixin.qq.com/s/RMkulE4NIb6XsSX83ra-Ww) +做这道题目之前,大家可以做一做[27.移除元素](https://programmercarl.com/0027.移除元素.html) 这道题目,使用暴力的解法,可以两层for循环,模拟数组删除元素(也就是向前覆盖)的过程。 -好了,我们说一说双指针法,大家如果对双指针还不熟悉,可以看我的这篇总结[双指针法:总结篇!](https://mp.weixin.qq.com/s/PLfYLuUIGDR6xVRQ_jTrmg)。 +好了,我们说一说双指针法,大家如果对双指针还不熟悉,可以看我的这篇总结[双指针法:总结篇!](https://programmercarl.com/双指针总结.html)。 -双指针法在数组移除元素中,可以达到O(n)的时间复杂度,在[27.移除元素](https://mp.weixin.qq.com/s/RMkulE4NIb6XsSX83ra-Ww)里已经详细讲解了,那么本题和移除元素其实是一个套路。 +双指针法在数组移除元素中,可以达到O(n)的时间复杂度,在[27.移除元素](https://programmercarl.com/0027.移除元素.html)里已经详细讲解了,那么本题和移除元素其实是一个套路。 **相当于对整个数组移除元素0,然后slowIndex之后都是移除元素0的冗余元素,把这些元素都赋值为0就可以了**。 @@ -42,7 +42,7 @@ C++代码如下: -```C++ +```CPP class Solution { public: void moveZeroes(vector& nums) { @@ -100,5 +100,5 @@ JavaScript: * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0300.最长上升子序列.md b/problems/0300.最长上升子序列.md index 9a7c543b..fdd5fda8 100644 --- a/problems/0300.最长上升子序列.md +++ b/problems/0300.最长上升子序列.md @@ -8,13 +8,13 @@ ## 300.最长递增子序列 -题目链接:https://leetcode-cn.com/problems/longest-increasing-subsequence/ +[力扣题目链接](https://leetcode-cn.com/problems/longest-increasing-subsequence/) 给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。 子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。 -  + 示例 1: 输入:nums = [10,9,2,5,3,7,101,18] 输出:4 @@ -27,20 +27,19 @@ 示例 3: 输入:nums = [7,7,7,7,7,7,7] 输出:1 -  + 提示: * 1 <= nums.length <= 2500 * -10^4 <= nums[i] <= 104 - ## 思路 最长上升子序列是动规的经典题目,这里dp[i]是可以根据dp[j] (j < i)推导出来的,那么依然用动规五部曲来分析详细一波: 1. dp[i]的定义 -**dp[i]表示i之前包括i的最长上升子序列**。 +**dp[i]表示i之前包括i的最长上升子序列的长度**。 2. 状态转移方程 @@ -60,7 +59,7 @@ dp[i] 是有0到i-1各个位置的最长升序子序列 推导而来,那么遍 j其实就是0到i-1,遍历i的循环里外层,遍历j则在内层,代码如下: -```C++ +```CPP for (int i = 1; i < nums.size(); i++) { for (int j = 0; j < i; j++) { if (nums[i] > nums[j]) dp[i] = max(dp[i], dp[j] + 1); @@ -80,7 +79,7 @@ for (int i = 1; i < nums.size(); i++) { 以上五部分析完毕,C++代码如下: -```C++ +```CPP class Solution { public: int lengthOfLIS(vector& nums) { @@ -189,13 +188,18 @@ const lengthOfLIS = (nums) => { return result; }; ``` -*复杂度分析* -- 时间复杂度:O(nlogn)。数组 nums 的长度为 n,我们依次用数组中的元素去更新 dp 数组,相当于插入最后递增的元素,而更新 dp 数组时需要进行 O(logn) 的二分搜索,所以总时间复杂度为 O(nlogn)。 -- 空间复杂度:O(n),需要额外使用长度为 n 的 dp 数组。 + + + + + + + + ----------------------- * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0309.最佳买卖股票时机含冷冻期.md b/problems/0309.最佳买卖股票时机含冷冻期.md index e28e8369..59178c64 100644 --- a/problems/0309.最佳买卖股票时机含冷冻期.md +++ b/problems/0309.最佳买卖股票时机含冷冻期.md @@ -8,9 +8,11 @@ ## 309.最佳买卖股票时机含冷冻期 -题目链接:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-with-cooldown/ +[力扣题目链接](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-with-cooldown/) -给定一个整数数组,其中第 i 个元素代表了第 i 天的股票价格 。​ +[https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-with-cooldown/](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-with-cooldown/) + +给定一个整数数组,其中第 i 个元素代表了第 i 天的股票价格 。 设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票): @@ -25,12 +27,12 @@ ## 思路 -> 之前我们在[动态规划:最佳买卖股票时机含冷冻期](https://mp.weixin.qq.com/s/IgC0iWWCDpYL9ZbTHGHgfw)讲过一次这道题目,讲解的过程感觉不是很严谨,和录友们也聊过这个问题,本着对大家负责的态度,有问题的地方我都会及时纠正,所以重新发文讲解一下。 +> 之前我们在[动态规划:最佳买卖股票时机含冷冻期](https://programmercarl.com/0309.最佳买卖股票时机含冷冻期.html)讲过一次这道题目,讲解的过程感觉不是很严谨,和录友们也聊过这个问题,本着对大家负责的态度,有问题的地方我都会及时纠正,所以重新发文讲解一下。 -相对于[动态规划:122.买卖股票的最佳时机II](https://mp.weixin.qq.com/s/d4TRWFuhaY83HPa6t5ZL-w),本题加上了一个冷冻期 +相对于[动态规划:122.买卖股票的最佳时机II](https://programmercarl.com/0122.买卖股票的最佳时机II(动态规划).html),本题加上了一个冷冻期 -在[动态规划:122.买卖股票的最佳时机II](https://mp.weixin.qq.com/s/d4TRWFuhaY83HPa6t5ZL-w) 中有两个状态,持有股票后的最多现金,和不持有股票的最多现金。 +在[动态规划:122.买卖股票的最佳时机II](https://programmercarl.com/0122.买卖股票的最佳时机II(动态规划).html) 中有两个状态,持有股票后的最多现金,和不持有股票的最多现金。 动规五部曲,分析如下: @@ -95,7 +97,7 @@ p[i][3] = dp[i - 1][2]; 综上分析,递推代码如下: -```C++ +```CPP dp[i][0] = max(dp[i - 1][0], max(dp[i - 1][3], dp[i - 1][1]) - prices[i]; dp[i][1] = max(dp[i - 1][1], dp[i - 1][3]); dp[i][2] = dp[i - 1][0] + prices[i]; @@ -129,7 +131,7 @@ dp[i][3] = dp[i - 1][2]; 代码如下: -```C++ +```CPP class Solution { public: int maxProfit(vector& prices) { @@ -236,4 +238,4 @@ const maxProfit = (prices) => { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0322.零钱兑换.md b/problems/0322.零钱兑换.md index 302ae789..cdc5027c 100644 --- a/problems/0322.零钱兑换.md +++ b/problems/0322.零钱兑换.md @@ -9,7 +9,7 @@ ## 322. 零钱兑换 -题目链接:https://leetcode-cn.com/problems/coin-change/ +[力扣题目链接](https://leetcode-cn.com/problems/coin-change/) 给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。 @@ -44,7 +44,7 @@ ## 思路 -在[动态规划:518.零钱兑换II](https://mp.weixin.qq.com/s/PlowDsI4WMBOzf3q80AksQ)中我们已经兑换一次零钱了,这次又要兑换,套路不一样! +在[动态规划:518.零钱兑换II](https://programmercarl.com/0518.零钱兑换II.html)中我们已经兑换一次零钱了,这次又要兑换,套路不一样! 题目中说每种硬币的数量是无限的,可以看出是典型的完全背包问题。 @@ -91,7 +91,7 @@ dp[0] = 0; **如果求排列数就是外层for遍历背包,内层for循环遍历物品**。 -在动态规划专题我们讲过了求组合数是[动态规划:518.零钱兑换II](https://mp.weixin.qq.com/s/PlowDsI4WMBOzf3q80AksQ),求排列数是[动态规划:377. 组合总和 Ⅳ](https://mp.weixin.qq.com/s/Iixw0nahJWQgbqVNk8k6gA)。 +在动态规划专题我们讲过了求组合数是[动态规划:518.零钱兑换II](https://programmercarl.com/0518.零钱兑换II.html),求排列数是[动态规划:377. 组合总和 Ⅳ](https://programmercarl.com/0377.组合总和Ⅳ.html)。 **所以本题的两个for循环的关系是:外层for循环遍历物品,内层for遍历背包或者外层for遍历背包,内层for循环遍历物品都是可以的!** @@ -112,7 +112,7 @@ dp[amount]为最终结果。 ## C++代码 以上分析完毕,C++ 代码如下: -```C++ +```CPP // 版本一 class Solution { public: @@ -134,7 +134,7 @@ public: 对于遍历方式遍历背包放在外循环,遍历物品放在内循环也是可以的,我就直接给出代码了 -```C++ +```CPP // 版本二 class Solution { public: @@ -166,7 +166,7 @@ public: 那么这篇文章就把遍历顺序分析的清清楚楚。 -[动态规划:518.零钱兑换II](https://mp.weixin.qq.com/s/PlowDsI4WMBOzf3q80AksQ)中求的是组合数,[动态规划:377. 组合总和 Ⅳ](https://mp.weixin.qq.com/s/Iixw0nahJWQgbqVNk8k6gA)中求的是排列数。 +[动态规划:518.零钱兑换II](https://programmercarl.com/0518.零钱兑换II.html)中求的是组合数,[动态规划:377. 组合总和 Ⅳ](https://programmercarl.com/0377.组合总和Ⅳ.html)中求的是排列数。 **而本题是要求最少硬币数量,硬币是组合数还是排列数都无所谓!所以两个for循环先后顺序怎样都可以!** @@ -330,4 +330,4 @@ const coinChange = (coins, amount) => { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0332.重新安排行程.md b/problems/0332.重新安排行程.md index 3c7e34d8..13ad9e35 100644 --- a/problems/0332.重新安排行程.md +++ b/problems/0332.重新安排行程.md @@ -11,7 +11,7 @@ ## 332.重新安排行程 -题目地址:https://leetcode-cn.com/problems/reconstruct-itinerary/ +[力扣题目链接](https://leetcode-cn.com/problems/reconstruct-itinerary/) 给定一个机票的字符串二维数组 [from, to],子数组中的两个成员分别表示飞机出发和降落的机场地点,对该行程进行重新规划排序。所有这些机票都属于一个从 JFK(肯尼迪国际机场)出发的先生,所以该行程必须从 JFK 开始。 @@ -20,7 +20,7 @@ * 所有的机场都用三个大写字母表示(机场代码)。 * 假定所有机票至少存在一种合理的行程。 * 所有的机票必须都用一次 且 只能用一次。 -  + 示例 1: 输入:[["MUC", "LHR"], ["JFK", "MUC"], ["SFO", "SJC"], ["LHR", "SFO"]] @@ -36,7 +36,7 @@ **如果对回溯算法基础还不了解的话,我还特意录制了一期视频:[带你学透回溯算法(理论篇)](https://www.bilibili.com/video/BV1cy4y167mM/)** 可以结合题解和视频一起看,希望对大家理解回溯算法有所帮助。 -这道题目还是很难的,之前我们用回溯法解决了如下问题:[组合问题](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ),[分割问题](https://mp.weixin.qq.com/s/v--VmA8tp9vs4bXCqHhBuA),[子集问题](https://mp.weixin.qq.com/s/NNRzX-vJ_pjK4qxohd_LtA),[排列问题](https://mp.weixin.qq.com/s/SCOjeMX1t41wcvJq49GhMw)。 +这道题目还是很难的,之前我们用回溯法解决了如下问题:[组合问题](https://programmercarl.com/0077.组合.html),[分割问题](https://programmercarl.com/0093.复原IP地址.html),[子集问题](https://programmercarl.com/0078.子集.html),[排列问题](https://programmercarl.com/0046.全排列.html)。 直觉上来看 这道题和回溯法没有什么关系,更像是图论中的深度优先搜索。 @@ -69,7 +69,7 @@ 一个机场映射多个机场,机场之间要靠字母序排列,一个机场映射多个机场,可以使用std::unordered_map,如果让多个机场之间再有顺序的话,就是用std::map 或者std::multimap 或者 std::multiset。 -如果对map 和 set 的实现机制不太了解,也不清楚为什么 map、multimap就是有序的同学,可以看这篇文章[关于哈希表,你该了解这些!](https://mp.weixin.qq.com/s/g8N6WmoQmsCUw3_BaWxHZA)。 +如果对map 和 set 的实现机制不太了解,也不清楚为什么 map、multimap就是有序的同学,可以看这篇文章[关于哈希表,你该了解这些!](https://programmercarl.com/哈希表理论基础.html)。 这样存放映射关系可以定义为 `unordered_map> targets` 或者 `unordered_map> targets`。 @@ -140,7 +140,7 @@ bool backtracking(int ticketNum, vector& result) { ![332.重新安排行程1](https://img-blog.csdnimg.cn/2020111518065555.png) -所以找到了这个叶子节点了直接返回,这个递归函数的返回值问题我们在讲解二叉树的系列的时候,在这篇[二叉树:递归函数究竟什么时候需要返回值,什么时候不要返回值?](https://mp.weixin.qq.com/s/6TWAVjxQ34kVqROWgcRFOg)详细介绍过。 +所以找到了这个叶子节点了直接返回,这个递归函数的返回值问题我们在讲解二叉树的系列的时候,在这篇[二叉树:递归函数究竟什么时候需要返回值,什么时候不要返回值?](https://programmercarl.com/0112.路径总和.html)详细介绍过。 当然本题的targets和result都需要初始化,代码如下: ``` @@ -164,7 +164,7 @@ if (result.size() == ticketNum + 1) { } ``` -已经看习惯回溯法代码的同学,到叶子节点了习惯性的想要收集结果,但发现并不需要,本题的result相当于 [回溯算法:求组合总和!](https://mp.weixin.qq.com/s/HX7WW6ixbFZJASkRnCTC3w)中的path,也就是本题的result就是记录路径的(就一条),在如下单层搜索的逻辑中result就添加元素了。 +已经看习惯回溯法代码的同学,到叶子节点了习惯性的想要收集结果,但发现并不需要,本题的result相当于 [回溯算法:求组合总和!](https://programmercarl.com/0216.组合总和III.html)中的path,也就是本题的result就是记录路径的(就一条),在如下单层搜索的逻辑中result就添加元素了。 * 单层搜索的逻辑 @@ -178,7 +178,7 @@ if (result.size() == ticketNum + 1) { 遍历过程如下: -```C++ +```CPP for (pair& target : targets[result[result.size() - 1]]) { if (target.second > 0 ) { // 记录到达机场是否飞过了 result.push_back(target.first); @@ -194,7 +194,7 @@ for (pair& target : targets[result[result.size() - 1]]) { 分析完毕,此时完整C++代码如下: -```C++ +```CPP class Solution { private: // unordered_map<出发机场, map<到达机场, 航班次数>> targets @@ -450,4 +450,4 @@ var findItinerary = function(tickets) { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0337.打家劫舍III.md b/problems/0337.打家劫舍III.md index 197841e9..dfb8ba57 100644 --- a/problems/0337.打家劫舍III.md +++ b/problems/0337.打家劫舍III.md @@ -9,7 +9,7 @@ ## 337.打家劫舍 III -题目链接:https://leetcode-cn.com/problems/house-robber-iii/ +[力扣题目链接](https://leetcode-cn.com/problems/house-robber-iii/) 在上次打劫完一条街道之后和一圈房屋后,小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为“根”。 除了“根”之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果两个直接相连的房子在同一天晚上被打劫,房屋将自动报警。 @@ -19,7 +19,7 @@ ## 思路 -这道题目和 [198.打家劫舍](https://mp.weixin.qq.com/s/UZ31WdLEEFmBegdgLkJ8Dw),[213.打家劫舍II](https://mp.weixin.qq.com/s/kKPx4HpH3RArbRcxAVHbeQ)也是如出一辙,只不过这个换成了树。 +这道题目和 [198.打家劫舍](https://programmercarl.com/0198.打家劫舍.html),[213.打家劫舍II](https://programmercarl.com/0213.打家劫舍II.html)也是如出一辙,只不过这个换成了树。 如果对树的遍历不够熟悉的话,那本题就有难度了。 @@ -29,13 +29,13 @@ 与198.打家劫舍,213.打家劫舍II一样,关键是要讨论当前节点抢还是不抢。 -如果抢了当前节点,两个孩子就不是动,如果没抢当前节点,就可以考虑抢左右孩子(**注意这里说的是“考虑”**) +如果抢了当前节点,两个孩子就不能动,如果没抢当前节点,就可以考虑抢左右孩子(**注意这里说的是“考虑”**) ### 暴力递归 代码如下: -```C++ +```CPP class Solution { public: int rob(TreeNode* root) { @@ -65,7 +65,7 @@ public: 代码如下: -```C++ +```CPP class Solution { public: unordered_map umap; // 记录计算过的结果 @@ -91,7 +91,7 @@ public: ### 动态规划 -在上面两种方法,其实对一个节点 投与不投得到的最大金钱都没有做记录,而是需要实时计算。 +在上面两种方法,其实对一个节点 偷与不偷得到的最大金钱都没有做记录,而是需要实时计算。 而动态规划其实就是使用状态转移容器来记录状态的变化,这里可以使用一个长度为2的数组,记录当前节点偷与不偷所得到的的最大金钱。 @@ -103,7 +103,7 @@ public: 参数为当前节点,代码如下: -```C++ +```CPP vector robTree(TreeNode* cur) { ``` @@ -121,7 +121,7 @@ vector robTree(TreeNode* cur) { 2. 确定终止条件 -在遍历的过程中,如果遇到空间点的话,很明显,无论偷还是不偷都是0,所以就返回 +在遍历的过程中,如果遇到空节点的话,很明显,无论偷还是不偷都是0,所以就返回 ``` if (cur == NULL) return vector{0, 0}; ``` @@ -138,7 +138,7 @@ if (cur == NULL) return vector{0, 0}; 代码如下: -```C++ +```CPP // 下标0:不偷,下标1:偷 vector left = robTree(cur->left); // 左 vector right = robTree(cur->right); // 右 @@ -156,7 +156,7 @@ vector right = robTree(cur->right); // 右 代码如下: -```C++ +```CPP vector left = robTree(cur->left); // 左 vector right = robTree(cur->right); // 右 @@ -179,7 +179,7 @@ return {val2, val1}; 递归三部曲与动规五部曲分析完毕,C++代码如下: -```C++ +```CPP class Solution { public: int rob(TreeNode* root) { @@ -210,7 +210,7 @@ public: 只不过平时我们习惯了在一维数组或者二维数组上推导公式,一下子换成了树,就需要对树的遍历方式足够了解! -大家还记不记得我在讲解贪心专题的时候,讲到这道题目:[贪心算法:我要监控二叉树!](https://mp.weixin.qq.com/s/kCxlLLjWKaE6nifHC3UL2Q),这也是贪心算法在树上的应用。**那我也可以把这个算法起一个名字,叫做树形贪心**,哈哈哈 +大家还记不记得我在讲解贪心专题的时候,讲到这道题目:[贪心算法:我要监控二叉树!](https://programmercarl.com/0968.监控二叉树.html),这也是贪心算法在树上的应用。**那我也可以把这个算法起一个名字,叫做树形贪心**,哈哈哈 “树形贪心”词汇从此诞生,来自「代码随想录」 @@ -368,7 +368,32 @@ class Solution: return (val1, val2) ``` -Go: +JavaScript: + +> 动态规划 + +```javascript +const rob = root => { + // 后序遍历函数 + const postOrder = node => { + // 递归出口 + if (!node) return [0, 0]; + // 遍历左子树 + const left = postOrder(node.left); + // 遍历右子树 + const right = postOrder(node.right); + // 不偷当前节点,左右子节点都可以偷或不偷,取最大值 + const DoNot = Math.max(left[0], left[1]) + Math.max(right[0], right[1]); + // 偷当前节点,左右子节点只能不偷 + const Do = node.val + left[0] + right[0]; + // [不偷,偷] + return [DoNot, Do]; + }; + const res = postOrder(root); + // 返回最大值 + return Math.max(...res); +}; +``` @@ -377,4 +402,4 @@ Go: * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0343.整数拆分.md b/problems/0343.整数拆分.md index 7b0dbd0f..c11210fc 100644 --- a/problems/0343.整数拆分.md +++ b/problems/0343.整数拆分.md @@ -8,14 +8,15 @@ ## 343. 整数拆分 -题目链接:https://leetcode-cn.com/problems/integer-break/ +[力扣题目链接](https://leetcode-cn.com/problems/integer-break/) 给定一个正整数 n,将其拆分为至少两个正整数的和,并使这些整数的乘积最大化。 返回你可以获得的最大乘积。 示例 1: 输入: 2 输出: 1 -解释: 2 = 1 + 1, 1 × 1 = 1。 + +\解释: 2 = 1 + 1, 1 × 1 = 1。 示例 2: 输入: 10 @@ -59,6 +60,10 @@ j是从1开始遍历,拆分j的情况,在遍历j的过程中其实都计算 所以递推公式:dp[i] = max({dp[i], (i - j) * j, dp[i - j] * j}); +那么在取最大值的时候,为什么还要比较dp[i]呢? + +因为在递推公式推导的过程中,每次计算dp[i],取最大的而已。 + 3. dp的初始化 @@ -101,7 +106,7 @@ for (int i = 3; i <= n ; i++) { 以上动规五部曲分析完毕,C++代码如下: -```C++ +```CPP class Solution { public: int integerBreak(int n) { @@ -128,7 +133,7 @@ public: 给出我的C++代码如下: -```C++ +```CPP class Solution { public: int integerBreak(int n) { @@ -154,7 +159,7 @@ public: 其实这道题目的递推公式并不好想,而且初始化的地方也很有讲究,我在写本题的时候一开始写的代码是这样的: -```C++ +```CPP class Solution { public: int integerBreak(int n) { @@ -223,7 +228,34 @@ class Solution: return dp[n] ``` Go: - +```golang +func integerBreak(n int) int { + /** + 动态五部曲 + 1.确定dp下标及其含义 + 2.确定递推公式 + 3.确定dp初始化 + 4.确定遍历顺序 + 5.打印dp + **/ + dp:=make([]int,n+1) + dp[1]=1 + dp[2]=1 + for i:=3;ib{ + return a + } + return b +} +``` Javascript: ```Javascript @@ -244,4 +276,4 @@ var integerBreak = function(n) { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0344.反转字符串.md b/problems/0344.反转字符串.md index aeb13a30..a02c27d5 100644 --- a/problems/0344.反转字符串.md +++ b/problems/0344.反转字符串.md @@ -12,7 +12,7 @@ # 344.反转字符串 -https://leetcode-cn.com/problems/reverse-string/ +[力扣题目链接](https://leetcode-cn.com/problems/reverse-string/) 编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 char[] 的形式给出。 @@ -55,7 +55,7 @@ https://leetcode-cn.com/problems/reverse-string/ 接下来再来讲一下如何解决反转字符串的问题。 -大家应该还记得,我们已经讲过了[206.反转链表](https://mp.weixin.qq.com/s/ckEvIVGcNLfrz6OLOMoT0A)。 +大家应该还记得,我们已经讲过了[206.反转链表](https://programmercarl.com/0206.翻转链表.html)。 在反转链表中,使用了双指针的方法。 @@ -63,7 +63,7 @@ https://leetcode-cn.com/problems/reverse-string/ 因为字符串也是一种数组,所以元素在内存中是连续分布,这就决定了反转链表和反转字符串方式上还是有所差异的。 -如果对数组和链表原理不清楚的同学,可以看这两篇,[关于链表,你该了解这些!](https://mp.weixin.qq.com/s/fDGMmLrW7ZHlzkzlf_dZkw),[必须掌握的数组理论知识](https://mp.weixin.qq.com/s/c2KABb-Qgg66HrGf8z-8Og)。 +如果对数组和链表原理不清楚的同学,可以看这两篇,[关于链表,你该了解这些!](https://programmercarl.com/链表理论基础.html),[必须掌握的数组理论知识](https://programmercarl.com/数组理论基础.html)。 对于字符串,我们定义两个指针(也可以说是索引下表),一个从字符串前面,一个从字符串后面,两个指针同时向中间移动,并交换元素。 @@ -74,7 +74,7 @@ https://leetcode-cn.com/problems/reverse-string/ 不难写出如下C++代码: -```C++ +```CPP void reverseString(vector& s) { for (int i = 0, j = s.size() - 1; i < s.size()/2; i++, j--) { swap(s[i],s[j]); @@ -90,7 +90,7 @@ swap可以有两种实现。 一种就是常见的交换数值: -```C++ +```CPP int tmp = s[i]; s[i] = s[j]; s[j] = tmp; @@ -99,7 +99,7 @@ s[j] = tmp; 一种就是通过位运算: -```C++ +```CPP s[i] ^= s[j]; s[j] ^= s[i]; s[i] ^= s[j]; @@ -120,7 +120,7 @@ s[i] ^= s[j]; C++代码如下: -```C++ +```CPP class Solution { public: void reverseString(vector& s) { @@ -162,21 +162,14 @@ class Solution: Do not return anything, modify s in-place instead. """ left, right = 0, len(s) - 1 - while(left < right): + + # 该方法已经不需要判断奇偶数,经测试后时间空间复杂度比用 for i in range(right//2)更低 + # 推荐该写法,更加通俗易懂 + while left < right: s[left], s[right] = s[right], s[left] left += 1 right -= 1 - -# 下面的写法更加简洁,但是都是同样的算法 -# class Solution: -# def reverseString(self, s: List[str]) -> None: -# """ -# Do not return anything, modify s in-place instead. -# """ - # 不需要判别是偶数个还是奇数个序列,因为奇数个的时候,中间那个不需要交换就可 -# for i in range(len(s)//2): -# s[i], s[len(s)-1-i] = s[len(s)-1-i], s[i] -# return s + ``` Go: @@ -210,13 +203,34 @@ var reverseString = function(s) { }; ``` +Swift: +```swift +// 双指针 - 元组 +func reverseString(_ s: inout [Character]) { + var l = 0 + var r = s.count - 1 + while l < r { + // 使用元祖 + (s[l], s[r]) = (s[r], s[l]) + l += 1 + r -= 1 + } +} - +// 双指针法 - 库函数 +func reverseString(_ s: inout [Character]) { + var j = s.count - 1 + for i in 0 ..< Int(Double(s.count) * 0.5) { + s.swapAt(i, j) + j -= 1 + } +} +``` ----------------------- * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0347.前K个高频元素.md b/problems/0347.前K个高频元素.md index 6b07c258..6012e118 100644 --- a/problems/0347.前K个高频元素.md +++ b/problems/0347.前K个高频元素.md @@ -13,7 +13,7 @@ # 347.前 K 个高频元素 -https://leetcode-cn.com/problems/top-k-frequent-elements/ +[力扣题目链接](https://leetcode-cn.com/problems/top-k-frequent-elements/) 给定一个非空的整数数组,返回其中出现频率前 k 高的元素。 @@ -76,7 +76,7 @@ https://leetcode-cn.com/problems/top-k-frequent-elements/ 我们来看一下C++代码: -```C++ +```CPP // 时间复杂度:O(nlogk) // 空间复杂度:O(n) class Solution { @@ -189,6 +189,174 @@ class Solution: Go: +```go +//方法一:小顶堆 +func topKFrequent(nums []int, k int) []int { + map_num:=map[int]int{} + //记录每个元素出现的次数 + for _,item:=range nums{ + map_num[item]++ + } + h:=&IHeap{} + heap.Init(h) + //所有元素入堆,堆的长度为k + for key,value:=range map_num{ + heap.Push(h,[2]int{key,value}) + if h.Len()>k{ + heap.Pop(h) + } + } + res:=make([]int,k) + //按顺序返回堆中的元素 + for i:=0;imap_num[ans[b]] + }) + return ans[:k] +} +``` + + + + +javaScript: +```js +/** + * @param {number[]} nums + * @param {number} k + * @return {number[]} + */ +var topKFrequent = function(nums, k) { + const map = new Map(); + + for(const num of nums) { + map.set(num, (map.get(num) || 0) + 1); + } + + // 创建小顶堆 + const priorityQueue = new PriorityQueue((a, b) => a[1] - b[1]); + + // entry 是一个长度为2的数组,0位置存储key,1位置存储value + for (const entry of map.entries()) { + priorityQueue.push(entry); + if (priorityQueue.size() > k) { + priorityQueue.pop(); + } + } + + const ret = []; + + for(let i = priorityQueue.size() - 1; i >= 0; i--) { + ret[i] = priorityQueue.pop()[0]; + } + + return ret; +}; + + +function PriorityQueue(compareFn) { + this.compareFn = compareFn; + this.queue = []; +} + +// 添加 +PriorityQueue.prototype.push = function(item) { + this.queue.push(item); + let index = this.queue.length - 1; + let parent = Math.floor((index - 1) / 2); + // 上浮 + while(parent >= 0 && this.compare(parent, index) > 0) { + // 交换 + [this.queue[index], this.queue[parent]] = [this.queue[parent], this.queue[index]]; + index = parent; + parent = Math.floor((index - 1) / 2); + } +} + +// 获取堆顶元素并移除 +PriorityQueue.prototype.pop = function() { + const ret = this.queue[0]; + + // 把最后一个节点移到堆顶 + this.queue[0] = this.queue.pop(); + + let index = 0; + // 左子节点下标,left + 1 就是右子节点下标 + let left = 1; + let selectedChild = this.compare(left, left + 1) > 0 ? left + 1 : left; + + // 下沉 + while(selectedChild !== undefined && this.compare(index, selectedChild) > 0) { + // 交换 + [this.queue[index], this.queue[selectedChild]] = [this.queue[selectedChild], this.queue[index]]; + index = selectedChild; + left = 2 * index + 1; + selectedChild = this.compare(left, left + 1) > 0 ? left + 1 : left; + } + + return ret; +} + +PriorityQueue.prototype.size = function() { + return this.queue.length; +} + +// 使用传入的 compareFn 比较两个位置的元素 +PriorityQueue.prototype.compare = function(index1, index2) { + if (this.queue[index1] === undefined) { + return 1; + } + if (this.queue[index2] === undefined) { + return -1; + } + + return this.compareFn(this.queue[index1], this.queue[index2]); +} +``` @@ -196,4 +364,4 @@ Go: * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0349.两个数组的交集.md b/problems/0349.两个数组的交集.md index 5c635d39..28867092 100644 --- a/problems/0349.两个数组的交集.md +++ b/problems/0349.两个数组的交集.md @@ -14,7 +14,7 @@ ## 349. 两个数组的交集 -https://leetcode-cn.com/problems/intersection-of-two-arrays/ +[力扣题目链接](https://leetcode-cn.com/problems/intersection-of-two-arrays/) 题意:给定两个数组,编写一个函数来计算它们的交集。 @@ -32,7 +32,7 @@ https://leetcode-cn.com/problems/intersection-of-two-arrays/ 这道题用暴力的解法时间复杂度是O(n^2),那来看看使用哈希法进一步优化。 -那么用数组来做哈希表也是不错的选择,例如[242. 有效的字母异位词](https://mp.weixin.qq.com/s/ffS8jaVFNUWyfn_8T31IdA) +那么用数组来做哈希表也是不错的选择,例如[242. 有效的字母异位词](https://programmercarl.com/0242.有效的字母异位词.html) 但是要注意,**使用数组来做哈希的题目,是因为题目都限制了数值的大小。** @@ -54,7 +54,7 @@ std::set和std::multiset底层实现都是红黑树,std::unordered_set的底 C++代码如下: -```C++ +```CPP class Solution { public: vector intersection(vector& nums1, vector& nums2) { @@ -121,13 +121,7 @@ Python: ```python class Solution: def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]: - result_set = set() - - set1 = set(nums1) - for num in nums2: - if num in set1: - result_set.add(num) # set1里出现的nums2元素 存放到结果 - return result_set + return list(set(nums1) & set(nums2)) # 两个数组先变成集合,求交集后还原为数组 ``` @@ -149,6 +143,26 @@ func intersection(nums1 []int, nums2 []int) []int { return res } ``` +```golang +//优化版,利用set,减少count统计 +func intersection(nums1 []int, nums2 []int) []int { + set:=make(map[int]struct{},0) + res:=make([]int,0) + for _,v:=range nums1{ + if _,ok:=set[v];!ok{ + set[v]=struct{}{} + } + } + for _,v:=range nums2{ + //如果存在于上一个数组中,则加入结果集,并清空该set值 + if _,ok:=set[v];ok{ + res=append(res,v) + delete(set, v) + } + } + return res +} +``` javaScript: @@ -178,7 +192,71 @@ var intersection = function(nums1, nums2) { }; ``` +Swift: +```swift +func intersection(_ nums1: [Int], _ nums2: [Int]) -> [Int] { + var set1 = Set() + var set2 = Set() + for num in nums1 { + set1.insert(num) + } + for num in nums2 { + if set1.contains(num) { + set2.insert(num) + } + } + return Array(set2) +} +``` +PHP: +```php +class Solution { + /** + * @param Integer[] $nums1 + * @param Integer[] $nums2 + * @return Integer[] + */ + function intersection($nums1, $nums2) { + if (count($nums1) == 0 || count($nums2) == 0) { + return []; + } + $counts = []; + $res = []; + foreach ($nums1 as $num) { + $counts[$num] = 1; + } + foreach ($nums2 as $num) { + if (isset($counts[$num])) { + $res[] = $num; + } + unset($counts[$num]); + } + + return $res; + } +} +``` + +Rust: +```rust +use std::collections::HashSet; +impl Solution { + pub fn intersection(nums1: Vec, nums2: Vec) -> Vec { + let mut resultSet: HashSet = HashSet::with_capacity(1000); + let nums1Set: HashSet = nums1.into_iter().collect(); + + for num in nums2.iter() { + if nums1Set.contains(num) { + resultSet.insert(*num); + } + } + + let ret: Vec = resultSet.into_iter().collect(); + ret + } +} +``` ## 相关题目 * 350.两个数组的交集 II @@ -188,4 +266,4 @@ var intersection = function(nums1, nums2) { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0376.摆动序列.md b/problems/0376.摆动序列.md index f64e0043..75965c37 100644 --- a/problems/0376.摆动序列.md +++ b/problems/0376.摆动序列.md @@ -7,11 +7,11 @@

欢迎大家参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!

-> 本周讲解了[贪心理论基础](https://mp.weixin.qq.com/s/O935TaoHE9Eexwe_vSbRAg),以及第一道贪心的题目:[贪心算法:分发饼干](https://mp.weixin.qq.com/s/YSuLIAYyRGlyxbp9BNC1uw),可能会给大家一种贪心算法比较简单的错觉,好了,接下来几天的题目难度要上来了,哈哈。 +> 本周讲解了[贪心理论基础](https://programmercarl.com/贪心算法理论基础.html),以及第一道贪心的题目:[贪心算法:分发饼干](https://programmercarl.com/0455.分发饼干.html),可能会给大家一种贪心算法比较简单的错觉,好了,接下来几天的题目难度要上来了,哈哈。 ## 376. 摆动序列 -题目链接:https://leetcode-cn.com/problems/wiggle-subsequence/ +[力扣题目链接](https://leetcode-cn.com/problems/wiggle-subsequence/) 如果连续数字之间的差严格地在正数和负数之间交替,则数字序列称为摆动序列。第一个差(如果存在的话)可能是正数或负数。少于两个元素的序列也是摆动序列。 @@ -70,7 +70,7 @@ C++代码如下(和上图是对应的逻辑): -```C++ +```CPP class Solution { public: int wiggleMaxLength(vector& nums) { @@ -192,4 +192,4 @@ var wiggleMaxLength = function(nums) { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0377.组合总和Ⅳ.md b/problems/0377.组合总和Ⅳ.md index 2ee009b3..01f48d45 100644 --- a/problems/0377.组合总和Ⅳ.md +++ b/problems/0377.组合总和Ⅳ.md @@ -5,11 +5,12 @@

欢迎大家参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!

+ # 动态规划:Carl称它为排列总和! ## 377. 组合总和 Ⅳ -题目链接:https://leetcode-cn.com/problems/combination-sum-iv/ +[力扣题目链接](https://leetcode-cn.com/problems/combination-sum-iv/) 难度:中等 @@ -35,6 +36,8 @@ target = 4 ## 思路 +对完全背包还不了解的同学,可以看这篇:[动态规划:关于完全背包,你该了解这些!](https://programmercarl.com/背包问题理论基础完全背包.html) + 本题题目描述说是求组合,但又说是可以元素相同顺序不同的组合算两个组合,**其实就是求排列!** 弄清什么是组合,什么是排列很重要。 @@ -43,7 +46,7 @@ target = 4 排列强调顺序,(1,5)和(5,1)是两个不同的排列。 -大家在公众号里学习回溯算法专题的时候,一定做过这两道题目[回溯算法:39.组合总和](https://mp.weixin.qq.com/s/FLg8G6EjVcxBjwCbzpACPw)和[回溯算法:40.组合总和II](https://mp.weixin.qq.com/s/_1zPYk70NvHsdY8UWVGXmQ)会感觉这两题和本题很像! +大家在公众号里学习回溯算法专题的时候,一定做过这两道题目[回溯算法:39.组合总和](https://programmercarl.com/0039.组合总和.html)和[回溯算法:40.组合总和II](https://programmercarl.com/0040.组合总和II.html)会感觉这两题和本题很像! 但其本质是本题求的是排列总和,而且仅仅是求排列总和的个数,并不是把所有的排列都列出来。 @@ -61,7 +64,7 @@ dp[i](考虑nums[j])可以由 dp[i - nums[j]](不考虑nums[j]) 推导 因为只要得到nums[j],排列个数dp[i - nums[j]],就是dp[i]的一部分。 -在[动态规划:494.目标和](https://mp.weixin.qq.com/s/2pWmaohX75gwxvBENS-NCw) 和 [动态规划:518.零钱兑换II](https://mp.weixin.qq.com/s/PlowDsI4WMBOzf3q80AksQ)中我们已经讲过了,求装满背包有几种方法,递推公式一般都是dp[i] += dp[i - nums[j]]; +在[动态规划:494.目标和](https://programmercarl.com/0494.目标和.html) 和 [动态规划:518.零钱兑换II](https://programmercarl.com/0518.零钱兑换II.html)中我们已经讲过了,求装满背包有几种方法,递推公式一般都是dp[i] += dp[i - nums[j]]; 本题也一样。 @@ -87,7 +90,7 @@ dp[i](考虑nums[j])可以由 dp[i - nums[j]](不考虑nums[j]) 推导 本题要求的是排列,那么这个for循环嵌套的顺序可以有说法了。 -在[动态规划:518.零钱兑换II](https://mp.weixin.qq.com/s/PlowDsI4WMBOzf3q80AksQ) 中就已经讲过了。 +在[动态规划:518.零钱兑换II](https://programmercarl.com/0518.零钱兑换II.html) 中就已经讲过了。 **如果求组合数就是外层for循环遍历物品,内层for遍历背包**。 @@ -107,7 +110,7 @@ dp[i](考虑nums[j])可以由 dp[i - nums[j]](不考虑nums[j]) 推导 以上分析完毕,C++代码如下: -```C++ +```CPP class Solution { public: int combinationSum4(vector& nums, int target) { @@ -134,7 +137,7 @@ C++测试用例有超过两个树相加超过int的数据,所以需要在if里 **求装满背包有几种方法,递归公式都是一样的,没有什么差别,但关键在于遍历顺序!** -本题与[动态规划:518.零钱兑换II](https://mp.weixin.qq.com/s/PlowDsI4WMBOzf3q80AksQ)就是一个鲜明的对比,一个是求排列,一个是求组合,遍历顺序完全不同。 +本题与[动态规划:518.零钱兑换II](https://programmercarl.com/0518.零钱兑换II.html)就是一个鲜明的对比,一个是求排列,一个是求组合,遍历顺序完全不同。 如果对遍历顺序没有深度理解的话,做这种完全背包的题目会很懵逼,即使题目刷过了可能也不太清楚具体是怎么过的。 @@ -220,10 +223,27 @@ const combinationSum4 = (nums, target) => { }; ``` +Rust +```Rust +impl Solution { + pub fn combination_sum4(nums: Vec, target: i32) -> i32 { + let mut dp = vec![0; target as usize + 1]; + dp[0] = 1; + for i in 1..=target as usize { + for &j in nums.iter() { + if i as i32 >= j { + dp[i] += dp[i- j as usize]; + } + } + } + return dp[target as usize]; + } +} +``` ----------------------- * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0383.赎金信.md b/problems/0383.赎金信.md index a5315b0e..75b31698 100644 --- a/problems/0383.赎金信.md +++ b/problems/0383.赎金信.md @@ -12,7 +12,7 @@ # 383. 赎金信 -https://leetcode-cn.com/problems/ransom-note/ +[力扣题目链接](https://leetcode-cn.com/problems/ransom-note/) 给定一个赎金信 (ransom) 字符串和一个杂志(magazine)字符串,判断第一个字符串 ransom 能不能由第二个字符串 magazines 里面的字符构成。如果可以构成,返回 true ;否则返回 false。 @@ -28,7 +28,7 @@ canConstruct("aa", "aab") -> true ## 思路 -这道题目和[242.有效的字母异位词](https://mp.weixin.qq.com/s/ffS8jaVFNUWyfn_8T31IdA)很像,[242.有效的字母异位词](https://mp.weixin.qq.com/s/ffS8jaVFNUWyfn_8T31IdA)相当于求 字符串a 和 字符串b 是否可以相互组成 ,而这道题目是求 字符串a能否组成字符串b,而不用管字符串b 能不能组成字符串a。 +这道题目和[242.有效的字母异位词](https://programmercarl.com/0242.有效的字母异位词.html)很像,[242.有效的字母异位词](https://programmercarl.com/0242.有效的字母异位词.html)相当于求 字符串a 和 字符串b 是否可以相互组成 ,而这道题目是求 字符串a能否组成字符串b,而不用管字符串b 能不能组成字符串a。 本题判断第一个字符串ransom能不能由第二个字符串magazines里面的字符构成,但是这里需要注意两点。 @@ -40,7 +40,7 @@ canConstruct("aa", "aab") -> true 那么第一个思路其实就是暴力枚举了,两层for循环,不断去寻找,代码如下: -```C++ +```CPP // 时间复杂度: O(n^2) // 空间复杂度:O(1) class Solution { @@ -79,7 +79,7 @@ public: 代码如下: -```C++ +```CPP // 时间复杂度: O(n) // 空间复杂度:O(1) class Solution { @@ -209,6 +209,22 @@ class Solution(object): return True ``` +Python写法四: + +```python3 +class Solution: + def canConstruct(self, ransomNote: str, magazine: str) -> bool: + c1 = collections.Counter(ransomNote) + c2 = collections.Counter(magazine) + x = c1 - c2 + #x只保留值大于0的符号,当c1里面的符号个数小于c2时,不会被保留 + #所以x只保留下了,magazine不能表达的 + if(len(x)==0): + return True + else: + return False +``` + Go: ```go @@ -251,9 +267,79 @@ var canConstruct = function(ransomNote, magazine) { ``` +PHP: +```php +class Solution { + /** + * @param String $ransomNote + * @param String $magazine + * @return Boolean + */ + function canConstruct($ransomNote, $magazine) { + if (count($ransomNote) > count($magazine)) { + return false; + } + $map = []; + for ($i = 0; $i < strlen($magazine); $i++) { + $map[$magazine[$i]] = ($map[$magazine[$i]] ?? 0) + 1; + } + for ($i = 0; $i < strlen($ransomNote); $i++) { + if (!isset($map[$ransomNote[$i]]) || --$map[$ransomNote[$i]] < 0) { + return false; + } + } + return true; + } +``` + +Swift: +```swift +func canConstruct(_ ransomNote: String, _ magazine: String) -> Bool { + var record = Array(repeating: 0, count: 26); + let aUnicodeScalarValue = "a".unicodeScalars.first!.value + for unicodeScalar in magazine.unicodeScalars { + // 通过record 记录 magazine 里各个字符出现的次数 + let idx: Int = Int(unicodeScalar.value - aUnicodeScalarValue) + record[idx] += 1 + } + for unicodeScalar in ransomNote.unicodeScalars { + // 遍历 ransomNote,在record里对应的字符个数做 -- 操作 + let idx: Int = Int(unicodeScalar.value - aUnicodeScalarValue) + record[idx] -= 1 + // 如果小于零说明在magazine没有 + if record[idx] < 0 { + return false + } + } + return true +} +``` + +Rust: +```rust +impl Solution { + pub fn can_construct(ransom_note: String, magazine: String) -> bool { + let baseChar = 'a'; + let mut record = vec![0; 26]; + + for byte in magazine.bytes() { + record[byte as usize - baseChar as usize] += 1; + } + + for byte in ransom_note.bytes() { + record[byte as usize - baseChar as usize] -= 1; + if record[byte as usize - baseChar as usize] < 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) -
+
diff --git a/problems/0392.判断子序列.md b/problems/0392.判断子序列.md index d97d2684..784e3bbc 100644 --- a/problems/0392.判断子序列.md +++ b/problems/0392.判断子序列.md @@ -9,7 +9,7 @@ ## 392.判断子序列 -题目链接:https://leetcode-cn.com/problems/is-subsequence/ +[力扣题目链接](https://leetcode-cn.com/problems/is-subsequence/) 给定字符串 s 和 t ,判断 s 是否为 t 的子序列。 @@ -107,7 +107,7 @@ dp[i][j]表示以下标i-1为结尾的字符串s和以下标j-1为结尾的字 动规五部曲分析完毕,C++代码如下: -```C++ +```CPP class Solution { public: bool isSubsequence(string s, string t) { @@ -180,7 +180,49 @@ class Solution: return False ``` +JavaScript: + +```javascript +const isSubsequence = (s, t) => { + // s、t的长度 + const [m, n] = [s.length, t.length]; + // dp全初始化为0 + const dp = new Array(m + 1).fill(0).map(x => new Array(n + 1).fill(0)); + for (let i = 1; i <= m; i++) { + for (let j = 1; j <= n; j++) { + // 更新dp[i][j],两种情况 + if (s[i - 1] === t[j - 1]) { + dp[i][j] = dp[i - 1][j - 1] + 1; + } else { + dp[i][j] = dp[i][j - 1]; + } + } + } + // 遍历结束,判断dp右下角的数是否等于s的长度 + return dp[m][n] === m ? true : false; +}; +``` + Go: +```go +func isSubsequence(s string, t string) bool { + dp := make([][]int,len(s)+1) + for i:=0;i +
diff --git a/problems/0404.左叶子之和.md b/problems/0404.左叶子之和.md index aa758367..c0eb7c8e 100644 --- a/problems/0404.左叶子之和.md +++ b/problems/0404.左叶子之和.md @@ -7,9 +7,9 @@

欢迎大家参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!

-## 404.左叶子之和 +# 404.左叶子之和 -题目地址:https://leetcode-cn.com/problems/sum-of-left-leaves/ +[力扣题目链接](https://leetcode-cn.com/problems/sum-of-left-leaves/) 计算给定二叉树的所有左叶子之和。 @@ -17,7 +17,7 @@ ![404.左叶子之和1](https://img-blog.csdnimg.cn/20210204151927654.png) -## 思路 +# 思路 **首先要注意是判断左叶子,不是二叉树左侧节点,所以不要上来想着层序遍历。** @@ -65,7 +65,7 @@ if (root == NULL) return 0; 代码如下: -```C++ +```CPP int leftValue = sumOfLeftLeaves(root->left); // 左 int rightValue = sumOfLeftLeaves(root->right); // 右 // 中 @@ -81,7 +81,7 @@ return sum; 整体递归代码如下: -```C++ +```CPP class Solution { public: int sumOfLeftLeaves(TreeNode* root) { @@ -102,7 +102,7 @@ public: 以上代码精简之后如下: -```C++ +```CPP class Solution { public: int sumOfLeftLeaves(TreeNode* root) { @@ -119,11 +119,11 @@ public: ## 迭代法 -本题迭代法使用前中后序都是可以的,只要把左叶子节点统计出来,就可以了,那么参考文章 [二叉树:听说递归能做的,栈也能做!](https://mp.weixin.qq.com/s/c_zCrGHIVlBjUH_hJtghCg)和[二叉树:前中后序迭代方式的写法就不能统一一下么?](https://mp.weixin.qq.com/s/WKg0Ty1_3SZkztpHubZPRg)中的写法,可以写出一个前序遍历的迭代法。 +本题迭代法使用前中后序都是可以的,只要把左叶子节点统计出来,就可以了,那么参考文章 [二叉树:听说递归能做的,栈也能做!](https://programmercarl.com/二叉树的迭代遍历.html)和[二叉树:迭代法统一写法](https://programmercarl.com/二叉树的统一迭代法.html)中的写法,可以写出一个前序遍历的迭代法。 判断条件都是一样的,代码如下: -```C++ +```CPP class Solution { public: @@ -146,7 +146,7 @@ public: }; ``` -## 总结 +# 总结 这道题目要求左叶子之和,其实是比较绕的,因为不能判断本节点是不是左叶子节点。 @@ -157,9 +157,9 @@ public: 希望通过这道题目,可以扩展大家对二叉树的解题思路。 -## 其他语言版本 +# 其他语言版本 -Java: +## Java **递归** @@ -201,42 +201,84 @@ class Solution { } } ``` +```java +// 层序遍历迭代法 +class Solution { + public int sumOfLeftLeaves(TreeNode root) { + int sum = 0; + if (root == null) return 0; + Queue queue = new LinkedList<>(); + queue.offer(root); + while (!queue.isEmpty()) { + int size = queue.size(); + while (size -- > 0) { + TreeNode node = queue.poll(); + if (node.left != null) { // 左节点不为空 + queue.offer(node.left); + if (node.left.left == null && node.left.right == null){ // 左叶子节点 + sum += node.left.val; + } + } + if (node.right != null) queue.offer(node.right); + } + } + return sum; + } +} +``` +## Python -Python: -```Python **递归** -# Definition for a binary tree node. -# class TreeNode: -# def __init__(self, val=0, left=None, right=None): -# self.val = val -# self.left = left -# self.right = right +```python class Solution: def sumOfLeftLeaves(self, root: TreeNode) -> int: - self.res=0 - def areleftleaves(root): - if not root:return - if root.left and (not root.left.left) and (not root.left.right):self.res+=root.left.val - areleftleaves(root.left) - areleftleaves(root.right) - areleftleaves(root) - return self.res + if not root: + return 0 + + left_left_leaves_sum = self.sumOfLeftLeaves(root.left) # 左 + right_left_leaves_sum = self.sumOfLeftLeaves(root.right) # 右 + + cur_left_leaf_val = 0 + if root.left and not root.left.left and not root.left.right: + cur_left_leaf_val = root.left.val # 中 + + return cur_left_leaf_val + left_left_leaves_sum + right_left_leaves_sum ``` -Go: -> 递归法 +**迭代** +```python +class Solution: + def sumOfLeftLeaves(self, root: TreeNode) -> int: + """ + Idea: Each time check current node's left node. + If current node don't have one, skip it. + """ + stack = [] + if root: + stack.append(root) + res = 0 + + while stack: + # 每次都把当前节点的左节点加进去. + cur_node = stack.pop() + if cur_node.left and not cur_node.left.left and not cur_node.left.right: + res += cur_node.left.val + + if cur_node.left: + stack.append(cur_node.left) + if cur_node.right: + stack.append(cur_node.right) + + return res +``` + +## Go + +**递归法** ```go -/** - * Definition for a binary tree node. - * type TreeNode struct { - * Val int - * Left *TreeNode - * Right *TreeNode - * } - */ func sumOfLeftLeaves(root *TreeNode) int { var res int findLeft(root,&res) @@ -256,17 +298,9 @@ func findLeft(root *TreeNode,res *int){ } ``` -> 迭代法 +**迭代法** ```go -/** - * Definition for a binary tree node. - * type TreeNode struct { - * Val int - * Left *TreeNode - * Right *TreeNode - * } - */ func sumOfLeftLeaves(root *TreeNode) int { var res int queue:=list.New() @@ -291,8 +325,10 @@ func sumOfLeftLeaves(root *TreeNode) int { ``` -JavaScript: -递归版本 +## JavaScript + +**递归法** + ```javascript var sumOfLeftLeaves = function(root) { //采用后序遍历 递归遍历 @@ -315,7 +351,8 @@ var sumOfLeftLeaves = function(root) { return nodesSum(root); }; ``` -迭代版本 + +**迭代法** ```javascript var sumOfLeftLeaves = function(root) { //采用层序遍历 @@ -345,4 +382,4 @@ var sumOfLeftLeaves = function(root) { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0406.根据身高重建队列.md b/problems/0406.根据身高重建队列.md index a5f66a5d..bd27b1b2 100644 --- a/problems/0406.根据身高重建队列.md +++ b/problems/0406.根据身高重建队列.md @@ -9,7 +9,7 @@ ## 406.根据身高重建队列 -题目链接:https://leetcode-cn.com/problems/queue-reconstruction-by-height/ +[力扣题目链接](https://leetcode-cn.com/problems/queue-reconstruction-by-height/) 假设有打乱顺序的一群人站成一个队列,数组 people 表示队列中一些人的属性(不一定按顺序)。每个 people[i] = [hi, ki] 表示第 i 个人的身高为 hi ,前面 正好 有 ki 个身高大于或等于 hi 的人。 @@ -43,9 +43,9 @@ 本题有两个维度,h和k,看到这种题目一定要想如何确定一个维度,然后在按照另一个维度重新排列。 -其实如果大家认真做了[135. 分发糖果](https://mp.weixin.qq.com/s/8MwlgFfvaNYmjGwjuMlETQ),就会发现和此题有点点的像。 +其实如果大家认真做了[135. 分发糖果](https://programmercarl.com/0135.分发糖果.html),就会发现和此题有点点的像。 -在[135. 分发糖果](https://mp.weixin.qq.com/s/8MwlgFfvaNYmjGwjuMlETQ)我就强调过一次,遇到两个维度权衡的时候,一定要先确定一个维度,再确定另一个维度。 +在[135. 分发糖果](https://programmercarl.com/0135.分发糖果.html)我就强调过一次,遇到两个维度权衡的时候,一定要先确定一个维度,再确定另一个维度。 **如果两个维度一起考虑一定会顾此失彼**。 @@ -76,11 +76,11 @@ 一些同学可能也会疑惑,你怎么知道局部最优就可以推出全局最优呢? 有数学证明么? -在贪心系列开篇词[关于贪心算法,你该了解这些!](https://mp.weixin.qq.com/s/O935TaoHE9Eexwe_vSbRAg)中,我已经讲过了这个问题了。 +在贪心系列开篇词[关于贪心算法,你该了解这些!](https://programmercarl.com/贪心算法理论基础.html)中,我已经讲过了这个问题了。 刷题或者面试的时候,手动模拟一下感觉可以局部最优推出整体最优,而且想不到反例,那么就试一试贪心,至于严格的数学证明,就不在讨论范围内了。 -如果没有读过[关于贪心算法,你该了解这些!](https://mp.weixin.qq.com/s/O935TaoHE9Eexwe_vSbRAg)的同学建议读一下,相信对贪心就有初步的了解了。 +如果没有读过[关于贪心算法,你该了解这些!](https://programmercarl.com/贪心算法理论基础.html)的同学建议读一下,相信对贪心就有初步的了解了。 回归本题,整个插入过程如下: @@ -99,7 +99,7 @@ C++代码如下: -```C++ +```CPP // 版本一 class Solution { public: @@ -127,7 +127,7 @@ public: 改成链表之后,C++代码如下: -```C++ +```CPP // 版本二 class Solution { public: @@ -157,15 +157,15 @@ public: 大家可以把两个版本的代码提交一下试试,就可以发现其差别了! -关于本题使用数组还是使用链表的性能差异,我在[贪心算法:根据身高重建队列(续集)](https://mp.weixin.qq.com/s/K-pRN0lzR-iZhoi-1FgbSQ)中详细讲解了一波 +关于本题使用数组还是使用链表的性能差异,我在[贪心算法:根据身高重建队列(续集)](https://programmercarl.com/根据身高重建队列(vector原理讲解).html)中详细讲解了一波 ## 总结 -关于出现两个维度一起考虑的情况,我们已经做过两道题目了,另一道就是[135. 分发糖果](https://mp.weixin.qq.com/s/8MwlgFfvaNYmjGwjuMlETQ)。 +关于出现两个维度一起考虑的情况,我们已经做过两道题目了,另一道就是[135. 分发糖果](https://programmercarl.com/0135.分发糖果.html)。 **其技巧都是确定一边然后贪心另一边,两边一起考虑,就会顾此失彼**。 -这道题目可以说比[135. 分发糖果](https://mp.weixin.qq.com/s/8MwlgFfvaNYmjGwjuMlETQ)难不少,其贪心的策略也是比较巧妙。 +这道题目可以说比[135. 分发糖果](https://programmercarl.com/0135.分发糖果.html)难不少,其贪心的策略也是比较巧妙。 最后我给出了两个版本的代码,可以明显看是使用C++中的list(底层链表实现)比vector(数组)效率高得多。 @@ -209,15 +209,38 @@ Python: ```python class Solution: def reconstructQueue(self, people: List[List[int]]) -> List[List[int]]: + # 先按照h维度的身高顺序从高到低排序。确定第一个维度 + # lambda返回的是一个元组:当-x[0](维度h)相同时,再根据x[1](维度k)从小到大排序 people.sort(key=lambda x: (-x[0], x[1])) que = [] + + # 根据每个元素的第二个维度k,贪心算法,进行插入 + # people已经排序过了:同一高度时k值小的排前面。 for p in people: que.insert(p[1], p) return que ``` Go: - +```golang +func reconstructQueue(people [][]int) [][]int { + //先将身高从大到小排序,确定最大个子的相对位置 + sort.Slice(people,func(i,j int)bool{ + if people[i][0]==people[j][0]{ + return people[i][1]people[j][0]//这个只是确保身高按照由大到小的顺序来排,并不确定K是按照从小到大排序的 + }) + //再按照K进行插入排序,优先插入K小的 + result := make([][]int, 0) + for _, info := range people { + result = append(result, info) + copy(result[info[1] +1:], result[info[1]:])//将插入位置之后的元素后移动一位(意思是腾出空间) + result[info[1]] = info//将插入元素位置插入元素 + } + return result +} +``` Javascript: ```Javascript var reconstructQueue = function(people) { @@ -243,4 +266,4 @@ var reconstructQueue = function(people) { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0416.分割等和子集.md b/problems/0416.分割等和子集.md index 75c665cd..415ff88b 100644 --- a/problems/0416.分割等和子集.md +++ b/problems/0416.分割等和子集.md @@ -8,7 +8,7 @@ ## 416. 分割等和子集 -题目链接:https://leetcode-cn.com/problems/partition-equal-subset-sum/ +[力扣题目链接](https://leetcode-cn.com/problems/partition-equal-subset-sum/) 题目难易:中等 @@ -22,7 +22,7 @@ 输入: [1, 5, 11, 5] 输出: true 解释: 数组可以分割成 [1, 5, 5] 和 [11]. -  + 示例 2: 输入: [1, 2, 3, 5] 输出: false @@ -47,8 +47,8 @@ 如果对01背包不够了解,建议仔细看完如下两篇: -* [动态规划:关于01背包问题,你该了解这些!](https://mp.weixin.qq.com/s/FwIiPPmR18_AJO5eiidT6w) -* [动态规划:关于01背包问题,你该了解这些!(滚动数组)](https://mp.weixin.qq.com/s/M4uHxNVKRKm5HPjkNZBnFA) +* [动态规划:关于01背包问题,你该了解这些!](https://programmercarl.com/背包理论基础01背包-1.html) +* [动态规划:关于01背包问题,你该了解这些!(滚动数组)](https://programmercarl.com/背包理论基础01背包-2.html) ## 01背包问题 @@ -106,7 +106,7 @@ 代码如下: -```C++ +```CPP // 题目中说:每个数组中的元素不会超过 100,数组的大小不会超过 200 // 总和不会大于20000,背包最大只需要其中一半,所以10001大小就可以了 vector dp(10001, 0); @@ -114,11 +114,11 @@ vector dp(10001, 0); 4. 确定遍历顺序 -在[动态规划:关于01背包问题,你该了解这些!(滚动数组)](https://mp.weixin.qq.com/s/M4uHxNVKRKm5HPjkNZBnFA)中就已经说明:如果使用一维dp数组,物品遍历的for循环放在外层,遍历背包的for循环放在内层,且内层for循环倒叙遍历! +在[动态规划:关于01背包问题,你该了解这些!(滚动数组)](https://programmercarl.com/背包理论基础01背包-2.html)中就已经说明:如果使用一维dp数组,物品遍历的for循环放在外层,遍历背包的for循环放在内层,且内层for循环倒叙遍历! 代码如下: -```C++ +```CPP // 开始 01背包 for(int i = 0; i < nums.size(); i++) { for(int j = target; j >= nums[i]; j--) { // 每一个元素一定是不可重复放入,所以从大到小遍历 @@ -141,7 +141,7 @@ dp[i]的数值一定是小于等于i的。 综上分析完毕,C++代码如下: -```C++ +```CPP class Solution { public: bool canPartition(vector& nums) { @@ -191,35 +191,23 @@ Java: ```Java class Solution { public boolean canPartition(int[] nums) { + if(nums == null || nums.length == 0) return false; + int n = nums.length; int sum = 0; - for (int i : nums) { - sum += i; + for(int num : nums){ + sum += num; } - if ((sum & 1) == 1) { - return false; - } - int length = nums.length; - int target = sum >> 1; - //dp[j]表示前i个元素可以找到相加等于j情况 - boolean[] dp = new boolean[target + 1]; - //对于第一个元素,只有当j=nums[0]时,才恰好填充满 - if (nums[0] <= target) { - dp[nums[0]] = true; - } - - for (int i = 1; i < length; i++) { - //j由右往左直到nums[i] - for (int j = target; j >= nums[i]; j--) { - //只有两种情况,要么放,要么不放 - //取其中的TRUE值 - dp[j] = dp[j] || dp[j - nums[i]]; - } - //一旦满足,结束,因为只需要找到一组值即可 - if (dp[target]) { - return dp[target]; + //总和为奇数,不能平分 + if(sum % 2 != 0) return false; + int target = sum / 2; + int[] dp = new int[target + 1]; + for(int i = 0; i < n; i++){ + for(int j = target; j >= nums[i]; j--){ + //物品 i 的重量是 nums[i],其价值也是 nums[i] + dp[j] = Math.max(dp[j], dp[j-nums[i]] + nums[i]); } } - return dp[target]; + return dp[target] == target; } } ``` @@ -239,6 +227,45 @@ class Solution: ``` Go: +``` +func canPartition(nums []int) bool { + /** + 动态五部曲: + 1.确定dp数组和下标含义 + 2.确定递推公式 + 3.dp数组初始化 + 4.dp遍历顺序 + 5.打印 + **/ + //确定和 + var sum int + for _,v:=range nums{ + sum+=v + } + if sum%2!=0{ //如果和为奇数,则不可能分成两个相等的数组 + return false + } + sum/=2 + //确定dp数组和下标含义 + var dp [][]bool //dp[i][j] 表示: 前i个石头是否总和不大于J + //初始化数组 + dp=make([][]bool,len(nums)+1) + for i,_:=range dp{ + dp[i]=make([]bool,sum+1) + dp[i][0]=true + } + for i:=1;i<=len(nums);i++{ + for j:=1;j<=sum;j++{//j是固定总量 + if j>=nums[i-1]{//如果容量够用则可放入背包 + dp[i][j]=dp[i-1][j]||dp[i-1][j-nums[i-1]] + }else{//如果容量不够用则不拿,维持前一个状态 + dp[i][j]=dp[i-1][j] + } + } + } + return dp[len(nums)][sum] +} +``` javaScript: @@ -266,4 +293,4 @@ var canPartition = function(nums) { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0435.无重叠区间.md b/problems/0435.无重叠区间.md index d32c2ebb..4e850114 100644 --- a/problems/0435.无重叠区间.md +++ b/problems/0435.无重叠区间.md @@ -9,7 +9,7 @@ ## 435. 无重叠区间 -题目链接:https://leetcode-cn.com/problems/non-overlapping-intervals/ +[力扣题目链接](https://leetcode-cn.com/problems/non-overlapping-intervals/) 给定一个区间的集合,找到需要移除区间的最小数量,使剩余区间互不重叠。 @@ -122,11 +122,11 @@ public: ## 补充 -本题其实和[452.用最少数量的箭引爆气球](https://mp.weixin.qq.com/s/HxVAJ6INMfNKiGwI88-RFw)非常像,弓箭的数量就相当于是非交叉区间的数量,只要把弓箭那道题目代码里射爆气球的判断条件加个等号(认为[0,1][1,2]不是相邻区间),然后用总区间数减去弓箭数量 就是要移除的区间数量了。 +本题其实和[452.用最少数量的箭引爆气球](https://programmercarl.com/0452.用最少数量的箭引爆气球.html)非常像,弓箭的数量就相当于是非交叉区间的数量,只要把弓箭那道题目代码里射爆气球的判断条件加个等号(认为[0,1][1,2]不是相邻区间),然后用总区间数减去弓箭数量 就是要移除的区间数量了。 -把[452.用最少数量的箭引爆气球](https://mp.weixin.qq.com/s/HxVAJ6INMfNKiGwI88-RFw)代码稍做修改,就可以AC本题。 +把[452.用最少数量的箭引爆气球](https://programmercarl.com/0452.用最少数量的箭引爆气球.html)代码稍做修改,就可以AC本题。 -```C++ +```CPP class Solution { public: // 按照区间右边界排序 @@ -152,7 +152,7 @@ public: ``` 这里按照 左区间遍历,或者按照右边界遍历,都可以AC,具体原因我还没有仔细看,后面有空再补充。 -```C++ +```CPP class Solution { public: // 按照区间左边界排序 @@ -186,27 +186,27 @@ Java: class Solution { public int eraseOverlapIntervals(int[][] intervals) { if (intervals.length < 2) return 0; + Arrays.sort(intervals, new Comparator() { @Override public int compare(int[] o1, int[] o2) { - if (o1[0] != o2[0]) { + if (o1[1] != o2[1]) { return Integer.compare(o1[1],o2[1]); } else { - return Integer.compare(o2[0],o1[0]); + return Integer.compare(o1[0],o2[0]); } } }); - int count = 0; + int count = 1; int edge = intervals[0][1]; for (int i = 1; i < intervals.length; i++) { - if (intervals[i][0] < edge) { - count++; - } else { + if (edge <= intervals[i][0]){ + count ++; //non overlap + 1 edge = intervals[i][1]; } } - return count; + return intervals.length - count; } } ``` @@ -250,8 +250,31 @@ class Solution: ``` Go: - +```golang +func eraseOverlapIntervals(intervals [][]int) int { + var flag int + //先排序 + sort.Slice(intervals,func(i,j int)bool{ + return intervals[i][0]intervals[i][0]{ + flag++ + intervals[i][1]=min(intervals[i-1][1],intervals[i][1])//由于是先排序的,所以,第一位是递增顺序,故只需要将临近两个元素的第二个值最小值更新到该元素的第二个值即可作之后的判断 + } + } + return flag +} +func min(a,b int)int{ + if a>b{ + return b + } + return a +} +``` Javascript: +- 按右边界排序 ```Javascript var eraseOverlapIntervals = function(intervals) { intervals.sort((a, b) => { @@ -263,7 +286,7 @@ var eraseOverlapIntervals = function(intervals) { for(let i = 1; i < intervals.length; i++) { let interval = intervals[i] - if(interval[0] >= right) { + if(interval[0] >= end) { end = interval[1] count += 1 } @@ -272,10 +295,28 @@ var eraseOverlapIntervals = function(intervals) { return intervals.length - count }; ``` +- 按左边界排序 +```js +var eraseOverlapIntervals = function(intervals) { + // 按照左边界升序排列 + intervals.sort((a, b) => a[0] - b[0]) + let count = 1 + let end = intervals[intervals.length - 1][0] + // 倒序遍历,对单个区间来说,左边界越大越好,因为给前面区间的空间越大 + for(let i = intervals.length - 2; i >= 0; i--) { + if(intervals[i][1] <= end) { + count++ + end = intervals[i][0] + } + } + // count 记录的是最大非重复区间的个数 + return intervals.length - count +} +``` ----------------------- * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0450.删除二叉搜索树中的节点.md b/problems/0450.删除二叉搜索树中的节点.md index 4695ed50..b12d40aa 100644 --- a/problems/0450.删除二叉搜索树中的节点.md +++ b/problems/0450.删除二叉搜索树中的节点.md @@ -9,9 +9,9 @@ > 二叉搜索树删除节点就涉及到结构调整了 -## 450.删除二叉搜索树中的节点 +# 450.删除二叉搜索树中的节点 -题目链接: https://leetcode-cn.com/problems/delete-node-in-a-bst/ +[力扣题目链接]( https://leetcode-cn.com/problems/delete-node-in-a-bst/) 给定一个二叉搜索树的根节点 root 和一个值 key,删除二叉搜索树中的 key 对应的节点,并保证二叉搜索树的性质不变。返回二叉搜索树(有可能被更新)的根节点的引用。 @@ -25,7 +25,7 @@ ![450.删除二叉搜索树中的节点](https://img-blog.csdnimg.cn/20201020171048265.png) -## 思路 +# 思路 搜索树的节点删除要比节点增加复杂的多,有很多情况需要考虑,做好心里准备。 @@ -35,7 +35,7 @@ * 确定递归函数参数以及返回值 -说道递归函数的返回值,在[二叉树:搜索树中的插入操作](https://mp.weixin.qq.com/s/lwKkLQcfbCNX2W-5SOeZEA)中通过递归返回值来加入新节点, 这里也可以通过递归返回值删除节点。 +说道递归函数的返回值,在[二叉树:搜索树中的插入操作](https://programmercarl.com/0701.二叉搜索树中的插入操作.html)中通过递归返回值来加入新节点, 这里也可以通过递归返回值删除节点。 代码如下: @@ -78,7 +78,7 @@ if (root == nullptr) return root; 代码如下: -```C++ +```CPP if (root->val == key) { // 第二种情况:左右孩子都为空(叶子节点),直接删除节点, 返回NULL为根节点 // 第三种情况:其左孩子为空,右孩子不为空,删除节点,右孩子补位 ,返回右孩子为根节点 @@ -111,17 +111,32 @@ return root; **整体代码如下:(注释中:情况1,2,3,4,5和上面分析严格对应)** -```C++ +```CPP class Solution { public: TreeNode* deleteNode(TreeNode* root, int key) { if (root == nullptr) return root; // 第一种情况:没找到删除的节点,遍历到空节点直接返回了 if (root->val == key) { // 第二种情况:左右孩子都为空(叶子节点),直接删除节点, 返回NULL为根节点 + if (root->left == nullptr && root->right == nullptr) { + ///! 内存释放 + delete root; + return nullptr; + } // 第三种情况:其左孩子为空,右孩子不为空,删除节点,右孩子补位 ,返回右孩子为根节点 - if (root->left == nullptr) return root->right; + else if (root->left == nullptr) { + auto retNode = root->right; + ///! 内存释放 + delete root; + return retNode; + } // 第四种情况:其右孩子为空,左孩子不为空,删除节点,左孩子补位,返回左孩子为根节点 - else if (root->right == nullptr) return root->left; + else if (root->right == nullptr) { + auto retNode = root->left; + ///! 内存释放 + delete root; + return retNode; + } // 第五种情况:左右孩子节点都不为空,则将删除节点的左子树放到删除节点的右子树的最左面节点的左孩子的位置 // 并返回删除节点右孩子为新的根节点。 else { @@ -156,7 +171,7 @@ public: 代码如下:(关键部分已经注释) -```C++ +```CPP class Solution { public: TreeNode* deleteNode(TreeNode* root, int key) { @@ -186,7 +201,7 @@ public: 代码如下: -```C++ +```CPP class Solution { private: // 将目标节点(删除节点)的左子树放到 目标节点的右子树的最左面节点的左孩子位置上 @@ -228,7 +243,7 @@ public: }; ``` -## 总结 +# 总结 读完本篇,大家会发现二叉搜索树删除节点比增加节点复杂的多。 @@ -246,10 +261,10 @@ public: 迭代法其实不太容易写出来,所以如果是初学者的话,彻底掌握第一种递归写法就够了。 -## 其他语言版本 +# 其他语言版本 -Java: +## Java ```java class Solution { public TreeNode deleteNode(TreeNode root, int key) { @@ -278,15 +293,36 @@ class Solution { } } ``` +```java +// 解法2 +class Solution { + public TreeNode deleteNode(TreeNode root, int key) { + if (root == null) return root; + if (root.val == key) { + if (root.left == null) { + return root.right; + } else if (root.right == null) { + return root.left; + } else { + TreeNode cur = root.right; + while (cur.left != null) { + cur = cur.left; + } + cur.left = root.left; + root = root.right; + return root; + } + } + if (root.val > key) root.left = deleteNode(root.left, key); + if (root.val < key) root.right = deleteNode(root.right, key); + return root; + } +} +``` + +## Python -Python: ```python -# Definition for a binary tree node. -# class TreeNode: -# def __init__(self, val=0, left=None, right=None): -# self.val = val -# self.left = left -# self.right = right class Solution: def deleteNode(self, root: TreeNode, key: int) -> TreeNode: if not root: return root #第一种情况:没找到删除的节点,遍历到空节点直接返回了 @@ -318,8 +354,9 @@ class Solution: return root ``` -Go: +## Go ```Go +// 递归版本 func deleteNode(root *TreeNode, key int) *TreeNode { if root==nil{ return nil @@ -356,11 +393,56 @@ func deleteNode1(root *TreeNode)*TreeNode{ root.Left=deleteNode1(root.Left) return root } +// 迭代版本 +func deleteOneNode(target *TreeNode) *TreeNode { + if target == nil { + return target + } + if target.Right == nil { + return target.Left + } + cur := target.Right + for cur.Left != nil { + cur = cur.Left + } + cur.Left = target.Left + return target.Right +} +func deleteNode(root *TreeNode, key int) *TreeNode { + // 特殊情况处理 + if root == nil { + return root + } + cur := root + var pre *TreeNode + for cur != nil { + if cur.Val == key { + break + } + pre = cur + if cur.Val > key { + cur = cur.Left + } else { + cur = cur.Right + } + } + if pre == nil { + return deleteOneNode(cur) + } + // pre 要知道是删除左孩子还有右孩子 + if pre.Left != nil && pre.Left.Val == key { + pre.Left = deleteOneNode(cur) + } + if pre.Right != nil && pre.Right.Val == key { + pre.Right = deleteOneNode(cur) + } + return root +} ``` -JavaScript版本 +## JavaScript -> 递归 +递归 ```javascript /** @@ -410,4 +492,4 @@ var deleteNode = function (root, key) { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0452.用最少数量的箭引爆气球.md b/problems/0452.用最少数量的箭引爆气球.md index bb3ebbdc..07141558 100644 --- a/problems/0452.用最少数量的箭引爆气球.md +++ b/problems/0452.用最少数量的箭引爆气球.md @@ -9,7 +9,7 @@ ## 452. 用最少数量的箭引爆气球 -题目链接:https://leetcode-cn.com/problems/minimum-number-of-arrows-to-burst-balloons/ +[力扣题目链接](https://leetcode-cn.com/problems/minimum-number-of-arrows-to-burst-balloons/) 在二维空间中有许多球形的气球。对于每个气球,提供的输入是水平方向上,气球直径的开始和结束坐标。由于它是水平的,所以纵坐标并不重要,因此只要知道开始和结束的横坐标就足够了。开始坐标总是小于结束坐标。 @@ -84,7 +84,7 @@ C++代码如下: -```C++ +```CPP class Solution { private: static bool cmp(const vector& a, const vector& b) { @@ -175,6 +175,30 @@ class Solution: Go: +```golang +func findMinArrowShots(points [][]int) int { + var res int =1//弓箭数 + //先按照第一位排序 + sort.Slice(points,func (i,j int) bool{ + return points[i][0]b{ + return b + } + return a +} +``` Javascript: ```Javascript var findMinArrowShots = function(points) { @@ -194,9 +218,33 @@ var findMinArrowShots = function(points) { }; ``` +C: +```c +int cmp(const void *a,const void *b) +{ + return ((*((int**)a))[0] > (*((int**)b))[0]); +} + +int findMinArrowShots(int** points, int pointsSize, int* pointsColSize){ + //将points数组作升序排序 + qsort(points, pointsSize, sizeof(points[0]),cmp); + + int arrowNum = 1; + int i = 1; + for(i = 1; i < pointsSize; i++) { + //若前一个气球与当前气球不重叠,证明需要增加箭的数量 + if(points[i][0] > points[i-1][1]) + arrowNum++; + else + //若前一个气球与当前气球重叠,判断并更新最小的x_end + points[i][1] = points[i][1] > points[i-1][1] ? points[i-1][1] : points[i][1]; + } + return arrowNum; +} +``` ----------------------- * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0454.四数相加II.md b/problems/0454.四数相加II.md index 0621ab5b..475f93a0 100644 --- a/problems/0454.四数相加II.md +++ b/problems/0454.四数相加II.md @@ -11,8 +11,7 @@ # 第454题.四数相加II - -https://leetcode-cn.com/problems/4sum-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。 @@ -35,9 +34,9 @@ D = [ 0, 2] # 思路 -本题咋眼一看好像和[0015.三数之和](https://mp.weixin.qq.com/s/r5cgZFu0tv4grBAexdcd8A),[0018.四数之和](https://mp.weixin.qq.com/s/nQrcco8AZJV1pAOVjeIU_g)差不多,其实差很多。 +本题咋眼一看好像和[0015.三数之和](https://programmercarl.com/0015.三数之和.html),[0018.四数之和](https://programmercarl.com/0018.四数之和.html)差不多,其实差很多。 -**本题是使用哈希法的经典题目,而[0015.三数之和](https://mp.weixin.qq.com/s/r5cgZFu0tv4grBAexdcd8A),[0018.四数之和](https://mp.weixin.qq.com/s/nQrcco8AZJV1pAOVjeIU_g)并不合适使用哈希法**,因为三数之和和四数之和这两道题目使用哈希法在不超时的情况下做到对结果去重是很困难的,很有多细节需要处理。 +**本题是使用哈希法的经典题目,而[0015.三数之和](https://programmercarl.com/0015.三数之和.html),[0018.四数之和](https://programmercarl.com/0018.四数之和.html)并不合适使用哈希法**,因为三数之和和四数之和这两道题目使用哈希法在不超时的情况下做到对结果去重是很困难的,很有多细节需要处理。 **而这道题目是四个独立的数组,只要找到A[i] + B[j] + C[k] + D[l] = 0就可以,不用考虑有重复的四个元素相加等于0的情况,所以相对于题目18. 四数之和,题目15.三数之和,还是简单了不少!** @@ -53,7 +52,7 @@ D = [ 0, 2] C++代码: -```C++ +```CPP class Solution { public: int fourSumCount(vector& A, vector& B, vector& C, vector& D) { @@ -162,15 +161,9 @@ class Solution(object): # count=0 # for x3 in nums3: # for x4 in nums4: -# key = -x3-x4 -# value = hashmap.get(key) - - # dict的get方法会返回None(key不存在)或者key对应的value - # 所以如果value==0,就会继续执行or,count+0,否则就会直接加value - # 这样就不用去写if判断了 - -# count += value or 0 - +# key = 0 - x3 - x4 +# value = hashmap[key] # 若差值(key)不存在,则value被赋值0 +# count += value # return count ``` @@ -228,10 +221,95 @@ var fourSumCount = function(nums1, nums2, nums3, nums4) { ``` +PHP: +```php +class Solution { + /** + * @param Integer[] $nums1 + * @param Integer[] $nums2 + * @param Integer[] $nums3 + * @param Integer[] $nums4 + * @return Integer + */ + function fourSumCount($nums1, $nums2, $nums3, $nums4) { + $map = []; + foreach ($nums1 as $n1) { + foreach ($nums2 as $n2) { + $temp = $n1 + $n2; + $map[$temp] = isset($map[$temp]) ? $map[$temp]+1 : 1; + } + } + $count = 0; + foreach ($nums3 as $n3) { + foreach ($nums4 as $n4) { + $temp = 0 - $n3 - $n4; + if (isset($map[$temp])) { + $count += $map[$temp]; + } + } + } + return $count; + } +} +``` +Swift: +```swift +func fourSumCount(_ nums1: [Int], _ nums2: [Int], _ nums3: [Int], _ nums4: [Int]) -> Int { + // key:a+b的数值,value:a+b数值出现的次数 + var map = [Int: Int]() + // 遍历nums1和nums2数组,统计两个数组元素之和,和出现的次数,放到map中 + for i in 0 ..< nums1.count { + for j in 0 ..< nums2.count { + let sum1 = nums1[i] + nums2[j] + map[sum1] = (map[sum1] ?? 0) + 1 + } + } + // 统计a+b+c+d = 0 出现的次数 + var res = 0 + // 在遍历大num3和num4数组,找到如果 0-(c+d) 在map中出现过的话,就把map中key对应的value也就是出现次数统计出来。 + for i in 0 ..< nums3.count { + for j in 0 ..< nums4.count { + let sum2 = nums3[i] + nums4[j] + let other = 0 - sum2 + if map.keys.contains(other) { + res += map[other]! + } + } + } + return res +} +``` + +Rust: +```rust +use std::collections::HashMap; +impl Solution { + pub fn four_sum_count(nums1: Vec, nums2: Vec, nums3: Vec, nums4: Vec) -> i32 { + let mut umap:HashMap = HashMap::new(); + for num1 in &nums1 { + for num2 in &nums2 { + *umap.entry(num1 + num2).or_insert(0) += 1; + } + } + + let mut count = 0; + + for num3 in &nums3 { + for num4 in &nums4 { + let target:i32 = - (num3 + num4); + count += umap.get(&target).unwrap_or(&0); + } + } + + count + } +} +``` + ----------------------- * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0455.分发饼干.md b/problems/0455.分发饼干.md index 6b121e36..8e20c402 100644 --- a/problems/0455.分发饼干.md +++ b/problems/0455.分发饼干.md @@ -9,7 +9,7 @@ ## 455.分发饼干 -题目链接:https://leetcode-cn.com/problems/assign-cookies/ +[力扣题目链接](https://leetcode-cn.com/problems/assign-cookies/) 假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。 @@ -59,7 +59,7 @@ C++代码整体如下: -```C++ +```CPP // 时间复杂度:O(nlogn) // 空间复杂度:O(1) class Solution { @@ -88,7 +88,7 @@ public: 代码如下: -```C++ +```CPP class Solution { public: int findContentChildren(vector& g, vector& s) { @@ -117,6 +117,7 @@ public: Java: ```java class Solution { + // 思路1:优先考虑饼干,小饼干先喂饱小胃口 public int findContentChildren(int[] g, int[] s) { Arrays.sort(g); Arrays.sort(s); @@ -132,10 +133,30 @@ class Solution { } } ``` +```java +class Solution { + // 思路2:优先考虑胃口,先喂饱大胃口 + public int findContentChildren(int[] g, int[] s) { + Arrays.sort(g); + Arrays.sort(s); + int count = 0; + int start = s.length - 1; + // 遍历胃口 + for (int index = g.length - 1; index >= 0; index--) { + if(start >= 0 && g[index] <= s[start]) { + start--; + count++; + } + } + return count; + } +} +``` Python: ```python3 class Solution: + # 思路1:优先考虑胃饼干 def findContentChildren(self, g: List[int], s: List[int]) -> int: g.sort() s.sort() @@ -145,6 +166,20 @@ class Solution: res += 1 return res ``` +```python +class Solution: + # 思路2:优先考虑胃口 + def findContentChildren(self, g: List[int], s: List[int]) -> int: + g.sort() + s.sort() + start, count = len(s) - 1, 0 + for index in range(len(g) - 1, -1, -1): # 先喂饱大胃口 + if start >= 0 and g[index] <= s[start]: + start -= 1 + count += 1 + return count +``` + Go: ```golang //排序后,局部最优 @@ -162,11 +197,10 @@ func findContentChildren(g []int, s []int) int { return child } - +``` Javascript: -```Javascript - +``` var findContentChildren = function(g, s) { g = g.sort((a, b) => a - b) s = s.sort((a, b) => a - b) @@ -188,4 +222,4 @@ var findContentChildren = function(g, s) { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0459.重复的子字符串.md b/problems/0459.重复的子字符串.md index 25b52e86..7d8a7286 100644 --- a/problems/0459.重复的子字符串.md +++ b/problems/0459.重复的子字符串.md @@ -13,7 +13,7 @@ # 459.重复的子字符串 -https://leetcode-cn.com/problems/repeated-substring-pattern/ +[力扣题目链接](https://leetcode-cn.com/problems/repeated-substring-pattern/) 给定一个非空的字符串,判断它是否可以由它的一个子串重复多次构成。给定的字符串只含有小写英文字母,并且长度不超过10000。 @@ -41,11 +41,11 @@ https://leetcode-cn.com/problems/repeated-substring-pattern/ * [帮你把KMP算法学个通透!(求next数组代码篇)](https://www.bilibili.com/video/BV1M5411j7Xx) -我们在[字符串:KMP算法精讲](https://mp.weixin.qq.com/s/MoRBHbS4hQXn7LcPdmHmIg)里提到了,在一个串中查找是否出现过另一个串,这是KMP的看家本领。 +我们在[字符串:KMP算法精讲](https://programmercarl.com/0028.实现strStr.html)里提到了,在一个串中查找是否出现过另一个串,这是KMP的看家本领。 那么寻找重复子串怎么也涉及到KMP算法了呢? -这里就要说一说next数组了,next 数组记录的就是最长相同前后缀( [字符串:KMP算法精讲](https://mp.weixin.qq.com/s/MoRBHbS4hQXn7LcPdmHmIg) 这里介绍了什么是前缀,什么是后缀,什么又是最长相同前后缀), 如果 next[len - 1] != -1,则说明字符串有最长相同的前后缀(就是字符串里的前缀子串和后缀子串相同的最长长度)。 +这里就要说一说next数组了,next 数组记录的就是最长相同前后缀( [字符串:KMP算法精讲](https://programmercarl.com/0028.实现strStr.html) 这里介绍了什么是前缀,什么是后缀,什么又是最长相同前后缀), 如果 next[len - 1] != -1,则说明字符串有最长相同的前后缀(就是字符串里的前缀子串和后缀子串相同的最长长度)。 最长相等前后缀的长度为:next[len - 1] + 1。 @@ -70,7 +70,7 @@ next[len - 1] = 7,next[len - 1] + 1 = 8,8就是此时字符串asdfasdfasdf C++代码如下:(这里使用了前缀表统一减一的实现方式) -```C++ +```CPP class Solution { public: void getNext (int* next, const string& s){ @@ -104,7 +104,7 @@ public: 前缀表(不减一)的C++代码实现 -```C++ +```CPP class Solution { public: void getNext (int* next, const string& s){ @@ -137,11 +137,11 @@ public: # 拓展 -在[字符串:KMP算法精讲](https://mp.weixin.qq.com/s/MoRBHbS4hQXn7LcPdmHmIg)中讲解KMP算法的基础理论,给出next数组究竟是如何来了,前缀表又是怎么回事,为什么要选择前缀表。 +在[字符串:KMP算法精讲](https://programmercarl.com/0028.实现strStr.html)中讲解KMP算法的基础理论,给出next数组究竟是如何来了,前缀表又是怎么回事,为什么要选择前缀表。 讲解一道KMP的经典题目,力扣:28. 实现 strStr(),判断文本串里是否出现过模式串,这里涉及到构造next数组的代码实现,以及使用next数组完成模式串与文本串的匹配过程。 -后来很多同学反馈说:搞不懂前后缀,什么又是最长相同前后缀(最长公共前后缀我认为这个用词不准确),以及为什么前缀表要统一减一(右移)呢,不减一行不行?针对这些问题,我在[字符串:KMP算法精讲](https://mp.weixin.qq.com/s/MoRBHbS4hQXn7LcPdmHmIg)给出了详细的讲解。 +后来很多同学反馈说:搞不懂前后缀,什么又是最长相同前后缀(最长公共前后缀我认为这个用词不准确),以及为什么前缀表要统一减一(右移)呢,不减一行不行?针对这些问题,我在[字符串:KMP算法精讲](https://programmercarl.com/0028.实现strStr.html)给出了详细的讲解。 ## 其他语言版本 @@ -369,4 +369,4 @@ var repeatedSubstringPattern = function (s) { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0463.岛屿的周长.md b/problems/0463.岛屿的周长.md index 40378854..609c25a5 100644 --- a/problems/0463.岛屿的周长.md +++ b/problems/0463.岛屿的周长.md @@ -1,6 +1,6 @@ ## 题目链接 -https://leetcode-cn.com/problems/island-perimeter/ +[力扣题目链接](https://leetcode-cn.com/problems/island-perimeter/) ## 思路 @@ -16,7 +16,7 @@ https://leetcode-cn.com/problems/island-perimeter/ C++代码如下:(详细注释) -```C++ +```CPP class Solution { public: int direction[4][2] = {0, 1, 1, 0, -1, 0, 0, -1}; @@ -56,7 +56,7 @@ result = 岛屿数量 * 4 - cover * 2; C++代码如下:(详细注释) -```C++ +```CPP class Solution { public: int islandPerimeter(vector>& grid) { @@ -84,6 +84,37 @@ public: Java: +```java +// 解法一 +class Solution { + // 上下左右 4 个方向 + int[] dirx = {-1, 1, 0, 0}; + int[] diry = {0, 0, -1, 1}; + + public int islandPerimeter(int[][] grid) { + int m = grid.length; + int n = grid[0].length; + int res = 0; // 岛屿周长 + for (int i = 0; i < m; i++) { + for (int j = 0; j < n; j++) { + if (grid[i][j] == 1) { + for (int k = 0; k < 4; k++) { + int x = i + dirx[k]; + int y = j + diry[k]; + // 当前位置是陆地,并且从当前位置4个方向扩展的“新位置”是“水域”或“新位置“越界,则会为周长贡献一条边 + if (x < 0 || x >= m || y < 0 || y >= n || grid[x][y] == 0) { + res++; + continue; + } + } + } + } + } + return res; + } +} +``` + Python: Go: @@ -95,4 +126,4 @@ JavaScript: * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0474.一和零.md b/problems/0474.一和零.md index 6b73c080..e3b05704 100644 --- a/problems/0474.一和零.md +++ b/problems/0474.一和零.md @@ -9,7 +9,7 @@ ## 474.一和零 -题目链接:https://leetcode-cn.com/problems/ones-and-zeroes/ +[力扣题目链接](https://leetcode-cn.com/problems/ones-and-zeroes/) 给你一个二进制字符串数组 strs 和两个整数 m 和 n 。 @@ -39,6 +39,11 @@ ## 思路 +如果对背包问题不都熟悉先看这两篇: + +* [动态规划:关于01背包问题,你该了解这些!](https://programmercarl.com/背包理论基础01背包-1.html) +* [动态规划:关于01背包问题,你该了解这些!(滚动数组)](https://programmercarl.com/背包理论基础01背包-2.html) + 这道题目,还是比较难的,也有点像程序员自己给自己出个脑筋急转弯,程序员何苦为难程序员呢哈哈。 来说题,本题不少同学会认为是多重背包,一些题解也是这么写的。 @@ -84,18 +89,18 @@ dp[i][j] 就可以是 dp[i - zeroNum][j - oneNum] + 1。 3. dp数组如何初始化 -在[动态规划:关于01背包问题,你该了解这些!(滚动数组)](https://mp.weixin.qq.com/s/M4uHxNVKRKm5HPjkNZBnFA)中已经讲解了,01背包的dp数组初始化为0就可以。 +在[动态规划:关于01背包问题,你该了解这些!(滚动数组)](https://programmercarl.com/背包理论基础01背包-2.html)中已经讲解了,01背包的dp数组初始化为0就可以。 因为物品价值不会是负数,初始为0,保证递推的时候dp[i][j]不会被初始值覆盖。 4. 确定遍历顺序 -在[动态规划:关于01背包问题,你该了解这些!(滚动数组)](https://mp.weixin.qq.com/s/M4uHxNVKRKm5HPjkNZBnFA)中,我们讲到了01背包为什么一定是外层for循环遍历物品,内层for循环遍历背包容量且从后向前遍历! +在[动态规划:关于01背包问题,你该了解这些!(滚动数组)](https://programmercarl.com/背包理论基础01背包-2.html)中,我们讲到了01背包为什么一定是外层for循环遍历物品,内层for循环遍历背包容量且从后向前遍历! 那么本题也是,物品就是strs里的字符串,背包容量就是题目描述中的m和n。 代码如下: -```C++ +```CPP for (string str : strs) { // 遍历物品 int oneNum = 0, zeroNum = 0; for (char c : str) { @@ -126,7 +131,7 @@ for (string str : strs) { // 遍历物品 以上动规五部曲分析完毕,C++代码如下: -```C++ +```CPP class Solution { public: int findMaxForm(vector& strs, int m, int n) { @@ -279,4 +284,4 @@ const findMaxForm = (strs, m, n) => { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0491.递增子序列.md b/problems/0491.递增子序列.md index 103b8b10..ea113f4b 100644 --- a/problems/0491.递增子序列.md +++ b/problems/0491.递增子序列.md @@ -11,7 +11,7 @@ ## 491.递增子序列 -题目链接:https://leetcode-cn.com/problems/increasing-subsequences/ +[力扣题目链接](https://leetcode-cn.com/problems/increasing-subsequences/) 给定一个整型数组, 你的任务是找到所有该数组的递增子序列,递增子序列的长度至少是2。 @@ -33,11 +33,11 @@ 这个递增子序列比较像是取有序的子集。而且本题也要求不能有相同的递增子序列。 -这又是子集,又是去重,是不是不由自主的想起了刚刚讲过的[回溯算法:求子集问题(二)](https://mp.weixin.qq.com/s/WJ4JNDRJgsW3eUN72Hh3uQ)。 +这又是子集,又是去重,是不是不由自主的想起了刚刚讲过的[回溯算法:求子集问题(二)](https://programmercarl.com/0090.子集II.html)。 就是因为太像了,更要注意差别所在,要不就掉坑里了! -在[回溯算法:求子集问题(二)](https://mp.weixin.qq.com/s/WJ4JNDRJgsW3eUN72Hh3uQ)中我们是通过排序,再加一个标记数组来达到去重的目的。 +在[回溯算法:求子集问题(二)](https://programmercarl.com/0090.子集II.html)中我们是通过排序,再加一个标记数组来达到去重的目的。 而本题求自增子序列,是不能对原数组经行排序的,排完序的数组都是自增子序列了。 @@ -66,7 +66,7 @@ void backtracking(vector& nums, int startIndex) * 终止条件 -本题其实类似求子集问题,也是要遍历树形结构找每一个节点,所以和[回溯算法:求子集问题!](https://mp.weixin.qq.com/s/NNRzX-vJ_pjK4qxohd_LtA)一样,可以不加终止条件,startIndex每次都会加1,并不会无限递归。 +本题其实类似求子集问题,也是要遍历树形结构找每一个节点,所以和[回溯算法:求子集问题!](https://programmercarl.com/0078.子集.html)一样,可以不加终止条件,startIndex每次都会加1,并不会无限递归。 但本题收集结果有所不同,题目要求递增子序列大小至少为2,所以代码如下: @@ -105,7 +105,7 @@ for (int i = startIndex; i < nums.size(); i++) { 最后整体C++代码如下: -```C++ +```CPP // 版本一 class Solution { private: @@ -150,7 +150,7 @@ public: 那么优化后的代码如下: -```C++ +```CPP // 版本二 class Solution { private: @@ -184,7 +184,7 @@ public: 这份代码在leetcode上提交,要比版本一耗时要好的多。 -**所以正如在[哈希表:总结篇!(每逢总结必经典)](https://mp.weixin.qq.com/s/1s91yXtarL-PkX07BfnwLg)中说的那样,数组,set,map都可以做哈希表,而且数组干的活,map和set都能干,但如何数值范围小的话能用数组尽量用数组**。 +**所以正如在[哈希表:总结篇!(每逢总结必经典)](https://programmercarl.com/哈希表总结.html)中说的那样,数组,set,map都可以做哈希表,而且数组干的活,map和set都能干,但如果数值范围小的话能用数组尽量用数组**。 @@ -192,7 +192,7 @@ public: 本题题解清一色都说是深度优先搜索,但我更倾向于说它用回溯法,而且本题我也是完全使用回溯法的逻辑来分析的。 -相信大家在本题中处处都能看到是[回溯算法:求子集问题(二)](https://mp.weixin.qq.com/s/WJ4JNDRJgsW3eUN72Hh3uQ)的身影,但处处又都是陷阱。 +相信大家在本题中处处都能看到是[回溯算法:求子集问题(二)](https://programmercarl.com/0090.子集II.html)的身影,但处处又都是陷阱。 **对于养成思维定式或者套模板套嗨了的同学,这道题起到了很好的警醒作用。更重要的是拓展了大家的思路!** @@ -320,4 +320,4 @@ var findSubsequences = function(nums) { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0494.目标和.md b/problems/0494.目标和.md index b7da0252..07cf0433 100644 --- a/problems/0494.目标和.md +++ b/problems/0494.目标和.md @@ -9,7 +9,7 @@ ## 494. 目标和 -题目链接:https://leetcode-cn.com/problems/target-sum/ +[力扣题目链接](https://leetcode-cn.com/problems/target-sum/) 难度:中等 @@ -39,7 +39,12 @@ ## 思路 -如果跟着「代码随想录」一起学过[回溯算法系列](https://mp.weixin.qq.com/s/r73thpBnK1tXndFDtlsdCQ)的录友,看到这道题,应该有一种直觉,就是感觉好像回溯法可以爆搜出来。 +如果对背包问题不都熟悉先看这两篇: + +* [动态规划:关于01背包问题,你该了解这些!](https://programmercarl.com/背包理论基础01背包-1.html) +* [动态规划:关于01背包问题,你该了解这些!(滚动数组)](https://programmercarl.com/背包理论基础01背包-2.html) + +如果跟着「代码随想录」一起学过[回溯算法系列](https://programmercarl.com/回溯总结.html)的录友,看到这道题,应该有一种直觉,就是感觉好像回溯法可以爆搜出来。 事实确实如此,下面我也会给出相应的代码,只不过会超时,哈哈。 @@ -59,7 +64,7 @@ target是固定的,sum是固定的,left就可以求出来。 ## 回溯算法 -在回溯算法系列中,一起学过这道题目[回溯算法:39. 组合总和](https://mp.weixin.qq.com/s/FLg8G6EjVcxBjwCbzpACPw)的录友应该感觉很熟悉,这不就是组合总和问题么? +在回溯算法系列中,一起学过这道题目[回溯算法:39. 组合总和](https://programmercarl.com/0039.组合总和.html)的录友应该感觉很熟悉,这不就是组合总和问题么? 此时可以套组合总和的回溯法代码,几乎不用改动。 @@ -67,7 +72,7 @@ target是固定的,sum是固定的,left就可以求出来。 我也把代码给出来吧,大家可以了解一下,回溯的解法,以下是本题转变为组合总和问题的回溯法代码: -```C++ +```CPP class Solution { private: vector> result; @@ -124,11 +129,14 @@ x = (S + sum) / 2 这么担心就对了,例如sum 是5,S是2的话其实就是无解的,所以: -```C++ +```CPP if ((S + sum) % 2 == 1) return 0; // 此时没有方案 ``` -**看到这种表达式,应该本能的反应,两个int相加数值可能溢出的问题,当然本题并没有溢出**。 +同时如果 S的绝对值已经大于sum,那么也是没有方案的。 +```CPP +if (abs(S) > sum) return 0; // 此时没有方案 +``` 再回归到01背包问题,为什么是01背包呢? @@ -144,7 +152,7 @@ dp[j] 表示:填满j(包括j)这么大容积的包,有dp[i]种方法 其实也可以使用二维dp数组来求解本题,dp[i][j]:使用 下标为[0, i]的nums[i]能够凑满j(包括j)这么大容量的包,有dp[i][j]种方法。 -下面我都是统一使用一维数组进行讲解, 二维降为一维(滚动数组),其实就是上一层拷贝下来,这个我在[动态规划:关于01背包问题,你该了解这些!(滚动数组)](https://mp.weixin.qq.com/s/M4uHxNVKRKm5HPjkNZBnFA)也有介绍。 +下面我都是统一使用一维数组进行讲解, 二维降为一维(滚动数组),其实就是上一层拷贝下来,这个我在[动态规划:关于01背包问题,你该了解这些!(滚动数组)](https://programmercarl.com/背包理论基础01背包-2.html)也有介绍。 2. 确定递推公式 @@ -179,7 +187,7 @@ dp[j]其他下标对应的数值应该初始化为0,从递归公式也可以 4. 确定遍历顺序 -在[动态规划:关于01背包问题,你该了解这些!(滚动数组)](https://mp.weixin.qq.com/s/M4uHxNVKRKm5HPjkNZBnFA)中,我们讲过对于01背包问题一维dp的遍历,nums放在外循环,target在内循环,且内循环倒序。 +在[动态规划:关于01背包问题,你该了解这些!(滚动数组)](https://programmercarl.com/背包理论基础01背包-2.html)中,我们讲过对于01背包问题一维dp的遍历,nums放在外循环,target在内循环,且内循环倒序。 5. 举例推导dp数组 @@ -194,13 +202,13 @@ dp数组状态变化如下: C++代码如下: -```C++ +```CPP class Solution { public: int findTargetSumWays(vector& nums, int S) { int sum = 0; for (int i = 0; i < nums.size(); i++) sum += nums[i]; - if (S > sum) return 0; // 此时没有方案 + if (abs(S) > sum) return 0; // 此时没有方案 if ((S + sum) % 2 == 1) return 0; // 此时没有方案 int bagSize = (S + sum) / 2; vector dp(bagSize + 1, 0); @@ -221,9 +229,9 @@ public: ## 总结 -此时 大家应该不仅想起,我们之前讲过的[回溯算法:39. 组合总和](https://mp.weixin.qq.com/s/FLg8G6EjVcxBjwCbzpACPw)是不是应该也可以用dp来做啊? +此时 大家应该不仅想起,我们之前讲过的[回溯算法:39. 组合总和](https://programmercarl.com/0039.组合总和.html)是不是应该也可以用dp来做啊? -是的,如果仅仅是求个数的话,就可以用dp,但[回溯算法:39. 组合总和](https://mp.weixin.qq.com/s/FLg8G6EjVcxBjwCbzpACPw)要求的是把所有组合列出来,还是要使用回溯法爆搜的。 +是的,如果仅仅是求个数的话,就可以用dp,但[回溯算法:39. 组合总和](https://programmercarl.com/0039.组合总和.html)要求的是把所有组合列出来,还是要使用回溯法爆搜的。 本题还是有点难度,大家也可以记住,在求装满背包有几种方法的情况下,递推公式一般为: @@ -248,6 +256,7 @@ class Solution { for (int i = 0; i < nums.length; i++) sum += nums[i]; if ((target + sum) % 2 != 0) return 0; int size = (target + sum) / 2; + if(size < 0) size = -size; int[] dp = new int[size + 1]; dp[0] = 1; for (int i = 0; i < nums.length; i++) { @@ -312,7 +321,7 @@ const findTargetSumWays = (nums, target) => { const sum = nums.reduce((a, b) => a+b); - if(target > sum) { + if(Math.abs(target) > sum) { return 0; } @@ -342,4 +351,4 @@ const findTargetSumWays = (nums, target) => { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0496.下一个更大元素I.md b/problems/0496.下一个更大元素I.md index 0404e434..95aca60b 100644 --- a/problems/0496.下一个更大元素I.md +++ b/problems/0496.下一个更大元素I.md @@ -1,7 +1,7 @@ # 496.下一个更大元素 I -题目链接:https://leetcode-cn.com/problems/next-greater-element-i/ +[力扣题目链接](https://leetcode-cn.com/problems/next-greater-element-i/) 给你两个 没有重复元素 的数组 nums1 和 nums2 ,其中nums1 是 nums2 的子集。 @@ -24,7 +24,7 @@ nums1 中数字 x 的下一个更大元素是指 x 在 nums2 中对应位 解释: 对于 num1 中的数字 2 ,第二个数组中的下一个较大数字是 3 。 对于 num1 中的数字 4 ,第二个数组中没有下一个更大的数字,因此输出-1 。 -  + 提示: * 1 <= nums1.length <= nums2.length <= 1000 @@ -34,13 +34,13 @@ nums1 中数字 x 的下一个更大元素是指 x 在 nums2 中对应位 # 思路 -做本题之前,建议先做一下[739. 每日温度](https://mp.weixin.qq.com/s/YeQ7eE0-hZpxJfJJziq25Q) +做本题之前,建议先做一下[739. 每日温度](https://programmercarl.com/0739.每日温度.html) -在[739. 每日温度](https://mp.weixin.qq.com/s/YeQ7eE0-hZpxJfJJziq25Q)中是求每个元素下一个比当前元素大的元素的位置。 +在[739. 每日温度](https://programmercarl.com/0739.每日温度.html)中是求每个元素下一个比当前元素大的元素的位置。 本题则是说nums1 是 nums2的子集,找nums1中的元素在nums2中下一个比当前元素大的元素。 -看上去和[739. 每日温度](https://mp.weixin.qq.com/s/YeQ7eE0-hZpxJfJJziq25Q) 就如出一辙了。 +看上去和[739. 每日温度](https://programmercarl.com/0739.每日温度.html) 就如出一辙了。 几乎是一样的,但是这么绕了一下,其实还上升了一点难度。 @@ -60,11 +60,11 @@ nums1 中数字 x 的下一个更大元素是指 x 在 nums2 中对应位 没有重复元素,我们就可以用map来做映射了。根据数值快速找到下标,还可以判断nums2[i]是否在nums1中出现过。 -C++中,当我们要使用集合来解决哈希问题的时候,优先使用unordered_set,因为它的查询和增删效率是最优的。我在[关于哈希表,你该了解这些!](https://mp.weixin.qq.com/s/RSUANESA_tkhKhYe3ZR8Jg)中也做了详细的解释。 +C++中,当我们要使用集合来解决哈希问题的时候,优先使用unordered_set,因为它的查询和增删效率是最优的。我在[关于哈希表,你该了解这些!](https://programmercarl.com/哈希表理论基础.html)中也做了详细的解释。 那么预处理代码如下: -```C++ +```CPP unordered_map umap; // key:下表元素,value:下表 for (int i = 0; i < nums1.size(); i++) { umap[nums1[i]] = i; @@ -100,7 +100,7 @@ for (int i = 0; i < nums1.size(); i++) { 代码如下: -```C++ +```CPP while (!st.empty() && nums2[i] > nums2[st.top()]) { if (umap.count(nums2[st.top()]) > 0) { // 看map里是否存在这个元素 int index = umap[nums2[st.top()]]; // 根据map找到nums2[st.top()] 在 nums1中的下表 @@ -114,7 +114,7 @@ st.push(i); 以上分析完毕,C++代码如下: -```C++ +```CPP // 版本一 class Solution { public: @@ -152,7 +152,7 @@ public: 针对版本一,进行代码精简后,代码如下: -```C++ +```CPP // 版本二 class Solution { public: @@ -186,7 +186,37 @@ public: 建议大家把情况一二三想清楚了,先写出版本一的代码,然后在其基础上在做精简! ## 其他语言版本 +Java +```java +class Solution { + public int[] nextGreaterElement(int[] nums1, int[] nums2) { + Stack temp = new Stack<>(); + int[] res = new int[nums1.length]; + Arrays.fill(res,-1); + HashMap hashMap = new HashMap<>(); + for (int i = 0 ; i< nums1.length ; i++){ + hashMap.put(nums1[i],i); + } + temp.add(0); + for (int i = 1; i < nums2.length; i++) { + if (nums2[i] <= nums2[temp.peek()]) { + temp.add(i); + } else { + while (!temp.isEmpty() && nums2[temp.peek()] < nums2[i]) { + if (hashMap.containsKey(nums2[temp.peek()])){ + Integer index = hashMap.get(nums2[temp.peek()]); + res[index] = nums2[i]; + } + temp.pop(); + } + temp.add(i); + } + } + return res; + } +} +``` Python: ```python3 class Solution: @@ -208,3 +238,4 @@ class Solution: return result ``` +
diff --git a/problems/0501.二叉搜索树中的众数.md b/problems/0501.二叉搜索树中的众数.md index ac7ff603..22d4f4ac 100644 --- a/problems/0501.二叉搜索树中的众数.md +++ b/problems/0501.二叉搜索树中的众数.md @@ -9,9 +9,9 @@ > 二叉树上应该怎么求,二叉搜索树上又应该怎么求? -## 501.二叉搜索树中的众数 +# 501.二叉搜索树中的众数 -题目地址:https://leetcode-cn.com/problems/find-mode-in-binary-search-tree/solution/ +[力扣题目链接](https://leetcode-cn.com/problems/find-mode-in-binary-search-tree/solution/) 给定一个有相同值的二叉搜索树(BST),找出 BST 中的所有众数(出现频率最高的元素)。 @@ -33,7 +33,7 @@ 进阶:你可以不使用额外的空间吗?(假设由递归产生的隐式调用栈的开销不被计算在内) -## 思路 +# 思路 这道题目呢,递归法我从两个维度来讲。 @@ -53,7 +53,7 @@ 这里采用前序遍历,代码如下: -```C++ +```CPP // map key:元素,value:出现频率 void searchBST(TreeNode* cur, unordered_map& map) { // 前序遍历 if (cur == NULL) return ; @@ -87,7 +87,7 @@ sort(vec.begin(), vec.end(), cmp); // 给频率排个序 代码如下: -```C++ +```CPP result.push_back(vec[0].first); for (int i = 1; i < vec.size(); i++) { // 取最高的放到result数组中 @@ -100,7 +100,7 @@ return result; 整体C++代码如下: -```C++ +```CPP class Solution { private: @@ -145,7 +145,7 @@ public: 中序遍历代码如下: -```C++ +```CPP void searchBST(TreeNode* cur) { if (cur == NULL) return ; searchBST(cur->left); // 左 @@ -161,7 +161,7 @@ void searchBST(TreeNode* cur) { 这就考察对树的操作了。 -在[二叉树:搜索树的最小绝对差](https://mp.weixin.qq.com/s/Hwzml6698uP3qQCC1ctUQQ)中我们就使用了pre指针和cur指针的技巧,这次又用上了。 +在[二叉树:搜索树的最小绝对差](https://programmercarl.com/0530.二叉搜索树的最小绝对差.html)中我们就使用了pre指针和cur指针的技巧,这次又用上了。 弄一个指针指向前一个节点,这样每次cur(当前节点)才能和pre(前一个节点)作比较。 @@ -217,7 +217,7 @@ if (count > maxCount) { // 如果计数大于最大值 关键代码都讲完了,完整代码如下:(**只需要遍历一遍二叉搜索树,就求出了众数的集合**) -```C++ +```CPP class Solution { private: int maxCount; // 最大频率 @@ -272,14 +272,14 @@ public: 二叉树前中后序转迭代,传送门: -* [二叉树:前中后序迭代法](https://mp.weixin.qq.com/s/c_zCrGHIVlBjUH_hJtghCg) -* [二叉树:前中后序统一风格的迭代方式](https://mp.weixin.qq.com/s/WKg0Ty1_3SZkztpHubZPRg) +* [二叉树:前中后序迭代法](https://programmercarl.com/二叉树的迭代遍历.html) +* [二叉树:前中后序统一风格的迭代方式](https://programmercarl.com/二叉树的统一迭代法.html) 下面我给出其中的一种中序遍历的迭代法,其中间处理逻辑一点都没有变(我从递归法直接粘过来的代码,连注释都没改,哈哈) 代码如下: -```C++ +```CPP class Solution { public: vector findMode(TreeNode* root) { @@ -321,7 +321,7 @@ public: }; ``` -## 总结 +# 总结 本题在递归法中,我给出了如果是普通二叉树,应该怎么求众数。 @@ -340,12 +340,13 @@ public: > **需要强调的是 leetcode上的耗时统计是非常不准确的,看个大概就行,一样的代码耗时可以差百分之50以上**,所以leetcode的耗时统计别太当回事,知道理论上的效率优劣就行了。 -## 其他语言版本 +# 其他语言版本 -Java: +## Java 暴力法 + ```java class Solution { public int[] findMode(FindModeInBinarySearchTree.TreeNode root) { @@ -379,6 +380,8 @@ class Solution { } ``` +中序遍历-不使用额外空间,利用二叉搜索树特性 + ```Java class Solution { ArrayList resList; @@ -426,16 +429,50 @@ class Solution { } } ``` +迭代法 +```java +class Solution { + public int[] findMode(TreeNode root) { + TreeNode pre = null; + Stack stack = new Stack<>(); + List result = new ArrayList<>(); + int maxCount = 0; + int count = 0; + TreeNode cur = root; + while (cur != null || !stack.isEmpty()) { + if (cur != null) { + stack.push(cur); + cur =cur.left; + }else { + cur = stack.pop(); + // 计数 + if (pre == null || cur.val != pre.val) { + count = 1; + }else { + count++; + } + // 更新结果 + if (count > maxCount) { + maxCount = count; + result.clear(); + result.add(cur.val); + }else if (count == maxCount) { + result.add(cur.val); + } + pre = cur; + cur = cur.right; + } + } + return result.stream().mapToInt(Integer::intValue).toArray(); + } +} +``` + +## Python + +递归法 -Python: ```python -# Definition for a binary tree node. -# class TreeNode: -# def __init__(self, val=0, left=None, right=None): -# self.val = val -# self.left = left -# self.right = right -# 递归法 class Solution: def findMode(self, root: TreeNode) -> List[int]: if not root: return @@ -460,36 +497,11 @@ class Solution: return findNumber(root) return self.res +``` -# 迭代法-中序遍历-使用额外空间map的方法: -class Solution: - def findMode(self, root: TreeNode) -> List[int]: - stack = [] - cur = root - pre = None - dist = {} - while cur or stack: - if cur: # 指针来访问节点,访问到最底层 - stack.append(cur) - cur = cur.left - else: # 逐一处理节点 - cur = stack.pop() - if cur.val in dist: - dist[cur.val] += 1 - else: - dist[cur.val] = 1 - pre = cur - cur = cur.right - - # 找出字典中最大的key - res = [] - for key, value in dist.items(): - if (value == max(dist.values())): - res.append(key) - return res - -# 迭代法-中序遍历-不使用额外空间,利用二叉搜索树特性: +迭代法-中序遍历-不使用额外空间,利用二叉搜索树特性 +```python class Solution: def findMode(self, root: TreeNode) -> List[int]: stack = [] @@ -521,18 +533,11 @@ class Solution: return res ``` -Go: +## Go + 暴力法(非BSL) ```go -/** - * Definition for a binary tree node. - * type TreeNode struct { - * Val int - * Left *TreeNode - * Right *TreeNode - * } - */ func findMode(root *TreeNode) []int { var history map[int]int var maxValue int @@ -571,15 +576,7 @@ func traversal(root *TreeNode,history map[int]int){ 计数法,不使用额外空间,利用二叉树性质,中序遍历 ```go -/** - * Definition for a binary tree node. - * type TreeNode struct { - * Val int - * Left *TreeNode - * Right *TreeNode - * } - */ - func findMode(root *TreeNode) []int { +func findMode(root *TreeNode) []int { res := make([]int, 0) count := 1 max := 1 @@ -611,8 +608,9 @@ func traversal(root *TreeNode,history map[int]int){ } ``` -JavaScript版本: -使用额外空间map的方法: +## JavaScript + +使用额外空间map的方法 ```javascript var findMode = function(root) { // 使用递归中序遍历 @@ -649,8 +647,10 @@ var findMode = function(root) { } return res; }; -``` +``` + 不使用额外空间,利用二叉树性质,中序遍历(有序): + ```javascript var findMode = function(root) { // 不使用额外空间,使用中序遍历,设置出现最大次数初始值为1 @@ -690,4 +690,4 @@ var findMode = function(root) { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0503.下一个更大元素II.md b/problems/0503.下一个更大元素II.md index 34ade48e..624c6c7c 100644 --- a/problems/0503.下一个更大元素II.md +++ b/problems/0503.下一个更大元素II.md @@ -1,7 +1,7 @@ # 503.下一个更大元素II -链接:https://leetcode-cn.com/problems/next-greater-element-ii/ +[力扣题目链接](https://leetcode-cn.com/problems/next-greater-element-ii/) 给定一个循环数组(最后一个元素的下一个元素是数组的第一个元素),输出每个元素的下一个更大元素。数字 x 的下一个更大的元素是按数组遍历顺序,这个数字之后的第一个比它更大的数,这意味着你应该循环地搜索它的下一个更大的数。如果不存在,则输出 -1。 @@ -14,13 +14,13 @@ # 思路 -做本题之前建议先做[739. 每日温度](https://mp.weixin.qq.com/s/YeQ7eE0-hZpxJfJJziq25Q) 和 [496.下一个更大元素 I](https://mp.weixin.qq.com/s/U0O6XkFOe-RMXthPS16sWQ)。 +做本题之前建议先做[739. 每日温度](https://programmercarl.com/0739.每日温度.html) 和 [496.下一个更大元素 I](https://programmercarl.com/0496.下一个更大元素I.html)。 -这道题和[739. 每日温度](https://mp.weixin.qq.com/s/YeQ7eE0-hZpxJfJJziq25Q)也几乎如出一辙。 +这道题和[739. 每日温度](https://programmercarl.com/0739.每日温度.html)也几乎如出一辙。 不同的时候本题要循环数组了。 -关于单调栈的讲解我在题解[739. 每日温度](https://mp.weixin.qq.com/s/YeQ7eE0-hZpxJfJJziq25Q)中已经详细讲解了。 +关于单调栈的讲解我在题解[739. 每日温度](https://programmercarl.com/0739.每日温度.html)中已经详细讲解了。 本篇我侧重与说一说,如何处理循环数组。 @@ -32,7 +32,7 @@ 代码如下: -```C++ +```CPP // 版本一 class Solution { public: @@ -69,7 +69,7 @@ resize倒是不费时间,是O(1)的操作,但扩充nums数组相当于多了 代码如下: -```C++ +```CPP // 版本二 class Solution { public: @@ -95,6 +95,28 @@ public: ## 其他语言版本 Java: +```Java +class Solution { + public int[] nextGreaterElements(int[] nums) { + //边界判断 + if(nums == null || nums.length <= 1) { + return new int[]{-1}; + } + int size = nums.length; + int[] result = new int[size];//存放结果 + Arrays.fill(result,-1);//默认全部初始化为-1 + Stack st= new Stack<>();//栈中存放的是nums中的元素下标 + for(int i = 0; i < 2*size; i++) { + while(!st.empty() && nums[i % size] > nums[st.peek()]) { + result[st.peek()] = nums[i % size];//更新result + st.pop();//弹出栈顶 + } + st.push(i % size); + } + return result; + } +} +``` Python: ```python3 @@ -110,5 +132,50 @@ class Solution: return dp ``` Go: +```go +func nextGreaterElements(nums []int) []int { + length := len(nums) + result := make([]int,length,length) + for i:=0;i0&&nums[i%length]>nums[stack[len(stack)-1]]{ + index := stack[len(stack)-1] + stack = stack[:len(stack)-1] // pop + result[index] = nums[i%length] + } + stack = append(stack,i%length) + } + return result +} +``` JavaScript: + +```JS +/** + * @param {number[]} nums + * @return {number[]} + */ +var nextGreaterElements = function (nums) { + // let map = new Map(); + let stack = []; + let res = new Array(nums.length).fill(-1); + for (let i = 0; i < nums.length * 2; i++) { + while ( + stack.length && + nums[i % nums.length] > nums[stack[stack.length - 1]] + ) { + let index = stack.pop(); + res[index] = nums[i % nums.length]; + } + stack.push(i % nums.length); + } + + return res; +}; +``` +
diff --git a/problems/0509.斐波那契数.md b/problems/0509.斐波那契数.md index dddac899..7e4df26c 100644 --- a/problems/0509.斐波那契数.md +++ b/problems/0509.斐波那契数.md @@ -8,7 +8,7 @@ ## 509. 斐波那契数 -题目地址:https://leetcode-cn.com/problems/fibonacci-number/ +[力扣题目链接](https://leetcode-cn.com/problems/fibonacci-number/) 斐波那契数,通常用 F(n) 表示,形成的序列称为 斐波那契数列 。该数列由 0 和 1 开始,后面的每一项数字都是前面两项数字的和。也就是: F(0) = 0,F(1) = 1 @@ -29,7 +29,7 @@ F(n) = F(n - 1) + F(n - 2),其中 n > 1 输入:4 输出:3 解释:F(4) = F(3) + F(2) = 2 + 1 = 3 -  + 提示: * 0 <= n <= 30 @@ -47,7 +47,7 @@ F(n) = F(n - 1) + F(n - 2),其中 n > 1 对于动规,如果没有方法论的话,可能简单题目可以顺手一写就过,难一点就不知道如何下手了。 -所以我总结的动规五部曲,是要用来贯穿整个动态规划系列的,就像之前讲过[二叉树系列的递归三部曲](https://mp.weixin.qq.com/s/I6ZXFbw09NR31F5CJR_geQ),[回溯法系列的回溯三部曲](https://mp.weixin.qq.com/s/gjSgJbNbd1eAA5WkA-HeWw)一样。后面慢慢大家就会体会到,动规五部曲方法的重要性。 +所以我总结的动规五部曲,是要用来贯穿整个动态规划系列的,就像之前讲过[二叉树系列的递归三部曲](https://programmercarl.com/前序/通过一道面试题目,讲一讲递归算法的时间复杂度!.html),[回溯法系列的回溯三部曲](https://programmercarl.com/回溯算法理论基础.html)一样。后面慢慢大家就会体会到,动规五部曲方法的重要性。 ### 动态规划 @@ -88,7 +88,7 @@ dp[1] = 1; 以上我们用动规的方法分析完了,C++代码如下: -```C++ +```CPP class Solution { public: int fib(int N) { @@ -110,7 +110,7 @@ public: 代码如下: -```C++ +```CPP class Solution { public: int fib(int N) { @@ -137,7 +137,7 @@ public: 代码如下: -```C++ +```CPP class Solution { public: int fib(int N) { @@ -150,14 +150,14 @@ public: * 时间复杂度:O(2^n) * 空间复杂度:O(n) 算上了编程语言中实现递归的系统栈所占空间 -这个递归的时间复杂度大家画一下树形图就知道了,如果不清晰的同学,可以看这篇:[通过一道面试题目,讲一讲递归算法的时间复杂度!](https://mp.weixin.qq.com/s/I6ZXFbw09NR31F5CJR_geQ) +这个递归的时间复杂度大家画一下树形图就知道了,如果不清晰的同学,可以看这篇:[通过一道面试题目,讲一讲递归算法的时间复杂度!](https://programmercarl.com/前序/通过一道面试题目,讲一讲递归算法的时间复杂度!.html) # 总结 斐波那契数列这道题目是非常基础的题目,我在后面的动态规划的讲解中将会多次提到斐波那契数列! -这里我严格按照[关于动态规划,你该了解这些!](https://leetcode-cn.com/circle/article/tNuNnM/)中的动规五部曲来分析了这道题目,一些分析步骤可能同学感觉没有必要搞的这么复杂,代码其实上来就可以撸出来。 +这里我严格按照[关于动态规划,你该了解这些!](https://programmercarl.com/动态规划理论基础.html)中的动规五部曲来分析了这道题目,一些分析步骤可能同学感觉没有必要搞的这么复杂,代码其实上来就可以撸出来。 但我还是强调一下,简单题是用来掌握方法论的,动规五部曲将在接下来的动态规划讲解中发挥重要作用,敬请期待! @@ -167,7 +167,7 @@ public: -## 其他语言版本 +# 其他语言版本 Java: @@ -238,4 +238,4 @@ var fib = function(n) { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0513.找树左下角的值.md b/problems/0513.找树左下角的值.md index 17d15fde..d2c05fd8 100644 --- a/problems/0513.找树左下角的值.md +++ b/problems/0513.找树左下角的值.md @@ -7,7 +7,9 @@

欢迎大家参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!

-## 513.找树左下角的值 +# 513.找树左下角的值 + +[力扣题目链接](https://leetcode-cn.com/problems/find-bottom-left-tree-value/) 给定一个二叉树,在树的最后一行找到最左边的值。 @@ -19,7 +21,7 @@ ![513.找树左下角的值1](https://img-blog.csdnimg.cn/20210204153017586.png) -## 思路 +# 思路 本地要找出树的最后一行找到最左边的值。此时大家应该想起用层序遍历是非常简单的了,反而用递归的话会比较难一点。 @@ -37,7 +39,7 @@ 如果使用递归法,如何判断是最后一行呢,其实就是深度最大的叶子节点一定是最后一行。 -如果对二叉树深度和高度还有点疑惑的话,请看:[110.平衡二叉树](https://mp.weixin.qq.com/s/isUS-0HDYknmC0Rr4R8mww)。 +如果对二叉树深度和高度还有点疑惑的话,请看:[110.平衡二叉树](https://programmercarl.com/0110.平衡二叉树.html)。 所以要找深度最大的叶子节点。 @@ -53,7 +55,7 @@ 代码如下: -``` +```CPP int maxLen = INT_MIN; // 全局变量 记录最大深度 int maxleftValue; // 全局变量 最大深度最左节点的数值 void traversal(TreeNode* root, int leftLen) @@ -75,7 +77,7 @@ void traversal(TreeNode* root, int leftLen) 代码如下: -``` +```CPP if (root->left == NULL && root->right == NULL) { if (leftLen > maxLen) { maxLen = leftLen; // 更新最大深度 @@ -89,7 +91,7 @@ if (root->left == NULL && root->right == NULL) { 在找最大深度的时候,递归的过程中依然要使用回溯,代码如下: -```C++ +```CPP // 中 if (root->left) { // 左 leftLen++; // 深度加一 @@ -106,7 +108,7 @@ return; 完整代码如下: -```C++ +```CPP class Solution { public: int maxLen = INT_MIN; @@ -140,7 +142,7 @@ public: 当然回溯的地方可以精简,精简代码如下: -```C++ +```CPP class Solution { public: int maxLen = INT_MIN; @@ -168,7 +170,7 @@ public: }; ``` -如果对回溯部分精简的代码 不理解的话,可以看这篇[二叉树:找我的所有路径?](https://mp.weixin.qq.com/s/Osw4LQD2xVUnCJ-9jrYxJA)和[二叉树:以为使用了递归,其实还隐藏着回溯](https://mp.weixin.qq.com/s/ivLkHzWdhjQQD1rQWe6zWA) 。这两篇文章详细分析了回溯隐藏在了哪里。 +如果对回溯部分精简的代码 不理解的话,可以看这篇[257. 二叉树的所有路径](https://programmercarl.com/0257.二叉树的所有路径.html) ## 迭代法 @@ -177,11 +179,11 @@ public: 只需要记录最后一行第一个节点的数值就可以了。 -如果对层序遍历不了解,看这篇[二叉树:层序遍历登场!](https://mp.weixin.qq.com/s/Gb3BjakIKGNpup2jYtTzog),这篇里也给出了层序遍历的模板,稍作修改就一过刷了这道题了。 +如果对层序遍历不了解,看这篇[二叉树:层序遍历登场!](https://programmercarl.com/0102.二叉树的层序遍历.html),这篇里也给出了层序遍历的模板,稍作修改就一过刷了这道题了。 代码如下: -```C++ +```CPP class Solution { public: int findBottomLeftValue(TreeNode* root) { @@ -203,20 +205,20 @@ public: }; ``` -## 总结 +# 总结 本题涉及如下几点: -* 递归求深度的写法,我们在[110.平衡二叉树](https://mp.weixin.qq.com/s/isUS-0HDYknmC0Rr4R8mww)中详细的分析了深度应该怎么求,高度应该怎么求。 -* 递归中其实隐藏了回溯,在[二叉树:以为使用了递归,其实还隐藏着回溯](https://mp.weixin.qq.com/s/ivLkHzWdhjQQD1rQWe6zWA)中讲解了究竟哪里使用了回溯,哪里隐藏了回溯。 -* 层次遍历,在[二叉树:层序遍历登场!](https://mp.weixin.qq.com/s/Gb3BjakIKGNpup2jYtTzog)深度讲解了二叉树层次遍历。 +* 递归求深度的写法,我们在[110.平衡二叉树](https://programmercarl.com/0110.平衡二叉树.html)中详细的分析了深度应该怎么求,高度应该怎么求。 +* 递归中其实隐藏了回溯,在[257. 二叉树的所有路径](https://programmercarl.com/0257.二叉树的所有路径.html)中讲解了究竟哪里使用了回溯,哪里隐藏了回溯。 +* 层次遍历,在[二叉树:层序遍历登场!](https://programmercarl.com/0102.二叉树的层序遍历.html)深度讲解了二叉树层次遍历。 所以本题涉及到的点,我们之前都讲解过,这些知识点需要同学们灵活运用,这样就举一反三了。 -## 其他语言版本 +# 其他语言版本 -Java: +## Java ```java // 递归法 @@ -273,42 +275,60 @@ class Solution { -Python: +## Python + +递归: ```python -//递归法 -# Definition for a binary tree node. -# class TreeNode: -# def __init__(self, val=0, left=None, right=None): -# self.val = val -# self.left = left -# self.right = right class Solution: def findBottomLeftValue(self, root: TreeNode) -> int: - depth=0 - self.res=[] - def level(root,depth): - if not root:return - if depth==len(self.res): - self.res.append([]) - self.res[depth].append(root.val) - level(root.left,depth+1) - level(root.right,depth+1) - level(root,depth) - return self.res[-1][0] -``` -Go: + max_depth = -float("INF") + leftmost_val = 0 -> 递归法 + def __traverse(root, cur_depth): + nonlocal max_depth, leftmost_val + if not root.left and not root.right: + if cur_depth > max_depth: + max_depth = cur_depth + leftmost_val = root.val + if root.left: + cur_depth += 1 + __traverse(root.left, cur_depth) + cur_depth -= 1 + if root.right: + cur_depth += 1 + __traverse(root.right, cur_depth) + cur_depth -= 1 + + __traverse(root, 0) + return leftmost_val +``` + +迭代 - 层序遍历: +```python +class Solution: + def findBottomLeftValue(self, root: TreeNode) -> int: + queue = deque() + if root: + queue.append(root) + result = 0 + while queue: + q_len = len(queue) + for i in range(q_len): + if i == 0: + result = queue[i].val + cur = queue.popleft() + if cur.left: + queue.append(cur.left) + if cur.right: + queue.append(cur.right) + return result +``` + +## Go + +递归法: ```go -/** - * Definition for a binary tree node. - * type TreeNode struct { - * Val int - * Left *TreeNode - * Right *TreeNode - * } - */ var maxDeep int // 全局变量 深度 var value int //全局变量 最终值 func findBottomLeftValue(root *TreeNode) int { @@ -340,17 +360,9 @@ func findLeftValue (root *TreeNode,deep int){ } ``` -> 迭代法 +迭代法: ```go -/** - * Definition for a binary tree node. - * type TreeNode struct { - * Val int - * Left *TreeNode - * Right *TreeNode - * } - */ func findBottomLeftValue(root *TreeNode) int { queue:=list.New() var gradation int @@ -372,8 +384,10 @@ func findBottomLeftValue(root *TreeNode) int { } ``` -JavaScript: -1. 递归版本 +## JavaScript + +递归版本: + ```javascript var findBottomLeftValue = function(root) { //首先考虑递归遍历 前序遍历 找到最大深度的叶子节点即可 @@ -395,7 +409,8 @@ var findBottomLeftValue = function(root) { return resNode; }; ``` -2. 层序遍历 +层序遍历: + ```javascript var findBottomLeftValue = function(root) { //考虑层序遍历 记录最后一行的第一个节点 @@ -426,4 +441,4 @@ var findBottomLeftValue = function(root) { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0516.最长回文子序列.md b/problems/0516.最长回文子序列.md index 388d8d6a..89b5667f 100644 --- a/problems/0516.最长回文子序列.md +++ b/problems/0516.最长回文子序列.md @@ -7,7 +7,7 @@

欢迎大家参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!

## 516.最长回文子序列 -题目链接:https://leetcode-cn.com/problems/longest-palindromic-subsequence/ +[力扣题目链接](https://leetcode-cn.com/problems/longest-palindromic-subsequence/) 给定一个字符串 s ,找到其中最长的回文子序列,并返回该序列的长度。可以假设 s 的最大长度为 1000 。 @@ -29,7 +29,7 @@ ## 思路 -我们刚刚做过了 [动态规划:回文子串](https://mp.weixin.qq.com/s/2WetyP6IYQ6VotegepVpEw),求的是回文子串,而本题要求的是回文子序列, 要搞清楚这两者之间的区别。 +我们刚刚做过了 [动态规划:回文子串](https://programmercarl.com/0647.回文子串.html),求的是回文子串,而本题要求的是回文子序列, 要搞清楚这两者之间的区别。 **回文子串是要连续的,回文子序列可不是连续的!** 回文子串,回文子序列都是动态规划经典题目。 @@ -69,7 +69,7 @@ 代码如下: -```C++ +```CPP if (s[i] == s[j]) { dp[i][j] = dp[i + 1][j - 1] + 2; } else { @@ -85,7 +85,7 @@ if (s[i] == s[j]) { 其他情况dp[i][j]初始为0就行,这样递推公式:dp[i][j] = max(dp[i + 1][j], dp[i][j - 1]); 中dp[i][j]才不会被初始值覆盖。 -```C++ +```CPP vector> dp(s.size(), vector(s.size(), 0)); for (int i = 0; i < s.size(); i++) dp[i][i] = 1; ``` @@ -102,7 +102,7 @@ for (int i = 0; i < s.size(); i++) dp[i][i] = 1; 代码如下: -```C++ +```CPP for (int i = s.size() - 1; i >= 0; i--) { for (int j = i + 1; j < s.size(); j++) { if (s[i] == s[j]) { @@ -124,7 +124,7 @@ for (int i = s.size() - 1; i >= 0; i--) { 以上分析完毕,C++代码如下: -```C++ +```CPP class Solution { public: int longestPalindromeSubseq(string s) { @@ -243,4 +243,4 @@ const longestPalindromeSubseq = (s) => { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0518.零钱兑换II.md b/problems/0518.零钱兑换II.md index 3f907da9..6eb31cd8 100644 --- a/problems/0518.零钱兑换II.md +++ b/problems/0518.零钱兑换II.md @@ -9,7 +9,7 @@ ## 518. 零钱兑换 II -链接:https://leetcode-cn.com/problems/coin-change-2/ +[力扣题目链接](https://leetcode-cn.com/problems/coin-change-2/) 难度:中等 @@ -46,7 +46,8 @@ 这是一道典型的背包问题,一看到钱币数量不限,就知道这是一个完全背包。 -对完全背包还不了解的同学,可以看这篇:[动态规划:关于完全背包,你该了解这些!](https://mp.weixin.qq.com/s/akwyxlJ4TLvKcw26KB9uJw) + +对完全背包还不了解的同学,可以看这篇:[动态规划:关于完全背包,你该了解这些!](https://programmercarl.com/背包问题理论基础完全背包.html) 但本题和纯完全背包不一样,**纯完全背包是能否凑成总金额,而本题是要求凑成总金额的个数!** @@ -78,7 +79,7 @@ dp[j] (考虑coins[i]的组合总和) 就是所有的dp[j - coins[i]](不 所以递推公式:dp[j] += dp[j - coins[i]]; -**这个递推公式大家应该不陌生了,我在讲解01背包题目的时候在这篇[动态规划:目标和!](https://mp.weixin.qq.com/s/2pWmaohX75gwxvBENS-NCw)中就讲解了,求装满背包有几种方法,一般公式都是:dp[j] += dp[j - nums[i]];** +**这个递推公式大家应该不陌生了,我在讲解01背包题目的时候在这篇[动态规划:目标和!](https://programmercarl.com/0494.目标和.html)中就讲解了,求装满背包有几种方法,一般公式都是:dp[j] += dp[j - nums[i]];** 3. dp数组如何初始化 @@ -93,7 +94,7 @@ dp[j] (考虑coins[i]的组合总和) 就是所有的dp[j - coins[i]](不 本题中我们是外层for循环遍历物品(钱币),内层for遍历背包(金钱总额),还是外层for遍历背包(金钱总额),内层for循环遍历物品(钱币)呢? -我在[动态规划:关于完全背包,你该了解这些!](https://mp.weixin.qq.com/s/akwyxlJ4TLvKcw26KB9uJw)中讲解了完全背包的两个for循环的先后顺序都是可以的。 +我在[动态规划:关于完全背包,你该了解这些!](https://programmercarl.com/背包问题理论基础完全背包.html)中讲解了完全背包的两个for循环的先后顺序都是可以的。 **但本题就不行了!** @@ -111,7 +112,7 @@ dp[j] (考虑coins[i]的组合总和) 就是所有的dp[j - coins[i]](不 代码如下: -```C++ +```CPP for (int i = 0; i < coins.size(); i++) { // 遍历物品 for (int j = coins[i]; j <= amount; j++) { // 遍历背包容量 dp[j] += dp[j - coins[i]]; @@ -151,7 +152,7 @@ for (int j = 0; j <= amount; j++) { // 遍历背包容量 以上分析完毕,C++代码如下: -```C++ +```CPP class Solution { public: int change(int amount, vector& coins) { @@ -170,7 +171,7 @@ public: ## 总结 -本题的递推公式,其实我们在[动态规划:目标和!](https://mp.weixin.qq.com/s/2pWmaohX75gwxvBENS-NCw)中就已经讲过了,**而难点在于遍历顺序!** +本题的递推公式,其实我们在[动态规划:目标和!](https://programmercarl.com/0494.目标和.html)中就已经讲过了,**而难点在于遍历顺序!** 在求装满背包有几种方案的时候,认清遍历顺序是非常关键的。 @@ -265,4 +266,4 @@ const change = (amount, coins) => { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0530.二叉搜索树的最小绝对差.md b/problems/0530.二叉搜索树的最小绝对差.md index bf646443..b19a0dd2 100644 --- a/problems/0530.二叉搜索树的最小绝对差.md +++ b/problems/0530.二叉搜索树的最小绝对差.md @@ -9,9 +9,9 @@ > 利用二叉搜索树的特性搞起! -## 530.二叉搜索树的最小绝对差 +# 530.二叉搜索树的最小绝对差 -题目地址:https://leetcode-cn.com/problems/minimum-absolute-difference-in-bst/ +[力扣题目链接](https://leetcode-cn.com/problems/minimum-absolute-difference-in-bst/) 给你一棵所有节点为非负值的二叉搜索树,请你计算树中任意两节点的差的绝对值的最小值。 @@ -21,7 +21,7 @@ 提示:树中至少有 2 个节点。 -## 思路 +# 思路 题目中要求在二叉搜索树上任意两节点的差的绝对值的最小值。 @@ -39,7 +39,7 @@ 代码如下: -```C++ +```CPP class Solution { private: vector vec; @@ -75,7 +75,7 @@ public: 代码如下: -```C++ +```CPP class Solution { private: int result = INT_MAX; @@ -101,11 +101,11 @@ public: ## 迭代 -看过这两篇[二叉树:听说递归能做的,栈也能做!](https://mp.weixin.qq.com/s/c_zCrGHIVlBjUH_hJtghCg),[二叉树:前中后序迭代方式的写法就不能统一一下么?](https://mp.weixin.qq.com/s/WKg0Ty1_3SZkztpHubZPRg)文章之后,不难写出两种中序遍历的迭代法。 +看过这两篇[二叉树:听说递归能做的,栈也能做!](https://programmercarl.com/二叉树的迭代遍历.html),[二叉树:前中后序迭代方式的写法就不能统一一下么?](https://programmercarl.com/二叉树的统一迭代法.html)文章之后,不难写出两种中序遍历的迭代法。 下面我给出其中的一种中序遍历的迭代法,代码如下: -```C++ +```CPP class Solution { public: int getMinimumDifference(TreeNode* root) { @@ -132,7 +132,7 @@ public: }; ``` -## 总结 +# 总结 **遇到在二叉搜索树上求什么最值,求差值之类的,都要思考一下二叉搜索树可是有序的,要利用好这一特点。** @@ -142,15 +142,11 @@ public: +# 其他语言版本 +## Java - - -## 其他语言版本 - - -Java: 递归 ```java class Solution { @@ -175,38 +171,38 @@ class Solution { } } ``` -```Java +迭代法-中序遍历 + +```java class Solution { - TreeNode pre;// 记录上一个遍历的结点 - int result = Integer.MAX_VALUE; + TreeNode pre; + Stack stack; public int getMinimumDifference(TreeNode root) { - if (root == null) { - return result; + if (root == null) return 0; + stack = new Stack<>(); + TreeNode cur = root; + int result = Integer.MAX_VALUE; + while (cur != null || !stack.isEmpty()) { + if (cur != null) { + stack.push(cur); // 将访问的节点放进栈 + cur = cur.left; // 左 + }else { + cur = stack.pop(); + if (pre != null) { // 中 + result = Math.min(result, cur.val - pre.val); + } + pre = cur; + cur = cur.right; // 右 + } } - // 左 - int left = getMinimumDifference(root.left); - - // 中 - if (pre != null) { - result = Math.min(left, root.val - pre.val); - } - pre = root; - // 右 - int right = getMinimumDifference(root.right); - result = Math.min(right, result); return result; } } ``` +## Python -Python: +递归 ```python -# Definition for a binary tree node. -# class TreeNode: -# def __init__(self, val=0, left=None, right=None): -# self.val = val -# self.left = left -# self.right = right class Solution: def getMinimumDifference(self, root: TreeNode) -> int: res = [] @@ -222,8 +218,10 @@ class Solution: for i in range(len(res)-1): // 统计有序数组的最小差值 r = min(abs(res[i]-res[i+1]),r) return r - -# 迭代法-中序遍历 +``` + +迭代法-中序遍历 +```python class Solution: def getMinimumDifference(self, root: TreeNode) -> int: stack = [] @@ -242,19 +240,13 @@ class Solution: cur = cur.right return result -``` -Go: -> 中序遍历,然后计算最小差值 +``` + +## Go: + +中序遍历,然后计算最小差值 ```go -/** - * Definition for a binary tree node. - * type TreeNode struct { - * Val int - * Left *TreeNode - * Right *TreeNode - * } - */ func getMinimumDifference(root *TreeNode) int { var res []int findMIn(root,&res) @@ -299,8 +291,8 @@ func getMinimumDifference(root *TreeNode) int { } ``` -JavaScript版本 - +## JavaScript +递归 先转换为有序数组 ```javascript /** * Definition for a binary tree node. @@ -332,9 +324,50 @@ var getMinimumDifference = function (root) { return diff; }; ``` +递归 在递归的过程中更新最小值 +```js +var getMinimumDifference = function(root) { + let res = Infinity + let preNode = null + // 中序遍历 + const inorder = (node) => { + if(!node) return + inorder(node.left) + // 更新res + if(preNode) res = Math.min(res, node.val - preNode.val) + // 记录前一个节点 + preNode = node + inorder(node.right) + } + inorder(root) + return res +} +``` + +迭代 中序遍历 +```js +var getMinimumDifference = function(root) { + let stack = [] + let cur = root + let res = Infinity + let pre = null + while(cur || stack.length) { + if(cur) { + stack.push(cur) + cur = cur.left + } else { + cur = stack.pop() + if(pre) res = Math.min(res, cur.val - pre.val) + pre = cur + cur = cur.right + } + } + return res +} +``` ----------------------- * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0538.把二叉搜索树转换为累加树.md b/problems/0538.把二叉搜索树转换为累加树.md index 3ecb8195..24fc7211 100644 --- a/problems/0538.把二叉搜索树转换为累加树.md +++ b/problems/0538.把二叉搜索树转换为累加树.md @@ -7,9 +7,9 @@

欢迎大家参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!

-## 538.把二叉搜索树转换为累加树 +# 538.把二叉搜索树转换为累加树 -题目链接:https://leetcode-cn.com/problems/convert-bst-to-greater-tree/ +[力扣题目链接](https://leetcode-cn.com/problems/convert-bst-to-greater-tree/) 给出二叉 搜索 树的根节点,该树的节点值各不相同,请你将其转换为累加树(Greater Sum Tree),使每个节点 node 的新值等于原树中大于或等于 node.val 的值之和。 @@ -23,20 +23,20 @@ ![538.把二叉搜索树转换为累加树](https://img-blog.csdnimg.cn/20201023160751832.png) -输入:[4,1,6,0,2,5,7,null,null,null,3,null,null,null,8] -输出:[30,36,21,36,35,26,15,null,null,null,33,null,null,null,8] +* 输入:[4,1,6,0,2,5,7,null,null,null,3,null,null,null,8] +* 输出:[30,36,21,36,35,26,15,null,null,null,33,null,null,null,8] 示例 2: -输入:root = [0,null,1] -输出:[1,null,1] +* 输入:root = [0,null,1] +* 输出:[1,null,1] 示例 3: -输入:root = [1,0,2] -输出:[3,3,2] +* 输入:root = [1,0,2] +* 输出:[3,3,2] 示例 4: -输入:root = [3,2,4,1] -输出:[7,9,4,10] +* 输入:root = [3,2,4,1] +* 输出:[7,9,4,10] 提示: @@ -45,7 +45,7 @@ * 树中的所有值 互不相同 。 * 给定的树为二叉搜索树。 -## 思路 +# 思路 一看到累加树,相信很多小伙伴都会疑惑:如何累加?遇到一个节点,然后在遍历其他节点累加?怎么一想这么麻烦呢。 @@ -69,7 +69,7 @@ 本题依然需要一个pre指针记录当前遍历节点cur的前一个节点,这样才方便做累加。 -pre指针的使用技巧,我们在[二叉树:搜索树的最小绝对差](https://mp.weixin.qq.com/s/Hwzml6698uP3qQCC1ctUQQ)和[二叉树:我的众数是多少?](https://mp.weixin.qq.com/s/KSAr6OVQIMC-uZ8MEAnGHg)都提到了,这是常用的操作手段。 +pre指针的使用技巧,我们在[二叉树:搜索树的最小绝对差](https://programmercarl.com/0530.二叉搜索树的最小绝对差.html)和[二叉树:我的众数是多少?](https://programmercarl.com/0501.二叉搜索树中的众数.html)都提到了,这是常用的操作手段。 * 递归函数参数以及返回值 @@ -107,7 +107,7 @@ traversal(cur->left); // 左 递归法整体代码如下: -```C++ +```CPP class Solution { private: int pre; // 记录前一个节点的数值 @@ -129,11 +129,11 @@ public: ## 迭代法 -迭代法其实就是中序模板题了,在[二叉树:前中后序迭代法](https://mp.weixin.qq.com/s/c_zCrGHIVlBjUH_hJtghCg)和[二叉树:前中后序统一方式迭代法](https://mp.weixin.qq.com/s/WKg0Ty1_3SZkztpHubZPRg)可以选一种自己习惯的写法。 +迭代法其实就是中序模板题了,在[二叉树:前中后序迭代法](https://programmercarl.com/二叉树的迭代遍历.html)和[二叉树:前中后序统一方式迭代法](https://programmercarl.com/二叉树的统一迭代法.html)可以选一种自己习惯的写法。 这里我给出其中的一种,代码如下: -```C++ +```CPP class Solution { private: int pre; // 记录前一个节点的数值 @@ -162,17 +162,17 @@ public: }; ``` -## 总结 +# 总结 经历了前面各种二叉树增删改查的洗礼之后,这道题目应该比较简单了。 **好了,二叉树已经接近尾声了,接下来就是要对二叉树来一个大总结了**。 -## 其他语言版本 +# 其他语言版本 -Java: +## Java ```Java class Solution { int sum; @@ -195,15 +195,10 @@ class Solution { } ``` -Python: -```python3 -# Definition for a binary tree node. -# class TreeNode: -# def __init__(self, val=0, left=None, right=None): -# self.val = val -# self.left = left -# self.right = right -#递归法 +## Python + +递归法 +```python class Solution: def convertBST(self, root: TreeNode) -> TreeNode: def buildalist(root): @@ -216,10 +211,10 @@ class Solution: buildalist(root) return root ``` -Go: +## Go -> 弄一个sum暂存其和值 +弄一个sum暂存其和值 ```go //右中左 @@ -239,23 +234,10 @@ func RightMLeft(root *TreeNode,sum *int) *TreeNode { } ``` -JavaScript版本 - -> 递归 +## JavaScript +递归 ```javascript -/** - * Definition for a binary tree node. - * function TreeNode(val, left, right) { - * this.val = (val===undefined ? 0 : val) - * this.left = (left===undefined ? null : left) - * this.right = (right===undefined ? null : right) - * } - */ -/** - * @param {TreeNode} root - * @return {TreeNode} - */ var convertBST = function(root) { let pre = 0; const ReverseInOrder = (cur) => { @@ -271,21 +253,8 @@ var convertBST = function(root) { }; ``` -> 迭代 - +迭代 ```javascript -/** - * Definition for a binary tree node. - * function TreeNode(val, left, right) { - * this.val = (val===undefined ? 0 : val) - * this.left = (left===undefined ? null : left) - * this.right = (right===undefined ? null : right) - * } - */ -/** - * @param {TreeNode} root - * @return {TreeNode} - */ var convertBST = function (root) { let pre = 0; let cur = root; @@ -308,4 +277,4 @@ var convertBST = function (root) { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0541.反转字符串II.md b/problems/0541.反转字符串II.md index 47e85161..df2fade8 100644 --- a/problems/0541.反转字符串II.md +++ b/problems/0541.反转字符串II.md @@ -12,7 +12,7 @@ # 541. 反转字符串II -https://leetcode-cn.com/problems/reverse-string-ii/ +[力扣题目链接](https://leetcode-cn.com/problems/reverse-string-ii/) 给定一个字符串 s 和一个整数 k,你需要对从字符串开头算起的每隔 2k 个字符的前 k 个字符进行反转。 @@ -46,7 +46,7 @@ https://leetcode-cn.com/problems/reverse-string-ii/ 使用C++库函数reverse的版本如下: -```C++ +```CPP class Solution { public: string reverseStr(string s, int k) { @@ -65,11 +65,11 @@ public: }; ``` -那么我们也可以实现自己的reverse函数,其实和题目[344. 反转字符串](https://mp.weixin.qq.com/s/_rNm66OJVl92gBDIbGpA3w)道理是一样的。 +那么我们也可以实现自己的reverse函数,其实和题目[344. 反转字符串](https://programmercarl.com/0344.反转字符串.html)道理是一样的。 下面我实现的reverse函数区间是左闭右闭区间,代码如下: -```C++ +```CPP class Solution { public: void reverse(string& s, int start, int end) { @@ -152,37 +152,58 @@ class Solution { } } ``` +```java +// 解法3 +class Solution { + public String reverseStr(String s, int k) { + char[] ch = s.toCharArray(); + // 1. 每隔 2k 个字符的前 k 个字符进行反转 + for (int i = 0; i< ch.length; i += 2 * k) { + // 2. 剩余字符小于 2k 但大于或等于 k 个,则反转前 k 个字符 + if (i + k <= ch.length) { + reverse(ch, i, i + k -1); + continue; + } + // 3. 剩余字符少于 k 个,则将剩余字符全部反转 + reverse(ch, i, ch.length - 1); + } + return new String(ch); + } + // 定义翻转函数 + public void reverse(char[] ch, int i, int j) { + for (; i < j; i++, j--) { + char temp = ch[i]; + ch[i] = ch[j]; + ch[j] = temp; + } + + } +} +``` Python: ```python - -class Solution(object): - def reverseStr(self, s, k): +class Solution: + def reverseStr(self, s: str, k: int) -> str: """ - :type s: str - :type k: int - :rtype: str + 1. 使用range(start, end, step)来确定需要调换的初始位置 + 2. 对于字符串s = 'abc',如果使用s[0:999] ===> 'abc'。字符串末尾如果超过最大长度,则会返回至字符串最后一个值,这个特性可以避免一些边界条件的处理。 + 3. 用切片整体替换,而不是一个个替换. """ - from functools import reduce - # turn s into a list - s = list(s) - - # another way to simply use a[::-1], but i feel this is easier to understand - def reverse(s): - left, right = 0, len(s) - 1 + def reverse_substring(text): + left, right = 0, len(text) - 1 while left < right: - s[left], s[right] = s[right], s[left] + text[left], text[right] = text[right], text[left] left += 1 right -= 1 - return s + return text - # make sure we reverse each 2k elements - for i in range(0, len(s), 2*k): - s[i:(i+k)] = reverse(s[i:(i+k)]) - - # combine list into str. - return reduce(lambda a, b: a+b, s) + res = list(s) + + for cur in range(0, len(s), 2 * k): + res[cur: cur + k] = reverse_substring(res[cur: cur + k]) + return ''.join(res) ``` @@ -233,10 +254,32 @@ var reverseStr = function(s, k) { ``` +Swift: + +```swift +func reverseStr(_ s: String, _ k: Int) -> String { + var ch = Array(s) + + for i in stride(from: 0, to: ch.count, by: 2 * k) { + var left = i + var right = min(s.count - 1, left + k - 1) + + while left < right { + (ch[left], ch[right]) = (ch[right], ch[left]) + left += 1 + right -= 1 + } + } + return String(ch) +} +``` + + + ----------------------- * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0583.两个字符串的删除操作.md b/problems/0583.两个字符串的删除操作.md index 9aef2310..91b07ca9 100644 --- a/problems/0583.两个字符串的删除操作.md +++ b/problems/0583.两个字符串的删除操作.md @@ -8,7 +8,7 @@ ## 583. 两个字符串的删除操作 -题目链接:https://leetcode-cn.com/problems/delete-operation-for-two-strings/ +[力扣题目链接](https://leetcode-cn.com/problems/delete-operation-for-two-strings/) 给定两个单词 word1 和 word2,找到使得 word1 和 word2 相同所需的最小步数,每步可以删除任意一个字符串中的一个字符。 @@ -20,7 +20,7 @@ ## 思路 -本题和[动态规划:115.不同的子序列](https://mp.weixin.qq.com/s/1SULY2XVSROtk_hsoVLu8A)相比,其实就是两个字符串可以都可以删除了,情况虽说复杂一些,但整体思路是不变的。 +本题和[动态规划:115.不同的子序列](https://programmercarl.com/0115.不同的子序列.html)相比,其实就是两个字符串可以都可以删除了,情况虽说复杂一些,但整体思路是不变的。 这次是两个字符串可以相互删了,这种题目也知道用动态规划的思路来解,动规五部曲,分析如下: @@ -56,7 +56,7 @@ dp[i][0]:word2为空字符串,以i-1为结尾的字符串word2要删除多 dp[0][j]的话同理,所以代码如下: -```C++ +```CPP vector> dp(word1.size() + 1, vector(word2.size() + 1)); for (int i = 0; i <= word1.size(); i++) dp[i][0] = i; for (int j = 0; j <= word2.size(); j++) dp[0][j] = j; @@ -78,7 +78,7 @@ for (int j = 0; j <= word2.size(); j++) dp[0][j] = j; 以上分析完毕,代码如下: -```C++ +```CPP class Solution { public: int minDistance(string word1, string word2) { @@ -147,8 +147,38 @@ class Solution: ``` Go: +```go +func minDistance(word1 string, word2 string) int { + dp := make([][]int, len(word1)+1) + for i := 0; i < len(dp); i++ { + dp[i] = make([]int, len(word2)+1) + } + //初始化 + for i := 0; i < len(dp); i++ { + dp[i][0] = i + } + for j := 0; j < len(dp[0]); j++ { + dp[0][j] = j + } + for i := 1; i < len(dp); i++ { + for j := 1; j < len(dp[i]); j++ { + if word1[i-1] == word2[j-1] { + dp[i][j] = dp[i-1][j-1] + } else { + dp[i][j] = min(min(dp[i-1][j]+1, dp[i][j-1]+1), dp[i-1][j-1]+2) + } + } + } + return dp[len(dp)-1][len(dp[0])-1] +} - +func min(a, b int) int { + if a < b { + return a + } + return b +} +``` Javascript: ```javascript const minDistance = (word1, word2) => { @@ -181,4 +211,4 @@ const minDistance = (word1, word2) => { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0617.合并二叉树.md b/problems/0617.合并二叉树.md index 19b58bd3..e21efcb3 100644 --- a/problems/0617.合并二叉树.md +++ b/problems/0617.合并二叉树.md @@ -7,9 +7,9 @@

欢迎大家参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!

-## 617.合并二叉树 +# 617.合并二叉树 -题目地址:https://leetcode-cn.com/problems/merge-two-binary-trees/ +[力扣题目链接](https://leetcode-cn.com/problems/merge-two-binary-trees/) 给定两个二叉树,想象当你将它们中的一个覆盖到另一个上时,两个二叉树的一些节点便会重叠。 @@ -21,7 +21,7 @@ 注意: 合并必须从两个树的根节点开始。 -## 思路 +# 思路 相信这道题目很多同学疑惑的点是如何同时遍历两个二叉树呢? @@ -90,7 +90,7 @@ return t1; 此时前序遍历,完整代码就写出来了,如下: -```C++ +```CPP class Solution { public: TreeNode* mergeTrees(TreeNode* t1, TreeNode* t2) { @@ -107,7 +107,7 @@ public: 那么中序遍历也是可以的,代码如下: -```C++ +```CPP class Solution { public: TreeNode* mergeTrees(TreeNode* t1, TreeNode* t2) { @@ -124,7 +124,7 @@ public: 后序遍历依然可以,代码如下: -```C++ +```CPP class Solution { public: TreeNode* mergeTrees(TreeNode* t1, TreeNode* t2) { @@ -145,7 +145,7 @@ public: 不修改输入树的结构,前序遍历,代码如下: -```C++ +```CPP class Solution { public: TreeNode* mergeTrees(TreeNode* t1, TreeNode* t2) { @@ -165,11 +165,11 @@ public: 使用迭代法,如何同时处理两棵树呢? -思路我们在[二叉树:我对称么?](https://mp.weixin.qq.com/s/Kgf0gjvlDlNDfKIH2b1Oxg)中的迭代法已经讲过一次了,求二叉树对称的时候就是把两个树的节点同时加入队列进行比较。 +思路我们在[二叉树:我对称么?](https://programmercarl.com/0101.对称二叉树.html)中的迭代法已经讲过一次了,求二叉树对称的时候就是把两个树的节点同时加入队列进行比较。 本题我们也使用队列,模拟的层序遍历,代码如下: -```C++ +```CPP class Solution { public: TreeNode* mergeTrees(TreeNode* t1, TreeNode* t2) { @@ -209,14 +209,14 @@ public: }; ``` -## 拓展 +# 拓展 当然也可以秀一波指针的操作,这是我写的野路子,大家就随便看看就行了,以防带跑遍了。 如下代码中,想要更改二叉树的值,应该传入指向指针的指针。 代码如下:(前序遍历) -```C++ +```CPP class Solution { public: void process(TreeNode** t1, TreeNode** t2) { @@ -241,21 +241,21 @@ public: }; ``` -## 总结 +# 总结 合并二叉树,也是二叉树操作的经典题目,如果没有接触过的话,其实并不简单,因为我们习惯了操作一个二叉树,一起操作两个二叉树,还会有点懵懵的。 -这不是我们第一次操作两颗二叉树了,在[二叉树:我对称么?](https://mp.weixin.qq.com/s/Kgf0gjvlDlNDfKIH2b1Oxg)中也一起操作了两棵二叉树。 +这不是我们第一次操作两颗二叉树了,在[二叉树:我对称么?](https://programmercarl.com/0101.对称二叉树.html)中也一起操作了两棵二叉树。 迭代法中,一般一起操作两个树都是使用队列模拟类似层序遍历,同时处理两个树的节点,这种方式最好理解,如果用模拟递归的思路的话,要复杂一些。 最后拓展中,我给了一个操作指针的野路子,大家随便看看就行了,如果学习C++的话,可以在去研究研究。 -## 其他语言版本 +# 其他语言版本 -Java: +## Java ```Java class Solution { @@ -274,7 +274,7 @@ class Solution { ```Java class Solution { - // 迭代 + // 使用栈迭代 public TreeNode mergeTrees(TreeNode root1, TreeNode root2) { if (root1 == null) { return root2; @@ -310,8 +310,47 @@ class Solution { } } ``` +```java +class Solution { + // 使用队列迭代 + public TreeNode mergeTrees(TreeNode root1, TreeNode root2) { + if (root1 == null) return root2; + if (root2 ==null) return root1; + Queue queue = new LinkedList<>(); + queue.offer(root1); + queue.offer(root2); + while (!queue.isEmpty()) { + TreeNode node1 = queue.poll(); + TreeNode node2 = queue.poll(); + // 此时两个节点一定不为空,val相加 + node1.val = node1.val + node2.val; + // 如果两棵树左节点都不为空,加入队列 + if (node1.left != null && node2.left != null) { + queue.offer(node1.left); + queue.offer(node2.left); + } + // 如果两棵树右节点都不为空,加入队列 + if (node1.right != null && node2.right != null) { + queue.offer(node1.right); + queue.offer(node2.right); + } + // 若node1的左节点为空,直接赋值 + if (node1.left == null && node2.left != null) { + node1.left = node2.left; + } + // 若node2的左节点为空,直接赋值 + if (node1.right == null && node2.right != null) { + node1.right = node2.right; + } + } + return root1; + } +} +``` -Python: +## Python + +**递归法 - 前序遍历** ```python # Definition for a binary tree node. # class TreeNode: @@ -319,44 +358,60 @@ Python: # self.val = val # self.left = left # self.right = right -# 递归法*前序遍历 class Solution: def mergeTrees(self, root1: TreeNode, root2: TreeNode) -> TreeNode: - if not root1: return root2 // 如果t1为空,合并之后就应该是t2 - if not root2: return root1 // 如果t2为空,合并之后就应该是t1 - root1.val = root1.val + root2.val //中 - root1.left = self.mergeTrees(root1.left , root2.left) //左 - root1.right = self.mergeTrees(root1.right , root2.right) //右 - return root1 //root1修改了结构和数值 + # 递归终止条件: + # 但凡有一个节点为空, 就立刻返回另外一个. 如果另外一个也为None就直接返回None. + if not root1: + return root2 + if not root2: + return root1 + # 上面的递归终止条件保证了代码执行到这里root1, root2都非空. + root1.val += root2.val # 中 + root1.left = self.mergeTrees(root1.left, root2.left) #左 + root1.right = self.mergeTrees(root1.right, root2.right) # 右 + + return root1 # ⚠️ 注意: 本题我们重复使用了题目给出的节点而不是创建新节点. 节省时间, 空间. -# 迭代法-覆盖原来的树 -class Solution: - def mergeTrees(self, root1: TreeNode, root2: TreeNode) -> TreeNode: - if not root1: return root2 - if not root2: return root1 - # 迭代,将树2覆盖到树1 - queue1 = [root1] - queue2 = [root2] - root = root1 - while queue1 and queue2: - root1 = queue1.pop(0) - root2 = queue2.pop(0) - root1.val += root2.val - if not root1.left: # 如果树1左儿子不存在,则覆盖后树1的左儿子为树2的左儿子 - root1.left = root2.left - elif root1.left and root2.left: - queue1.append(root1.left) - queue2.append(root2.left) - - if not root1.right: # 同理,处理右儿子 - root1.right = root2.right - elif root1.right and root2.right: - queue1.append(root1.right) - queue2.append(root2.right) - return root ``` -Go: +**迭代法** +```python +class Solution: + def mergeTrees(self, root1: TreeNode, root2: TreeNode) -> TreeNode: + if not root1: + return root2 + if not root2: + return root1 + + queue = deque() + queue.append(root1) + queue.append(root2) + + while queue: + node1 = queue.popleft() + node2 = queue.popleft() + # 更新queue + # 只有两个节点都有左节点时, 再往queue里面放. + if node1.left and node2.left: + queue.append(node1.left) + queue.append(node2.left) + # 只有两个节点都有右节点时, 再往queue里面放. + if node1.right and node2.right: + queue.append(node1.right) + queue.append(node2.right) + + # 更新当前节点. 同时改变当前节点的左右孩子. + node1.val += node2.val + if not node1.left and node2.left: + node1.left = node2.left + if not node1.right and node2.right: + node1.right = node2.right + + return root1 +``` + +## Go ```go /** @@ -408,9 +463,49 @@ func mergeTrees(root1 *TreeNode, root2 *TreeNode) *TreeNode { root1.Right = mergeTrees(root1.Right, root2.Right) return root1 } + +// 迭代版本 +func mergeTrees(root1 *TreeNode, root2 *TreeNode) *TreeNode { + queue := make([]*TreeNode,0) + if root1 == nil{ + return root2 + } + if root2 == nil{ + return root1 + } + queue = append(queue,root1) + queue = append(queue,root2) + + for size:=len(queue);size>0;size=len(queue){ + node1 := queue[0] + queue = queue[1:] + node2 := queue[0] + queue = queue[1:] + node1.Val += node2.Val + // 左子树都不为空 + if node1.Left != nil && node2.Left != nil{ + queue = append(queue,node1.Left) + queue = append(queue,node2.Left) + } + // 右子树都不为空 + if node1.Right !=nil && node2.Right !=nil{ + queue = append(queue,node1.Right) + queue = append(queue,node2.Right) + } + // 树 1 的左子树为 nil,直接接上树 2 的左子树 + if node1.Left == nil{ + node1.Left = node2.Left + } + // 树 1 的右子树为 nil,直接接上树 2 的右子树 + if node1.Right == nil{ + node1.Right = node2.Right + } + } + return root1 +} ``` -JavaScript: +## JavaScript ```javascript /** @@ -446,4 +541,4 @@ var mergeTrees = function (root1, root2) { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0647.回文子串.md b/problems/0647.回文子串.md index d3b734ae..7b90ca0c 100644 --- a/problems/0647.回文子串.md +++ b/problems/0647.回文子串.md @@ -8,7 +8,7 @@ ## 647. 回文子串 -题目链接:https://leetcode-cn.com/problems/palindromic-substrings/ +[力扣题目链接](https://leetcode-cn.com/problems/palindromic-substrings/) 给定一个字符串,你的任务是计算这个字符串中有多少个回文子串。 @@ -61,7 +61,7 @@ 以上三种情况分析完了,那么递归公式如下: -```C++ +```CPP if (s[i] == s[j]) { if (j - i <= 1) { // 情况一 和 情况二 result++; @@ -101,7 +101,7 @@ dp[i + 1][j - 1] 在 dp[i][j]的左下角,如图: 代码如下: -```C++ +```CPP for (int i = s.size() - 1; i >= 0; i--) { // 注意遍历顺序 for (int j = i; j < s.size(); j++) { if (s[i] == s[j]) { @@ -129,7 +129,7 @@ for (int i = s.size() - 1; i >= 0; i--) { // 注意遍历顺序 以上分析完毕,C++代码如下: -```C++ +```CPP class Solution { public: int countSubstrings(string s) { @@ -154,7 +154,7 @@ public: ``` 以上代码是为了凸显情况一二三,当然是可以简洁一下的,如下: -```C++ +```CPP class Solution { public: int countSubstrings(string s) { @@ -192,7 +192,7 @@ public: **这两种情况可以放在一起计算,但分别计算思路更清晰,我倾向于分别计算**,代码如下: -```C++ +```CPP class Solution { public: int countSubstrings(string s) { @@ -412,4 +412,4 @@ const countSubstrings = (s) => { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0649.Dota2参议院.md b/problems/0649.Dota2参议院.md index 696124d6..e2900824 100644 --- a/problems/0649.Dota2参议院.md +++ b/problems/0649.Dota2参议院.md @@ -10,6 +10,9 @@ # 649. Dota2 参议院 +[力扣题目链接](https://leetcode-cn.com/problems/dota2-senate/) + + Dota2 的世界里有两个阵营:Radiant(天辉)和 Dire(夜魇) Dota2 参议院由来自两派的参议员组成。现在参议院希望对一个 Dota2 游戏里的改变作出决定。他们以一个基于轮为过程的投票进行。在每一轮中,每一位参议员都可以行使两项权利中的一项: @@ -67,7 +70,7 @@ Dota2 参议院由来自两派的参议员组成。现在参议院希望对一 局部最优可以退出全局最优,举不出反例,那么试试贪心。 -如果对贪心算法理论基础还不了解的话,可以看看这篇:[关于贪心算法,你该了解这些!](https://mp.weixin.qq.com/s/O935TaoHE9Eexwe_vSbRAg) ,相信看完之后对贪心就有基本的了解了。 +如果对贪心算法理论基础还不了解的话,可以看看这篇:[关于贪心算法,你该了解这些!](https://programmercarl.com/贪心算法理论基础.html) ,相信看完之后对贪心就有基本的了解了。 # 代码实现 @@ -78,7 +81,7 @@ Dota2 参议院由来自两派的参议员组成。现在参议院希望对一 C++代码如下: -```C++ +```CPP class Solution { public: string predictPartyVictory(string senate) { @@ -115,16 +118,102 @@ public: ## Java ```java +class Solution { + public String predictPartyVictory(String senateStr) { + // R = true表示本轮循环结束后,字符串里依然有R。D同理 + Boolean R = true, D = true; + // 当flag大于0时,R在D前出现,R可以消灭D。当flag小于0时,D在R前出现,D可以消灭R + int flag = 0; + byte[] senate = senateStr.getBytes(); + while (R && D) { // 一旦R或者D为false,就结束循环,说明本轮结束后只剩下R或者D了 + R = false; + D = false; + for (int i = 0; i < senate.length; i++) { + if (senate[i] == 'R') { + if (flag < 0) senate[i] = 0; // 消灭R,R此时为false + else R = true; // 如果没被消灭,本轮循环结束有R + flag++; + } + if (senate[i] == 'D') { + if (flag > 0) senate[i] = 0; + else D = true; + flag--; + } + } + } + // 循环结束之后,R和D只能有一个为true + return R == true ? "Radiant" : "Dire"; + } +} ``` ## Python ```python +class Solution: + def predictPartyVictory(self, senate: str) -> str: + # R = true表示本轮循环结束后,字符串里依然有R。D同理 + R , D = True, True + + # 当flag大于0时,R在D前出现,R可以消灭D。当flag小于0时,D在R前出现,D可以消灭R + flag = 0 + + senate = list(senate) + while R and D: # 一旦R或者D为false,就结束循环,说明本轮结束后只剩下R或者D了 + R = False + D = False + for i in range(len(senate)) : + if senate[i] == 'R' : + if flag < 0: senate[i] = '0' # 消灭R,R此时为false + else: R = True # 如果没被消灭,本轮循环结束有R + flag += 1 + if senate[i] == 'D': + if flag > 0: senate[i] = '0' + else: D = True + flag -= 1 + # 循环结束之后,R和D只能有一个为true + return "Radiant" if R else "Dire" ``` ## Go ```go + +func predictPartyVictory(senateStr string) string { + // R = true表示本轮循环结束后,字符串里依然有R。D同理 + R, D := true, true + // 当flag大于0时,R在D前出现,R可以消灭D。当flag小于0时,D在R前出现,D可以消灭R + flag := 0 + + senate := []byte(senateStr) + for R && D { // 一旦R或者D为false,就结束循环,说明本轮结束后只剩下R或者D了 + R = false + D = false + for i := 0; i < len(senate); i++ { + if senate[i] == 'R' { + if flag < 0 { + senate[i] = 0 // 消灭R,R此时为false + } else { + R = true // 如果没被消灭,本轮循环结束有R + } + flag++; + } + if (senate[i] == 'D') { + if flag > 0 { + senate[i] = 0 + } else { + D = true + } + flag-- + } + } + } + // 循环结束之后,R和D只能有一个为true + if R { + return "Radiant" + } + return "Dire"; +} ``` ## JavaScript @@ -136,5 +225,5 @@ public: * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0654.最大二叉树.md b/problems/0654.最大二叉树.md index 4e1e7a72..b644a0a3 100644 --- a/problems/0654.最大二叉树.md +++ b/problems/0654.最大二叉树.md @@ -7,9 +7,9 @@

欢迎大家参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!

-## 654.最大二叉树 +# 654.最大二叉树 -题目地址:https://leetcode-cn.com/problems/maximum-binary-tree/ +[力扣题目地址](https://leetcode-cn.com/problems/maximum-binary-tree/) 给定一个不含重复元素的整数数组。一个以此数组构建的最大二叉树定义如下: @@ -27,7 +27,7 @@ 给定的数组的大小在 [1, 1000] 之间。 -## 思路 +# 思路 最大二叉树的构建过程如下: @@ -41,7 +41,7 @@ 代码如下: -``` +```CPP TreeNode* constructMaximumBinaryTree(vector& nums) ``` @@ -53,7 +53,7 @@ TreeNode* constructMaximumBinaryTree(vector& nums) 代码如下: -``` +```CPP TreeNode* node = new TreeNode(0); if (nums.size() == 1) {     node->val = nums[0]; @@ -68,7 +68,7 @@ if (nums.size() == 1) { 1. 先要找到数组中最大的值和对应的下表, 最大的值构造根节点,下表用来下一步分割数组。 代码如下: -``` +```CPP int maxValue = 0; int maxValueIndex = 0; for (int i = 0; i < nums.size(); i++) { @@ -86,7 +86,7 @@ node->val = maxValue; 这里要判断maxValueIndex > 0,因为要保证左区间至少有一个数值。 代码如下: -``` +```CPP if (maxValueIndex > 0) {     vector newVec(nums.begin(), nums.begin() + maxValueIndex);     node->left = constructMaximumBinaryTree(newVec); @@ -99,7 +99,7 @@ if (maxValueIndex > 0) { 代码如下: -``` +```CPP if (maxValueIndex < (nums.size() - 1)) {     vector newVec(nums.begin() + maxValueIndex + 1, nums.end());     node->right = constructMaximumBinaryTree(newVec); @@ -107,7 +107,7 @@ if (maxValueIndex < (nums.size() - 1)) { ``` 这样我们就分析完了,整体代码如下:(详细注释) -```C++ +```CPP class Solution { public: TreeNode* constructMaximumBinaryTree(vector& nums) { @@ -143,11 +143,11 @@ public: 以上代码比较冗余,效率也不高,每次还要切割的时候每次都要定义新的vector(也就是数组),但逻辑比较清晰。 -和文章[二叉树:构造二叉树登场!](https://mp.weixin.qq.com/s/7r66ap2s-shvVvlZxo59xg)中一样的优化思路,就是每次分隔不用定义新的数组,而是通过下表索引直接在原数组上操作。 +和文章[二叉树:构造二叉树登场!](https://programmercarl.com/0106.从中序与后序遍历序列构造二叉树.html)中一样的优化思路,就是每次分隔不用定义新的数组,而是通过下表索引直接在原数组上操作。 优化后代码如下: -```C++ +```CPP class Solution { private: // 在左闭右开区间[left, right),构造二叉树 @@ -177,12 +177,12 @@ public: }; ``` -## 拓展 +# 拓展 可以发现上面的代码看上去简洁一些,**主要是因为第二版其实是允许空节点进入递归,所以不用在递归的时候加判断节点是否为空** 第一版递归过程:(加了if判断,为了不让空节点进入递归) -```C++ +```CPP if (maxValueIndex > 0) { // 这里加了判断是为了不让空节点进入递归 vector newVec(nums.begin(), nums.begin() + maxValueIndex); @@ -209,10 +209,10 @@ root->right = traversal(nums, maxValueIndex + 1, right); 第二版相应的终止条件,是遇到空节点,也就是数组区间为0,就终止了。 +# 总结 -## 总结 -这道题目其实和 [二叉树:构造二叉树登场!](https://mp.weixin.qq.com/s/7r66ap2s-shvVvlZxo59xg) 是一个思路,比[二叉树:构造二叉树登场!](https://mp.weixin.qq.com/s/7r66ap2s-shvVvlZxo59xg) 还简单一些。 +这道题目其实和 [二叉树:构造二叉树登场!](https://programmercarl.com/0106.从中序与后序遍历序列构造二叉树.html) 是一个思路,比[二叉树:构造二叉树登场!](https://programmercarl.com/0106.从中序与后序遍历序列构造二叉树.html) 还简单一些。 **注意类似用数组构造二叉树的题目,每次分隔尽量不要定义新的数组,而是通过下表索引直接在原数组上操作,这样可以节约时间和空间上的开销。** @@ -220,10 +220,10 @@ root->right = traversal(nums, maxValueIndex + 1, right); 其实就是不同代码风格的实现,**一般情况来说:如果让空节点(空指针)进入递归,就不加if,如果不让空节点进入递归,就加if限制一下, 终止条件也会相应的调整。** -## 其他语言版本 +# 其他语言版本 -Java: +## Java ```Java class Solution { @@ -255,30 +255,38 @@ class Solution { } ``` -Python: +## Python + ```python -# Definition for a binary tree node. -# class TreeNode: -# def __init__(self, val=0, left=None, right=None): -# self.val = val -# self.left = left -# self.right = right -//递归法 class Solution: + """最大二叉树 递归法""" + def constructMaximumBinaryTree(self, nums: List[int]) -> TreeNode: - if not nums: return None //终止条件 - root = TreeNode(max(nums)) //新建节点 - p = nums.index(root.val) //找到最大值位置 - if p > 0: //保证有左子树 - root.left = self.constructMaximumBinaryTree(nums[:p]) //递归 - if p < len(nums): //保证有右子树 - root.right = self.constructMaximumBinaryTree(nums[p+1:]) //递归 + return self.traversal(nums, 0, len(nums)) + + def traversal(self, nums: List[int], begin: int, end: int) -> TreeNode: + # 列表长度为0时返回空节点 + if begin == end: + return None + + # 找到最大的值和其对应的下标 + max_index = begin + for i in range(begin, end): + if nums[i] > nums[max_index]: + max_index = i + + # 构建当前节点 + root = TreeNode(nums[max_index]) + + # 递归构建左右子树 + root.left = self.traversal(nums, begin, max_index) + root.right = self.traversal(nums, max_index + 1, end) + return root ``` -Go: +## Go -> 654. 最大二叉树 ```go /** @@ -311,7 +319,7 @@ func findMax(nums []int) (index int){ } ``` -JavaScript版本 +## JavaScript ```javascript /** @@ -354,4 +362,4 @@ var constructMaximumBinaryTree = function (nums) { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0657.机器人能否返回原点.md b/problems/0657.机器人能否返回原点.md index cd26836c..ffa5d6f2 100644 --- a/problems/0657.机器人能否返回原点.md +++ b/problems/0657.机器人能否返回原点.md @@ -9,7 +9,7 @@ # 657. 机器人能否返回原点 -题目地址:https://leetcode-cn.com/problems/robot-return-to-origin/ +[力扣题目链接](https://leetcode-cn.com/problems/robot-return-to-origin/) 在二维平面上,有一个机器人从原点 (0, 0) 开始。给出它的移动顺序,判断这个机器人在完成移动后是否在 (0, 0) 处结束。 @@ -48,7 +48,7 @@ C++代码如下: -```C++ +```CPP class Solution { public: bool judgeCircle(string moves) { @@ -71,26 +71,89 @@ public: ## Java ```java +// 时间复杂度:O(n) +// 空间复杂度:如果采用 toCharArray,则是 O(n);如果使用 charAt,则是 O(1) +class Solution { + public boolean judgeCircle(String moves) { + int x = 0; + int y = 0; + for (char c : moves.toCharArray()) { + if (c == 'U') y++; + if (c == 'D') y--; + if (c == 'L') x++; + if (c == 'R') x--; + } + return x == 0 && y == 0; + } +} ``` ## Python ```python +# 时间复杂度:O(n) +# 空间复杂度:O(1) +class Solution: + def judgeCircle(self, moves: str) -> bool: + x = 0 # 记录当前位置 + y = 0 + for i in range(len(moves)): + if (moves[i] == 'U'): + y += 1 + if (moves[i] == 'D'): + y -= 1 + if (moves[i] == 'L'): + x += 1 + if (moves[i] == 'R'): + x -= 1 + return x == 0 and y == 0 ``` ## Go ```go +func judgeCircle(moves string) bool { + x := 0 + y := 0 + for i := 0; i < len(moves); i++ { + if moves[i] == 'U' { + y++ + } + if moves[i] == 'D' { + y-- + } + if moves[i] == 'L' { + x++ + } + if moves[i] == 'R' { + x-- + } + } + return x == 0 && y == 0; +} ``` ## JavaScript ```js +// 时间复杂度:O(n) +// 空间复杂度:O(1) +var judgeCircle = function(moves) { + var x = 0; // 记录当前位置 + var y = 0; + for (var i = 0; i < moves.length; i++) { + if (moves[i] == 'U') y++; + if (moves[i] == 'D') y--; + if (moves[i] == 'L') x++; + if (moves[i] == 'R') x--; + } + return x == 0 && y == 0; +}; ``` ----------------------- * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0669.修剪二叉搜索树.md b/problems/0669.修剪二叉搜索树.md index 26394eaa..ef6a3e00 100644 --- a/problems/0669.修剪二叉搜索树.md +++ b/problems/0669.修剪二叉搜索树.md @@ -10,9 +10,9 @@ > 如果不对递归有深刻的理解,本题有点难 > 单纯移除一个节点那还不够,要修剪! -## 669. 修剪二叉搜索树 +# 669. 修剪二叉搜索树 -题目链接:https://leetcode-cn.com/problems/trim-a-binary-search-tree/ +[力扣题目链接](https://leetcode-cn.com/problems/trim-a-binary-search-tree/) 给定一个二叉搜索树,同时给定最小边界L 和最大边界 R。通过修剪二叉搜索树,使得所有节点的值在[L, R]中 (R>=L) 。你可能需要改变树的根节点,所以结果应当返回修剪好的二叉搜索树的新的根节点。 @@ -20,7 +20,7 @@ ![669.修剪二叉搜索树1](https://img-blog.csdnimg.cn/20201014173219142.png) -## 思路 +# 思路 相信看到这道题目大家都感觉是一道简单题(事实上leetcode上也标明是简单)。 @@ -32,7 +32,7 @@ 不难写出如下代码: -```C++ +```CPP class Solution { public: TreeNode* trimBST(TreeNode* root, int low, int high) { @@ -71,7 +71,7 @@ public: 但是有返回值,更方便,可以通过递归函数的返回值来移除节点。 -这样的做法在[二叉树:搜索树中的插入操作](https://mp.weixin.qq.com/s/lwKkLQcfbCNX2W-5SOeZEA)和[二叉树:搜索树中的删除操作](https://mp.weixin.qq.com/s/-p-Txvch1FFk3ygKLjPAKw)中大家已经了解过了。 +这样的做法在[二叉树:搜索树中的插入操作](https://programmercarl.com/0701.二叉搜索树中的插入操作.html)和[二叉树:搜索树中的删除操作](https://programmercarl.com/0450.删除二叉搜索树中的节点.html)中大家已经了解过了。 代码如下: @@ -145,7 +145,7 @@ root->left = trimBST(root->left, low, high); 最后整体代码如下: -```C++ +```CPP class Solution { public: TreeNode* trimBST(TreeNode* root, int low, int high) { @@ -167,7 +167,7 @@ public: 精简之后代码如下: -```C++ +```CPP class Solution { public: TreeNode* trimBST(TreeNode* root, int low, int high) { @@ -195,7 +195,7 @@ public: 代码如下: -```C++ +```CPP class Solution { public: TreeNode* trimBST(TreeNode* root, int L, int R) { @@ -228,7 +228,7 @@ public: }; ``` -## 总结 +# 总结 修剪二叉搜索树其实并不难,但在递归法中大家可看出我费了很大的功夫来讲解如何删除节点的,这个思路其实是比较绕的。 @@ -238,10 +238,10 @@ public: 本题我依然给出递归法和迭代法,初学者掌握递归就可以了,如果想进一步学习,就把迭代法也写一写。 -## 其他语言版本 +# 其他语言版本 -Java: +## Java ```Java class Solution { @@ -264,16 +264,10 @@ class Solution { ``` -Python: +## Python ```python3 -# Definition for a binary tree node. -# class TreeNode: -# def __init__(self, val=0, left=None, right=None): -# self.val = val -# self.left = left -# self.right = right class Solution: def trimBST(self, root: TreeNode, low: int, high: int) -> TreeNode: if not root: return root @@ -285,17 +279,12 @@ class Solution: root.right = self.trimBST(root.right,low,high) // root->right接入符合条件的右孩子 return root ``` -Go: + +## Go + ```go -/** - * Definition for a binary tree node. - * type TreeNode struct { - * Val int - * Left *TreeNode - * Right *TreeNode - * } - */ +// 递归 func trimBST(root *TreeNode, low int, high int) *TreeNode { if root==nil{ return nil @@ -312,10 +301,43 @@ func trimBST(root *TreeNode, low int, high int) *TreeNode { root.Right=trimBST(root.Right,low,high) return root } + +// 迭代 +func trimBST(root *TreeNode, low int, high int) *TreeNode { + if root == nil { + return nil + } + // 处理 root,让 root 移动到[low, high] 范围内,注意是左闭右闭 + for root != nil && (root.Valhigh){ + if root.Val < low{ + root = root.Right + }else{ + root = root.Left + } + } + cur := root + // 此时 root 已经在[low, high] 范围内,处理左孩子元素小于 low 的情况(左节点是一定小于 root.Val,因此天然小于 high) + for cur != nil{ + for cur.Left!=nil && cur.Left.Val < low{ + cur.Left = cur.Left.Right + } + cur = cur.Left + } + cur = root + // 此时 root 已经在[low, high] 范围内,处理右孩子大于 high 的情况 + for cur != nil{ + for cur.Right!=nil && cur.Right.Val > high{ + cur.Right = cur.Right.Left + } + cur = cur.Right + } + return root +} ``` -JavaScript版本: +## JavaScript版本 + 迭代: ```js var trimBST = function(root, low, high) { @@ -373,4 +395,4 @@ var trimBST = function (root,low,high) { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0673.最长递增子序列的个数.md b/problems/0673.最长递增子序列的个数.md index da0be740..b3907e0e 100644 --- a/problems/0673.最长递增子序列的个数.md +++ b/problems/0673.最长递增子序列的个数.md @@ -9,6 +9,10 @@ # 673.最长递增子序列的个数 + +[力扣题目链接](https://leetcode-cn.com/problems/number-of-longest-increasing-subsequence/) + + 给定一个未排序的整数数组,找到最长递增子序列的个数。 示例 1: @@ -58,7 +62,7 @@ if (nums[i] > nums[j]) dp[i] = max(dp[i], dp[j] + 1); 代码如下: -```C++ +```CPP if (nums[i] > nums[j]) { if (dp[j] + 1 > dp[i]) { count[i] = count[j]; @@ -71,7 +75,7 @@ if (nums[i] > nums[j]) { 当然也可以这么写: -```C++ +```CPP if (nums[i] > nums[j]) { if (dp[j] + 1 > dp[i]) { dp[i] = dp[j] + 1; // 更新dp[i]放在这里,就不用max了 @@ -88,7 +92,7 @@ if (nums[i] > nums[j]) { 代码如下: -```C++ +```CPP for (int i = 1; i < nums.size(); i++) { for (int j = 0; j < i; j++) { if (nums[i] > nums[j]) { @@ -131,7 +135,7 @@ dp[i] 是由0到i-1各个位置的最长升序子序列 推导而来,那么遍 j其实就是0到i-1,遍历i的循环里外层,遍历j则在内层,代码如下: -```C++ +```CPP for (int i = 1; i < nums.size(); i++) { for (int j = 0; j < i; j++) { if (nums[i] > nums[j]) { @@ -152,7 +156,7 @@ for (int i = 1; i < nums.size(); i++) { 代码如下: -```C++ +```CPP for (int i = 1; i < nums.size(); i++) { for (int j = 0; j < i; j++) { if (nums[i] > nums[j]) { @@ -184,7 +188,7 @@ for (int i = 0; i < nums.size(); i++) { 以上分析完毕,C++整体代码如下: -```C++ +```CPP class Solution { public: int findNumberOfLIS(vector& nums) { @@ -224,16 +228,110 @@ public: ## Java ```java +class Solution { + public int findNumberOfLIS(int[] nums) { + if (nums.length <= 1) return nums.length; + int[] dp = new int[nums.length]; + for(int i = 0; i < dp.length; i++) dp[i] = 1; + int[] count = new int[nums.length]; + for(int i = 0; i < count.length; i++) count[i] = 1; + + int maxCount = 0; + for (int i = 1; i < nums.length; i++) { + for (int j = 0; j < i; j++) { + if (nums[i] > nums[j]) { + if (dp[j] + 1 > dp[i]) { + dp[i] = dp[j] + 1; + count[i] = count[j]; + } else if (dp[j] + 1 == dp[i]) { + count[i] += count[j]; + } + } + if (dp[i] > maxCount) maxCount = dp[i]; + } + } + int result = 0; + for (int i = 0; i < nums.length; i++) { + if (maxCount == dp[i]) result += count[i]; + } + return result; + } +} ``` ## Python ```python +class Solution: + def findNumberOfLIS(self, nums: List[int]) -> int: + size = len(nums) + if size<= 1: return size + + dp = [1 for i in range(size)] + count = [1 for i in range(size)] + + maxCount = 0 + for i in range(1, size): + for j in range(i): + if nums[i] > nums[j]: + if dp[j] + 1 > dp[i] : + dp[i] = dp[j] + 1 + count[i] = count[j] + elif dp[j] + 1 == dp[i] : + count[i] += count[j] + if dp[i] > maxCount: + maxCount = dp[i]; + result = 0 + for i in range(size): + if maxCount == dp[i]: + result += count[i] + return result; ``` ## Go ```go + +func findNumberOfLIS(nums []int) int { + size := len(nums) + if size <= 1 { + return size + } + + dp := make([]int, size); + for i, _ := range dp { + dp[i] = 1 + } + count := make([]int, size); + for i, _ := range count { + count[i] = 1 + } + + maxCount := 0 + for i := 1; i < size; i++ { + for j := 0; j < i; j++ { + if nums[i] > nums[j] { + if dp[j] + 1 > dp[i] { + dp[i] = dp[j] + 1 + count[i] = count[j] + } else if dp[j] + 1 == dp[i] { + count[i] += count[j] + } + } + if dp[i] > maxCount { + maxCount = dp[i] + } + } + } + + result := 0 + for i := 0; i < size; i++ { + if maxCount == dp[i] { + result += count[i] + } + } + return result +} ``` ## JavaScript @@ -245,5 +343,5 @@ public: * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0674.最长连续递增序列.md b/problems/0674.最长连续递增序列.md index 614bff72..3f3b5e6f 100644 --- a/problems/0674.最长连续递增序列.md +++ b/problems/0674.最长连续递增序列.md @@ -8,7 +8,7 @@ ## 674. 最长连续递增序列 -题目链接:https://leetcode-cn.com/problems/longest-continuous-increasing-subsequence/ +[力扣题目链接](https://leetcode-cn.com/problems/longest-continuous-increasing-subsequence/) 给定一个未经排序的整数数组,找到最长且 连续递增的子序列,并返回该序列的长度。 @@ -24,7 +24,7 @@ 输入:nums = [2,2,2,2,2] 输出:1 解释:最长连续递增序列是 [2], 长度为1。 -  + 提示: * 0 <= nums.length <= 10^4 @@ -33,7 +33,7 @@ ## 思路 -本题相对于昨天的[动态规划:300.最长递增子序列](https://mp.weixin.qq.com/s/f8nLO3JGfgriXep_gJQpqQ)最大的区别在于“连续”。 +本题相对于昨天的[动态规划:300.最长递增子序列](https://programmercarl.com/0300.最长上升子序列.html)最大的区别在于“连续”。 本题要求的是最长**连续**递增序列 @@ -53,7 +53,7 @@ 即:dp[i + 1] = dp[i] + 1; -**注意这里就体现出和[动态规划:300.最长递增子序列](https://mp.weixin.qq.com/s/f8nLO3JGfgriXep_gJQpqQ)的区别!** +**注意这里就体现出和[动态规划:300.最长递增子序列](https://programmercarl.com/0300.最长上升子序列.html)的区别!** 因为本题要求连续递增子序列,所以就必要比较nums[i + 1]与nums[i],而不用去比较nums[j]与nums[i] (j是在0到i之间遍历)。 @@ -73,7 +73,7 @@ 本文在确定递推公式的时候也说明了为什么本题只需要一层for循环,代码如下: -```C++ +```CPP for (int i = 0; i < nums.size() - 1; i++) { if (nums[i + 1] > nums[i]) { // 连续记录 dp[i + 1] = dp[i] + 1; // 递推公式 @@ -91,7 +91,7 @@ for (int i = 0; i < nums.size() - 1; i++) { 以上分析完毕,C++代码如下: -```C++ +```CPP class Solution { public: int findLengthOfLCIS(vector& nums) { @@ -118,7 +118,7 @@ public: 代码如下: -```C++ +```CPP class Solution { public: int findLengthOfLCIS(vector& nums) { @@ -144,7 +144,7 @@ public: 本题也是动规里子序列问题的经典题目,但也可以用贪心来做,大家也会发现贪心好像更简单一点,而且空间复杂度仅是O(1)。 -在动规分析中,关键是要理解和[动态规划:300.最长递增子序列](https://mp.weixin.qq.com/s/f8nLO3JGfgriXep_gJQpqQ)的区别。 +在动规分析中,关键是要理解和[动态规划:300.最长递增子序列](https://programmercarl.com/0300.最长上升子序列.html)的区别。 **要联动起来,才能理解递增子序列怎么求,递增连续子序列又要怎么求**。 @@ -268,4 +268,4 @@ const findLengthOfLCIS = (nums) => { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0684.冗余连接.md b/problems/0684.冗余连接.md index 9215af5f..c9eb33c4 100644 --- a/problems/0684.冗余连接.md +++ b/problems/0684.冗余连接.md @@ -37,7 +37,7 @@ 这里整理出我的并查集模板如下: -```C++ +```CPP int n = 1005; // 节点数量3 到 1000 int father[1005]; @@ -88,7 +88,7 @@ bool same(int u, int v) { 并查集C++代码如下: -```C++ +```CPP class Solution { private: int n = 1005; // 节点数量3 到 1000 @@ -161,7 +161,6 @@ public: * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
@@ -169,3 +168,4 @@ public: +
diff --git a/problems/0685.冗余连接II.md b/problems/0685.冗余连接II.md index b744c831..68d0376e 100644 --- a/problems/0685.冗余连接II.md +++ b/problems/0685.冗余连接II.md @@ -11,7 +11,7 @@ # 685.冗余连接II -题目地址:https://leetcode-cn.com/problems/redundant-connection-ii/ +[力扣题目链接](https://leetcode-cn.com/problems/redundant-connection-ii/) 在本问题中,有根树指满足以下条件的 有向 图。该树只有一个根节点,所有其他节点都是该根节点的后继。该树除了根节点之外的每一个节点都有且只有一个父节点,而根节点没有父节点。 @@ -58,7 +58,7 @@ 首先先计算节点的入度,代码如下: -```C++ +```CPP int inDegree[N] = {0}; // 记录节点入度 n = edges.size(); // 边的数量 for (int i = 0; i < n; i++) { @@ -70,7 +70,7 @@ for (int i = 0; i < n; i++) { 代码如下: -```C++ +```CPP vector vec; // 记录入度为2的边(如果有的话就两条边) // 找入度为2的节点所对应的边,注意要倒叙,因为优先返回最后出现在二维数组中的答案 for (int i = n - 1; i >= 0; i--) { @@ -112,7 +112,7 @@ vector getRemoveEdge(const vector>& edges) 本题C++代码如下:(详细注释了) -```C++ +```CPP class Solution { private: static const int N = 1010; // 如题:二维数组大小的在3到1000范围内 @@ -224,6 +224,6 @@ public: * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0700.二叉搜索树中的搜索.md b/problems/0700.二叉搜索树中的搜索.md index d6899ac5..c25ea12f 100644 --- a/problems/0700.二叉搜索树中的搜索.md +++ b/problems/0700.二叉搜索树中的搜索.md @@ -7,9 +7,9 @@

欢迎大家参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!

-## 700.二叉搜索树中的搜索 +# 700.二叉搜索树中的搜索 -题目地址:https://leetcode-cn.com/problems/search-in-a-binary-search-tree/ +[力扣题目地址](https://leetcode-cn.com/problems/search-in-a-binary-search-tree/) 给定二叉搜索树(BST)的根节点和一个值。 你需要在BST中找到节点值等于给定值的节点。 返回以该节点为根的子树。 如果节点不存在,则返回 NULL。 @@ -19,11 +19,11 @@ 在上述示例中,如果要找的值是 5,但因为没有节点值为 5,我们应该返回 NULL。 -## 思路 +# 思路 之前我们讲了都是普通二叉树,那么接下来看看二叉搜索树。 -在[关于二叉树,你该了解这些!](https://mp.weixin.qq.com/s/_ymfWYvTNd2GvWvC5HOE4A)中,我们已经讲过了二叉搜索树。 +在[关于二叉树,你该了解这些!](https://programmercarl.com/二叉树理论基础.html)中,我们已经讲过了二叉搜索树。 二叉搜索树是一个有序树: @@ -73,13 +73,13 @@ return NULL; 这里可能会疑惑,在递归遍历的时候,什么时候直接return 递归函数的返回值,什么时候不用加这个 return呢。 -我们在[二叉树:递归函数究竟什么时候需要返回值,什么时候不要返回值?](https://mp.weixin.qq.com/s/6TWAVjxQ34kVqROWgcRFOg)中讲了,如果要搜索一条边,递归函数就要加返回值,这里也是一样的道理。 +我们在[二叉树:递归函数究竟什么时候需要返回值,什么时候不要返回值?](https://programmercarl.com/0112.路径总和.html)中讲了,如果要搜索一条边,递归函数就要加返回值,这里也是一样的道理。 **因为搜索到目标节点了,就要立即return了,这样才是找到节点就返回(搜索某一条边),如果不加return,就是遍历整棵树了。** 整体代码如下: -```C++ +```CPP class Solution { public: TreeNode* searchBST(TreeNode* root, int val) { @@ -109,7 +109,7 @@ public: 所以迭代法代码如下: -```C++ +```CPP class Solution { public: TreeNode* searchBST(TreeNode* root, int val) { @@ -125,7 +125,7 @@ public: 第一次看到了如此简单的迭代法,是不是感动的痛哭流涕,哭一会~ -## 总结 +# 总结 本篇我们介绍了二叉搜索树的遍历方式,因为二叉搜索树的有序性,遍历的时候要比普通二叉树简单很多。 @@ -138,9 +138,9 @@ public: -## 其他语言版本 +# 其他语言版本 -Java: +## Java ```Java class Solution { @@ -207,22 +207,25 @@ class Solution { } ``` -Python: +## Python 递归法: ```python -# Definition for a binary tree node. -# class TreeNode: -# def __init__(self, val=0, left=None, right=None): -# self.val = val -# self.left = left -# self.right = right class Solution: def searchBST(self, root: TreeNode, val: int) -> TreeNode: - if not root or root.val == val: return root //为空或者已经找到都是直接返回root,所以合并了 - if root.val > val: return self.searchBST(root.left,val) //注意一定要加return - else: return self.searchBST(root.right,val) + # 为什么要有返回值: + # 因为搜索到目标节点就要立即return, + # 这样才是找到节点就返回(搜索某一条边),如果不加return,就是遍历整棵树了。 + + if not root or root.val == val: + return root + + if root.val > val: + return self.searchBST(root.left, val) + + if root.val < val: + return self.searchBST(root.right, val) ``` @@ -239,19 +242,11 @@ class Solution: ``` -Go: +## Go -> 递归法 +递归法: ```go -/** - * Definition for a binary tree node. - * type TreeNode struct { - * Val int - * Left *TreeNode - * Right *TreeNode - * } - */ //递归法 func searchBST(root *TreeNode, val int) *TreeNode { if root==nil||root.Val==val{ @@ -264,17 +259,9 @@ func searchBST(root *TreeNode, val int) *TreeNode { } ``` -> 迭代法 +迭代法: ```go -/** - * Definition for a binary tree node. - * type TreeNode struct { - * Val int - * Left *TreeNode - * Right *TreeNode - * } - */ //迭代法 func searchBST(root *TreeNode, val int) *TreeNode { for root!=nil{ @@ -290,9 +277,9 @@ func searchBST(root *TreeNode, val int) *TreeNode { } ``` -JavaScript版本 +## JavaScript -> 递归 +递归: ```javascript /** @@ -318,9 +305,9 @@ var searchBST = function (root, val) { return searchBST(root.right, val); return null; }; -``` +``` -> 迭代 +迭代: ```javascript /** @@ -355,4 +342,4 @@ var searchBST = function (root, val) { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0701.二叉搜索树中的插入操作.md b/problems/0701.二叉搜索树中的插入操作.md index 61027453..d5b9eb3f 100644 --- a/problems/0701.二叉搜索树中的插入操作.md +++ b/problems/0701.二叉搜索树中的插入操作.md @@ -7,9 +7,9 @@

欢迎大家参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!

-## 701.二叉搜索树中的插入操作 +# 701.二叉搜索树中的插入操作 -链接:https://leetcode-cn.com/problems/insert-into-a-binary-search-tree/ +[力扣题目链接](https://leetcode-cn.com/problems/insert-into-a-binary-search-tree/) 给定二叉搜索树(BST)的根节点和要插入树中的值,将值插入二叉搜索树。 返回插入后二叉搜索树的根节点。 输入数据保证,新值和原始二叉搜索树中的任意节点值都不同。 @@ -24,7 +24,7 @@ * -10^8 <= val <= 10^8 * 新值和原始二叉搜索树中的任意节点值都不同 -## 思路 +# 思路 其实这道题目其实是一道简单题目,**但是题目中的提示:有多种有效的插入方式,还可以重构二叉搜索树,一下子吓退了不少人**,瞬间感觉题目复杂了很多。 @@ -96,7 +96,7 @@ return root; 整体代码如下: -```C++ +```CPP class Solution { public: TreeNode* insertIntoBST(TreeNode* root, int val) { @@ -126,7 +126,7 @@ void traversal(TreeNode* cur, int val) 代码如下: -```C++ +```CPP class Solution { private: TreeNode* parent; @@ -164,15 +164,15 @@ public: ## 迭代 -再来看看迭代法,对二叉搜索树迭代写法不熟悉,可以看这篇:[二叉树:二叉搜索树登场!](https://mp.weixin.qq.com/s/vsKrWRlETxCVsiRr8v_hHg) +再来看看迭代法,对二叉搜索树迭代写法不熟悉,可以看这篇:[二叉树:二叉搜索树登场!](https://programmercarl.com/0700.二叉搜索树中的搜索.html) 在迭代法遍历的过程中,需要记录一下当前遍历的节点的父节点,这样才能做插入节点的操作。 -在[二叉树:搜索树的最小绝对差](https://mp.weixin.qq.com/s/Hwzml6698uP3qQCC1ctUQQ)和[二叉树:我的众数是多少?](https://mp.weixin.qq.com/s/KSAr6OVQIMC-uZ8MEAnGHg)中,都是用了记录pre和cur两个指针的技巧,本题也是一样的。 +在[二叉树:搜索树的最小绝对差](https://programmercarl.com/0530.二叉搜索树的最小绝对差.html)和[二叉树:我的众数是多少?](https://programmercarl.com/0501.二叉搜索树中的众数.html)中,都是用了记录pre和cur两个指针的技巧,本题也是一样的。 代码如下: -```C++ +```CPP class Solution { public: TreeNode* insertIntoBST(TreeNode* root, int val) { @@ -195,7 +195,7 @@ public: }; ``` -## 总结 +# 总结 首先在二叉搜索树中的插入操作,大家不用恐惧其重构搜索树,其实根本不用重构。 @@ -204,9 +204,10 @@ public: 最后依然给出了迭代的方法,迭代的方法就需要记录当前遍历节点的父节点了,这个和没有返回值的递归函数实现的代码逻辑是一样的。 -## 其他语言版本 +# 其他语言版本 + +## Java -Java: ```java class Solution { public TreeNode insertIntoBST(TreeNode root, int val) { @@ -253,9 +254,9 @@ class Solution { } ``` -Python: +## Python -递归法 +**递归法** - 有返回值 ```python class Solution: @@ -267,10 +268,68 @@ class Solution: if root.val > val: root.left = self.insertIntoBST(root.left, val) # 递归创建左子树 return root +``` + +**递归法** - 无返回值 +```python +class Solution: + def insertIntoBST(self, root: TreeNode, val: int) -> TreeNode: + if not root: + return TreeNode(val) + parent = None + def __traverse(cur: TreeNode, val: int) -> None: + # 在函数运行的同时把新节点插入到该被插入的地方. + nonlocal parent + if not cur: + new_node = TreeNode(val) + if parent.val < val: + parent.right = new_node + else: + parent.left = new_node + return + + parent = cur # 重点: parent的作用只有运行到上面if not cur:才会发挥出来. + if cur.val < val: + __traverse(cur.right, val) + else: + __traverse(cur.left, val) + return + __traverse(root, val) + return root +``` + +**迭代法** +与无返回值的递归函数的思路大体一致 +```python +class Solution: + def insertIntoBST(self, root: TreeNode, val: int) -> TreeNode: + if not root: + return TreeNode(val) + parent = None + cur = root + + # 用while循环不断地找新节点的parent + while cur: + if cur.val < val: + parent = cur + cur = cur.right + elif cur.val > val: + parent = cur + cur = cur.left + + # 运行到这意味着已经跳出上面的while循环, + # 同时意味着新节点的parent已经被找到. + # parent已被找到, 新节点已经ready. 把两个节点黏在一起就好了. + if parent.val > val: + parent.left = TreeNode(val) + else: + parent.right = TreeNode(val) + + return root + ``` - -Go: +## Go 递归法 @@ -287,8 +346,10 @@ func insertIntoBST(root *TreeNode, val int) *TreeNode { } return root } -``` -迭代法 +``` + +迭代法 + ```go func insertIntoBST(root *TreeNode, val int) *TreeNode { if root == nil { @@ -314,9 +375,9 @@ func insertIntoBST(root *TreeNode, val int) *TreeNode { } ``` -JavaScript版本 +## JavaScript -> 有返回值的递归写法 +有返回值的递归写法 ```javascript /** @@ -348,7 +409,7 @@ var insertIntoBST = function (root, val) { }; ``` -> 无返回值的递归 +无返回值的递归 ```javascript /** @@ -388,7 +449,7 @@ var insertIntoBST = function (root, val) { }; ``` -> 迭代 +迭代 ```javascript /** @@ -431,4 +492,4 @@ var insertIntoBST = function (root, val) { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0704.二分查找.md b/problems/0704.二分查找.md index cce471ae..f358d2be 100644 --- a/problems/0704.二分查找.md +++ b/problems/0704.二分查找.md @@ -9,7 +9,7 @@ ## 704. 二分查找 -题目链接:https://leetcode-cn.com/problems/binary-search/ +[力扣题目链接](https://leetcode-cn.com/problems/binary-search/) 给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target  ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。 @@ -63,7 +63,7 @@ 代码如下:(详细注释) -```C++ +```CPP // 版本一 class Solution { public: @@ -102,7 +102,7 @@ public: 代码如下:(详细注释) -```C++ +```CPP // 版本二 class Solution { public: @@ -139,7 +139,7 @@ public: ## 相关题目推荐 -* [35.搜索插入位置](./0035.搜索插入位置.md) +* [35.搜索插入位置](https://programmercarl.com/0035.搜索插入位置.html) * 34.在排序数组中查找元素的第一个和最后一个位置 * 69.x 的平方根 * 367.有效的完全平方数 @@ -235,8 +235,6 @@ class Solution: return -1 ``` - - **Go:** (版本一)左闭右闭区间 @@ -279,7 +277,7 @@ func search(nums []int, target int) int { } ``` -**javaScript:** +**JavaScript:** ```js @@ -316,12 +314,204 @@ var search = function(nums, target) { ``` +**Ruby:** +```ruby +# (版本一)左闭右闭区间 +def search(nums, target) + left, right = 0, nums.length - 1 + while left <= right # 由于定义target在一个在左闭右闭的区间里,因此极限情况下存在left==right + middle = (left + right) / 2 + if nums[middle] > target + right = middle - 1 + elsif nums[middle] < target + left = middle + 1 + else + return middle # return兼具返回与跳出循环的作用 + end + end + -1 +end +# (版本二)左闭右开区间 + +def search(nums, target) + left, right = 0, nums.length + while left < right # 由于定义target在一个在左闭右开的区间里,因此极限情况下right=left+1 + middle = (left + right) / 2 + if nums[middle] > target + right = middle + elsif nums[middle] < target + left = middle + 1 + else + return middle + end + end + -1 +end +``` + +**Swift:** + +```swift +// (版本一)左闭右闭区间 +func search(nums: [Int], target: Int) -> Int { + // 1. 先定义区间。这里的区间是[left, right] + var left = 0 + var right = nums.count - 1 + + while left <= right {// 因为taeget是在[left, right]中,包括两个边界值,所以这里的left == right是有意义的 + // 2. 计算区间中间的下标(如果left、right都比较大的情况下,left + right就有可能会溢出) + // let middle = (left + right) / 2 + // 防溢出: + let middle = left + (right - left) / 2 + + // 3. 判断 + if target < nums[middle] { + // 当目标在区间左侧,就需要更新右边的边界值,新区间为[left, middle - 1] + right = middle - 1 + } else if target > nums[middle] { + // 当目标在区间右侧,就需要更新左边的边界值,新区间为[middle + 1, right] + left = middle + 1 + } else { + // 当目标就是在中间,则返回中间值的下标 + return middle + } + } + + // 如果找不到目标,则返回-1 + return -1 +} + +// (版本二)左闭右开区间 +func search(nums: [Int], target: Int) -> Int { + var left = 0 + var right = nums.count + + while left < right { + let middle = left + ((right - left) >> 1) + + if target < nums[middle] { + right = middle + } else if target > nums[middle] { + left = middle + 1 + } else { + return middle + } + } + + return -1 +} + +``` + +**Rust:** + +```rust +# (版本一)左闭右闭区间 + +impl Solution { + pub fn search(nums: Vec, target: i32) -> i32 { + let mut left:usize = 0; + let mut right:usize = nums.len() - 1; + while left as i32 <= right as i32{ + let mid = (left + right) / 2; + if nums[mid] < target { + left = mid + 1; + } else if nums[mid] > target { + right = mid - 1; + } else { + return mid as i32; + } + } + -1 + } +} + +# (版本二)左闭右开区间 + +impl Solution { + pub fn search(nums: Vec, target: i32) -> i32 { + let mut left:usize = 0; + let mut right:usize = nums.len(); + while left < right { + let mid = (left + right) / 2; + if nums[mid] < target { + left = mid + 1; + } else if nums[mid] > target { + right = mid; + } else { + return mid as i32; + } + } + -1 + } +} +``` + +**C:** +```c +int search(int* nums, int numsSize, int target){ + int left = 0; + int right = numsSize-1; + int middle = 0; + //若left小于等于right,说明区间中元素不为0 + while(left<=right) { + //更新查找下标middle的值 + middle = (left+right)/2; + //此时target可能会在[left,middle-1]区间中 + if(nums[middle] > target) { + right = middle-1; + } + //此时target可能会在[middle+1,right]区间中 + else if(nums[middle] < target) { + left = middle+1; + } + //当前下标元素等于target值时,返回middle + else if(nums[middle] == target){ + return middle; + } + } + //若未找到target元素,返回-1 + return -1; +} +``` + +**PHP:** +```php +// 左闭右闭区间 +class Solution { + /** + * @param Integer[] $nums + * @param Integer $target + * @return Integer + */ + function search($nums, $target) { + if (count($nums) == 0) { + return -1; + } + $left = 0; + $right = count($nums) - 1; + while ($left <= $right) { + $mid = floor(($left + $right) / 2); + if ($nums[$mid] == $target) { + return $mid; + } + if ($nums[$mid] > $target) { + $right = $mid - 1; + } + else { + $left = $mid + 1; + } + } + return -1; + } +} +``` ----------------------- * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0707.设计链表.md b/problems/0707.设计链表.md index 8be28f88..e05165a9 100644 --- a/problems/0707.设计链表.md +++ b/problems/0707.设计链表.md @@ -11,7 +11,7 @@ # 707.设计链表 -https://leetcode-cn.com/problems/design-linked-list/ +[力扣题目链接](https://leetcode-cn.com/problems/design-linked-list/) 题意: @@ -28,9 +28,9 @@ https://leetcode-cn.com/problems/design-linked-list/ # 思路 -如果对链表的基础知识还不太懂,可以看这篇文章:[关于链表,你该了解这些!](https://mp.weixin.qq.com/s/fDGMmLrW7ZHlzkzlf_dZkw) +如果对链表的基础知识还不太懂,可以看这篇文章:[关于链表,你该了解这些!](https://programmercarl.com/链表理论基础.html) -如果对链表的虚拟头结点不清楚,可以看这篇文章:[链表:听说用虚拟头节点会方便很多?](https://mp.weixin.qq.com/s/L5aanfALdLEwVWGvyXPDqA) +如果对链表的虚拟头结点不清楚,可以看这篇文章:[链表:听说用虚拟头节点会方便很多?](https://programmercarl.com/0203.移除链表元素.html) 删除链表节点: ![链表-删除节点](https://img-blog.csdnimg.cn/20200806195114541.png) @@ -56,7 +56,7 @@ https://leetcode-cn.com/problems/design-linked-list/ ## 代码 -```C++ +```CPP class MyLinkedList { public: // 定义链表节点结构体 @@ -154,7 +154,129 @@ private: ## 其他语言版本 +C: +```C +typedef struct { + int val; + struct MyLinkedList* next; +}MyLinkedList; +/** Initialize your data structure here. */ + +MyLinkedList* myLinkedListCreate() { + //这个题必须用虚拟头指针,参数都是一级指针,头节点确定后没法改指向了!!! + MyLinkedList* head = (MyLinkedList *)malloc(sizeof (MyLinkedList)); + head->next = NULL; + return head; +} + +/** Get the value of the index-th node in the linked list. If the index is invalid, return -1. */ +int myLinkedListGet(MyLinkedList* obj, int index) { + MyLinkedList *cur = obj->next; + for (int i = 0; cur != NULL; i++){ + if (i == index){ + return cur->val; + } + else{ + cur = cur->next; + } + } + return -1; +} + +/** Add a node of value val before the first element of the linked list. After the insertion, the new node will be the first node of the linked list. */ +void myLinkedListAddAtHead(MyLinkedList* obj, int val) { + MyLinkedList *nhead = (MyLinkedList *)malloc(sizeof (MyLinkedList)); + nhead->val = val; + nhead->next = obj->next; + obj->next = nhead; + +} + +/** Append a node of value val to the last element of the linked list. */ +void myLinkedListAddAtTail(MyLinkedList* obj, int val) { + MyLinkedList *cur = obj; + while(cur->next != NULL){ + cur = cur->next; + } + MyLinkedList *ntail = (MyLinkedList *)malloc(sizeof (MyLinkedList)); + ntail->val = val; + ntail->next = NULL; + cur->next = ntail; +} + +/** Add a node of value val before the index-th node in the linked list. If index equals to the length of linked list, the node will be appended to the end of linked list. If index is greater than the length, the node will not be inserted. */ +void myLinkedListAddAtIndex(MyLinkedList* obj, int index, int val) { + if (index == 0){ + myLinkedListAddAtHead(obj, val); + return; + } + MyLinkedList *cur = obj->next; + for (int i = 1 ;cur != NULL; i++){ + if (i == index){ + MyLinkedList* newnode = (MyLinkedList *)malloc(sizeof (MyLinkedList)); + newnode->val = val; + newnode->next = cur->next; + cur->next = newnode; + return; + } + else{ + cur = cur->next; + } + } +} + +/** Delete the index-th node in the linked list, if the index is valid. */ +void myLinkedListDeleteAtIndex(MyLinkedList* obj, int index) { + if (index == 0){ + MyLinkedList *tmp = obj->next; + if (tmp != NULL){ + obj->next = tmp->next; + free(tmp) + } + return; + } + MyLinkedList *cur = obj->next; + for (int i = 1 ;cur != NULL && cur->next != NULL; i++){ + if (i == index){ + MyLinkedList *tmp = cur->next; + if (tmp != NULL) { + cur->next = tmp->next; + free(tmp); + } + return; + } + else{ + cur = cur->next; + } + } + +} + +void myLinkedListFree(MyLinkedList* obj) { + while(obj != NULL){ + MyLinkedList *tmp = obj; + obj = obj->next; + free(tmp); + } +} + +/** + * Your MyLinkedList struct will be instantiated and called as such: + * MyLinkedList* obj = myLinkedListCreate(); + * int param_1 = myLinkedListGet(obj, index); + + * myLinkedListAddAtHead(obj, val); + + * myLinkedListAddAtTail(obj, val); + + * myLinkedListAddAtIndex(obj, index, val); + + * myLinkedListDeleteAtIndex(obj, index); + + * myLinkedListFree(obj); +*/ +``` Java: ```Java @@ -758,6 +880,155 @@ MyLinkedList.prototype.deleteAtIndex = function(index) { * obj.deleteAtIndex(index) */ ``` +Kotlin: +```kotlin +class MyLinkedList { + + var next: ListNode? = null + + var size: Int = 0 + + fun get(index: Int): Int { + if (index + 1 > size) return -1 + var cur = this.next + for (i in 0 until index) { + cur = cur?.next + } + return cur?.`val` ?: -1 + } + + fun addAtHead(`val`: Int) { + val head = ListNode(`val`) + head.next = this.next + this.next = head + size++ + } + + fun addAtTail(`val`: Int) { + val pre = ListNode(0) + pre.next = this.next + var cur: ListNode? = pre + while (cur?.next != null) { + cur = cur.next + } + cur?.next = ListNode(`val`) + this.next = pre.next + size++ + } + + fun addAtIndex(index: Int, `val`: Int) { + if (index > size) return + val pre = ListNode(0) + pre.next = this.next + var cur:ListNode? = pre + for (i in 0 until index) { + cur = cur?.next + } + val temp = cur?.next + cur?.next = ListNode(`val`) + cur?.next?.next = temp + this.next = pre.next + size++ + } + + fun deleteAtIndex(index: Int) { + if (index + 1 > size) return + val pre = ListNode(0) + pre.next = this.next + var cur: ListNode? = pre + for (i in 0 until index) { + cur = cur?.next + } + val temp = cur?.next?.next + cur?.next?.next = null + cur?.next = temp + this.next = pre.next + size-- + } +} +``` + + +Swift: + +```swift +class MyLinkedList { + var dummyHead: ListNode? + var size: Int + + init() { + dummyHead = ListNode(0) + size = 0 + } + + func get(_ index: Int) -> Int { + if index >= size || index < 0 { + return -1 + } + + var curNode = dummyHead?.next + var curIndex = index + + while curIndex > 0 { + curNode = curNode?.next + curIndex -= 1 + } + + return curNode?.value ?? -1 + } + + func addAtHead(_ val: Int) { + let newHead = ListNode(val) + newHead.next = dummyHead?.next + dummyHead?.next = newHead + size += 1 + } + + func addAtTail(_ val: Int) { + let newNode = ListNode(val) + var curNode = dummyHead + while curNode?.next != nil { + curNode = curNode?.next + } + + curNode?.next = newNode + size += 1 + } + + func addAtIndex(_ index: Int, _ val: Int) { + if index > size { + return + } + + let newNode = ListNode(val) + var curNode = dummyHead + var curIndex = index + + while curIndex > 0 { + curNode = curNode?.next + curIndex -= 1 + } + + newNode.next = curNode?.next + curNode?.next = newNode + size += 1 + } + + func deleteAtIndex(_ index: Int) { + if index >= size || index < 0 { + return + } + + var curNode = dummyHead + for _ in 0.. +
diff --git a/problems/0714.买卖股票的最佳时机含手续费.md b/problems/0714.买卖股票的最佳时机含手续费.md index 2e8f9208..4ac4684e 100644 --- a/problems/0714.买卖股票的最佳时机含手续费.md +++ b/problems/0714.买卖股票的最佳时机含手续费.md @@ -9,7 +9,7 @@ ## 714. 买卖股票的最佳时机含手续费 -题目链接:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-with-transaction-fee/ +[力扣题目链接](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-with-transaction-fee/) 给定一个整数数组 prices,其中第 i 个元素代表了第 i 天的股票价格 ;非负整数 fee 代表了交易股票的手续费用。 @@ -37,11 +37,11 @@ ## 思路 -本题相对于[贪心算法:122.买卖股票的最佳时机II](https://mp.weixin.qq.com/s/VsTFA6U96l18Wntjcg3fcg),多添加了一个条件就是手续费。 +本题相对于[贪心算法:122.买卖股票的最佳时机II](https://programmercarl.com/0122.买卖股票的最佳时机II.html),多添加了一个条件就是手续费。 ## 贪心算法 -在[贪心算法:122.买卖股票的最佳时机II](https://mp.weixin.qq.com/s/VsTFA6U96l18Wntjcg3fcg)中使用贪心策略不用关心具体什么时候买卖,只要收集每天的正利润,最后稳稳的就是最大利润了。 +在[贪心算法:122.买卖股票的最佳时机II](https://programmercarl.com/0122.买卖股票的最佳时机II.html)中使用贪心策略不用关心具体什么时候买卖,只要收集每天的正利润,最后稳稳的就是最大利润了。 而本题有了手续费,就要关系什么时候买卖了,因为计算所获得利润,需要考虑买卖利润可能不足以手续费的情况。 @@ -60,7 +60,7 @@ 贪心算法C++代码如下: -```C++ +```CPP class Solution { public: int maxProfit(vector& prices, int fee) { @@ -97,11 +97,11 @@ public: 我在公众号「代码随想录」里将在下一个系列详细讲解动态规划,所以本题解先给出我的C++代码(带详细注释),感兴趣的同学可以自己先学习一下。 -相对于[贪心算法:122.买卖股票的最佳时机II](https://mp.weixin.qq.com/s/VsTFA6U96l18Wntjcg3fcg)的动态规划解法中,只需要在计算卖出操作的时候减去手续费就可以了,代码几乎是一样的。 +相对于[贪心算法:122.买卖股票的最佳时机II](https://programmercarl.com/0122.买卖股票的最佳时机II.html)的动态规划解法中,只需要在计算卖出操作的时候减去手续费就可以了,代码几乎是一样的。 C++代码如下: -```C++ +```CPP class Solution { public: int maxProfit(vector& prices, int fee) { @@ -126,7 +126,7 @@ public: C++ 代码如下: -```C++ +```CPP class Solution { public: int maxProfit(vector& prices, int fee) { @@ -216,7 +216,30 @@ class Solution: # 贪心思路 ``` Go: - +```golang +func maxProfit(prices []int, fee int) int { + var minBuy int = prices[0] //第一天买入 + var res int + for i:=0;i=minBuy&&prices[i]-fee-minBuy<=0{ + continue + } + //可以售卖了 + if prices[i]>minBuy+fee{ + //累加每天的收益 + res+=prices[i]-minBuy-fee + //更新最小值(如果还在收获利润的区间里,表示并不是真正的卖出,而计算利润每次都要减去手续费,所以要让minBuy = prices[i] - fee;,这样在明天收获利润的时候,才不会多减一次手续费!) + minBuy=prices[i]-fee + } + } + return res +} +``` Javascript: ```Javascript // 贪心思路 @@ -246,4 +269,4 @@ var maxProfit = function(prices, fee) { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0714.买卖股票的最佳时机含手续费(动态规划).md b/problems/0714.买卖股票的最佳时机含手续费(动态规划).md index e2b83999..7c54a2fe 100644 --- a/problems/0714.买卖股票的最佳时机含手续费(动态规划).md +++ b/problems/0714.买卖股票的最佳时机含手续费(动态规划).md @@ -8,7 +8,7 @@ ## 714.买卖股票的最佳时机含手续费 -题目链接:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-with-transaction-fee/ +[力扣题目链接](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-with-transaction-fee/) 给定一个整数数组 prices,其中第 i 个元素代表了第 i 天的股票价格 ;非负整数 fee 代表了交易股票的手续费用。 @@ -36,7 +36,7 @@ ## 思路 -在讲解贪心专题的时候,我们已经讲过本题了[贪心算法:买卖股票的最佳时机含手续费](https://mp.weixin.qq.com/s/olWrUuDEYw2Jx5rMeG7XAg) +在讲解贪心专题的时候,我们已经讲过本题了[贪心算法:买卖股票的最佳时机含手续费](https://programmercarl.com/0714.买卖股票的最佳时机含手续费.html) 使用贪心算法,的性能是: * 时间复杂度:O(n) @@ -44,7 +44,7 @@ 那么我们再来看看是使用动规的方法如何解题。 -相对于[动态规划:122.买卖股票的最佳时机II](https://mp.weixin.qq.com/s/d4TRWFuhaY83HPa6t5ZL-w),本题只需要在计算卖出操作的时候减去手续费就可以了,代码几乎是一样的。 +相对于[动态规划:122.买卖股票的最佳时机II](https://programmercarl.com/0122.买卖股票的最佳时机II.html),本题只需要在计算卖出操作的时候减去手续费就可以了,代码几乎是一样的。 唯一差别在于递推公式部分,所以本篇也就不按照动规五部曲详细讲解了,主要讲解一下递推公式部分。 @@ -68,11 +68,11 @@ dp[i][1] 表示第i天不持有股票所得最多现金 所以:dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] + prices[i] - fee); -**本题和[动态规划:122.买卖股票的最佳时机II](https://mp.weixin.qq.com/s/d4TRWFuhaY83HPa6t5ZL-w)的区别就是这里需要多一个减去手续费的操作**。 +**本题和[动态规划:122.买卖股票的最佳时机II](https://programmercarl.com/0122.买卖股票的最佳时机II.html)的区别就是这里需要多一个减去手续费的操作**。 以上分析完毕,C++代码如下: -```C++ +```CPP class Solution { public: int maxProfit(vector& prices, int fee) { @@ -152,6 +152,25 @@ class Solution: ``` Go: +```Go +func maxProfit(prices []int, fee int) int { + n := len(prices) + dp := make([][2]int, n) + dp[0][0] = -prices[0] + for i := 1; i < n; i++ { + dp[i][1] = max(dp[i-1][1], dp[i-1][0]+prices[i]-fee) + dp[i][0] = max(dp[i-1][0], dp[i-1][1]-prices[i]) + } + return dp[n-1][1] +} + +func max(a, b int) int { + if a > b { + return a + } + return b +} +``` Javascript: ```javascript @@ -171,4 +190,4 @@ const maxProfit = (prices,fee) => { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0718.最长重复子数组.md b/problems/0718.最长重复子数组.md index 9ee94a3d..9e3da663 100644 --- a/problems/0718.最长重复子数组.md +++ b/problems/0718.最长重复子数组.md @@ -8,7 +8,7 @@ ## 718. 最长重复子数组 -题目链接:https://leetcode-cn.com/problems/maximum-length-of-repeated-subarray/ +[力扣题目链接](https://leetcode-cn.com/problems/maximum-length-of-repeated-subarray/) 给两个整数数组 A 和 B ,返回两个数组中公共的、长度最长的子数组的长度。 @@ -74,7 +74,7 @@ dp[i][j] :以下标i - 1为结尾的A,和以下标j - 1为结尾的B,最 代码如下: -```C++ +```CPP for (int i = 1; i <= A.size(); i++) { for (int j = 1; j <= B.size(); j++) { if (A[i - 1] == B[j - 1]) { @@ -94,7 +94,7 @@ for (int i = 1; i <= A.size(); i++) { 以上五部曲分析完毕,C++代码如下: -```C++ +```CPP class Solution { public: int findLength(vector& A, vector& B) { @@ -128,7 +128,7 @@ public: **此时遍历B数组的时候,就要从后向前遍历,这样避免重复覆盖**。 -```C++ +```CPP class Solution { public: int findLength(vector& A, vector& B) { @@ -252,10 +252,37 @@ func findLength(A []int, B []int) int { } ``` +JavaScript: + +> 动态规划 + +```javascript +const findLength = (A, B) => { + // A、B数组的长度 + const [m, n] = [A.length, B.length]; + // dp数组初始化,都初始化为0 + const dp = new Array(m + 1).fill(0).map(x => new Array(n + 1).fill(0)); + // 初始化最大长度为0 + let res = 0; + for (let i = 1; i <= m; i++) { + for (let j = 1; j <= n; j++) { + // 遇到A[i - 1] === B[j - 1],则更新dp数组 + if (A[i - 1] === B[j - 1]) { + dp[i][j] = dp[i - 1][j - 1] + 1; + } + // 更新res + res = dp[i][j] > res ? dp[i][j] : res; + } + } + // 遍历完成,返回res + return res; +}; +``` + ----------------------- * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0724.寻找数组的中心索引.md b/problems/0724.寻找数组的中心索引.md index bf989979..b4115893 100644 --- a/problems/0724.寻找数组的中心索引.md +++ b/problems/0724.寻找数组的中心索引.md @@ -43,7 +43,7 @@ * 判断leftSum和rightSum是否相同 C++代码如下: -```C++ +```CPP class Solution { public: int pivotIndex(vector& nums) { @@ -67,11 +67,38 @@ public: ## Java ```java +class Solution { + public int pivotIndex(int[] nums) { + int sum = 0; + for (int i = 0; i < nums.length; i++) { + sum += nums[i]; // 总和 + } + int leftSum = 0; + int rightSum = 0; + for (int i = 0; i < nums.length; i++) { + leftSum += nums[i]; + rightSum = sum - leftSum + nums[i]; // leftSum 里面已经有 nums[i],多减了一次,所以加上 + if (leftSum == rightSum) { + return i; + } + } + return -1; // 不存在 + } +} ``` ## Python -```python +```python3 +class Solution: + def pivotIndex(self, nums: List[int]) -> int: + numSum = sum(nums) #数组总和 + leftSum = 0 + for i in range(len(nums)): + if numSum - leftSum -nums[i] == leftSum: #左右和相等 + return i + leftSum += nums[i] + return -1 ``` ## Go @@ -88,6 +115,5 @@ public: * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
- +
diff --git a/problems/0738.单调递增的数字.md b/problems/0738.单调递增的数字.md index 75c7f250..f63a05b8 100644 --- a/problems/0738.单调递增的数字.md +++ b/problems/0738.单调递增的数字.md @@ -8,7 +8,7 @@ ## 738.单调递增的数字 -题目链接: https://leetcode-cn.com/problems/monotone-increasing-digits/ +[力扣题目链接](https://leetcode-cn.com/problems/monotone-increasing-digits/) 给定一个非负整数 N,找出小于或等于 N 的最大的整数,同时这个整数需要满足其各个位数上的数字是单调递增。 @@ -34,7 +34,7 @@ 题意很简单,那么首先想的就是暴力解法了,来我替大家暴力一波,结果自然是超时! 代码如下: -```C++ +```CPP class Solution { private: bool checkNum(int num) { @@ -87,7 +87,7 @@ public: C++代码如下: -```C++ +```CPP class Solution { public: int monotoneIncreasingDigits(int N) { @@ -159,7 +159,26 @@ class Solution: ``` Go: - +```golang +func monotoneIncreasingDigits(N int) int { + s := strconv.Itoa(N)//将数字转为字符串,方便使用下标 + ss := []byte(s)//将字符串转为byte数组,方便更改。 + n := len(ss) + if n <= 1 { + return N + } + for i:=n-1 ; i>0; i-- { + if ss[i-1] > ss[i] {//前一个大于后一位,前一位减1,后面的全部置为9 + ss[i-1] -= 1 + for j := i ; j < n; j++ {//后面的全部置为9 + ss[j] = '9' + } + } + } + res, _ := strconv.Atoi(string(ss)) + return res +} +``` Javascript: ```Javascript var monotoneIncreasingDigits = function(n) { @@ -190,4 +209,4 @@ var monotoneIncreasingDigits = function(n) { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0739.每日温度.md b/problems/0739.每日温度.md index 8ad79fe3..b00701ed 100644 --- a/problems/0739.每日温度.md +++ b/problems/0739.每日温度.md @@ -9,8 +9,7 @@ # 739. 每日温度 - -https://leetcode-cn.com/problems/daily-temperatures/ +[力扣题目链接](https://leetcode-cn.com/problems/daily-temperatures/) 请根据每日 气温 列表,重新生成一个列表。对应位置的输出为:要想观测到更高的气温,至少需要等待的天数。如果气温在这之后都不会升高,请在该位置用 0 来代替。 @@ -117,7 +116,7 @@ T[4]弹出之后, T[5] > T[3] (当前遍历的元素T[i]大于栈顶元素T[ C++代码如下: -```C++ +```CPP // 版本一 class Solution { public: @@ -148,7 +147,7 @@ public: 精简代码如下: -```C++ +```CPP // 版本二 class Solution { public: @@ -283,5 +282,5 @@ func dailyTemperatures(num []int) []int { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0746.使用最小花费爬楼梯.md b/problems/0746.使用最小花费爬楼梯.md index 4238a389..eb2a437a 100644 --- a/problems/0746.使用最小花费爬楼梯.md +++ b/problems/0746.使用最小花费爬楼梯.md @@ -8,7 +8,7 @@ ## 746. 使用最小花费爬楼梯 -题目链接:https://leetcode-cn.com/problems/min-cost-climbing-stairs/ +[力扣题目链接](https://leetcode-cn.com/problems/min-cost-climbing-stairs/) 数组的每个下标作为一个阶梯,第 i 个阶梯对应着一个非负数的体力花费值 cost[i](下标从 0 开始)。 @@ -34,7 +34,7 @@ ## 思路 -这道题目可以说是昨天[动态规划:爬楼梯](https://mp.weixin.qq.com/s/Ohop0jApSII9xxOMiFhGIw)的花费版本。 +这道题目可以说是昨天[动态规划:爬楼梯](https://programmercarl.com/0070.爬楼梯.html)的花费版本。 **注意题目描述:每当你爬上一个阶梯你都要花费对应的体力值,一旦支付了相应的体力值,你就可以选择向上爬一个阶梯或者爬两个阶梯** @@ -98,7 +98,7 @@ dp[1] = cost[1]; 以上分析完毕,整体C++代码如下: -```C++ +```CPP // 版本一 class Solution { public: @@ -120,7 +120,7 @@ public: 还可以优化空间复杂度,因为dp[i]就是由前两位推出来的,那么也不用dp数组了,C++代码如下: -```C++ +```CPP // 版本二 class Solution { public: @@ -165,7 +165,7 @@ public: 所以代码这么写: -```C++ +```CPP class Solution { public: int minCostClimbingStairs(vector& cost) { @@ -185,9 +185,9 @@ public: # 总结 -大家可以发现这道题目相对于 昨天的[动态规划:爬楼梯](https://mp.weixin.qq.com/s/Ohop0jApSII9xxOMiFhGIw)有难了一点,但整体思路是一样。 +大家可以发现这道题目相对于 昨天的[动态规划:爬楼梯](https://programmercarl.com/0070.爬楼梯.html)有难了一点,但整体思路是一样。 -从[动态规划:斐波那契数](https://mp.weixin.qq.com/s/ko0zLJplF7n_4TysnPOa_w)到 [动态规划:爬楼梯](https://mp.weixin.qq.com/s/Ohop0jApSII9xxOMiFhGIw)再到今天这道题目,录友们感受到循序渐进的梯度了嘛。 +从[动态规划:斐波那契数](https://programmercarl.com/0509.斐波那契数.html)到 [动态规划:爬楼梯](https://programmercarl.com/0070.爬楼梯.html)再到今天这道题目,录友们感受到循序渐进的梯度了嘛。 每个系列开始的时候,都有录友和我反馈说题目太简单了,赶紧上难度,但也有录友和我说有点难了,快跟不上了。 @@ -272,4 +272,4 @@ var minCostClimbingStairs = function(cost) { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0763.划分字母区间.md b/problems/0763.划分字母区间.md index b36e00b7..43c663c2 100644 --- a/problems/0763.划分字母区间.md +++ b/problems/0763.划分字母区间.md @@ -9,7 +9,7 @@ ## 763.划分字母区间 -题目链接: https://leetcode-cn.com/problems/partition-labels/ +[力扣题目链接](https://leetcode-cn.com/problems/partition-labels/) 字符串 S 由小写字母组成。我们要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中。返回一个表示每个字符串片段的长度的列表。 @@ -20,7 +20,7 @@ 划分结果为 "ababcbaca", "defegde", "hijhklij"。 每个字母最多出现在一个片段中。 像 "ababcbacadefegde", "hijhklij" 的划分是错误的,因为划分的片段数较少。 -  + 提示: * S的长度在[1, 500]之间。 @@ -47,7 +47,7 @@ 明白原理之后,代码并不复杂,如下: -```C++ +```CPP class Solution { public: vector partitionLabels(string S) { @@ -181,4 +181,4 @@ var partitionLabels = function(s) { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0841.钥匙和房间.md b/problems/0841.钥匙和房间.md index 1b3c4a95..4a0185ec 100644 --- a/problems/0841.钥匙和房间.md +++ b/problems/0841.钥匙和房间.md @@ -10,7 +10,7 @@ # 841.钥匙和房间 -题目地址:https://leetcode-cn.com/problems/keys-and-rooms/ +[力扣题目链接](https://leetcode-cn.com/problems/keys-and-rooms/) 有 N 个房间,开始时你位于 0 号房间。每个房间有不同的号码:0,1,2,...,N-1,并且房间里可能有一些钥匙能使你进入下一个房间。 @@ -54,7 +54,7 @@ BFS C++代码代码如下: -```C++ +```CPP class Solution { bool bfs(const vector>& rooms) { vector visited(rooms.size(), 0); // 标记房间是否被访问过 @@ -89,7 +89,7 @@ public: DFS C++代码如下: -```C++ +```CPP class Solution { private: void dfs(int key, const vector>& rooms, vector& visited) { @@ -131,5 +131,5 @@ JavaScript: * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0844.比较含退格的字符串.md b/problems/0844.比较含退格的字符串.md index 9f37959d..e6bf3493 100644 --- a/problems/0844.比较含退格的字符串.md +++ b/problems/0844.比较含退格的字符串.md @@ -9,12 +9,12 @@ # 844.比较含退格的字符串 -题目链接:https://leetcode-cn.com/problems/backspace-string-compare/ +[力扣题目链接](https://leetcode-cn.com/problems/backspace-string-compare/) 给定 S 和 T 两个字符串,当它们分别被输入到空白的文本编辑器后,判断二者是否相等,并返回结果。 # 代表退格字符。 注意:如果对空文本输入退格字符,文本继续为空。 -  + 示例 1: * 输入:S = "ab#c", T = "ad#c" * 输出:true @@ -42,7 +42,7 @@ ## 普通方法(使用栈的思路) -这道题目一看就是要使用栈的节奏,这种匹配(消除)问题也是栈的擅长所在,跟着一起刷题的同学应该知道,在[栈与队列:匹配问题都是栈的强项](https://mp.weixin.qq.com/s/1-x6r1wGA9mqIHW5LrMvBg),我就已经提过了一次使用栈来做类似的事情了。 +这道题目一看就是要使用栈的节奏,这种匹配(消除)问题也是栈的擅长所在,跟着一起刷题的同学应该知道,在[栈与队列:匹配问题都是栈的强项](https://programmercarl.com/1047.删除字符串中的所有相邻重复项.html),我就已经提过了一次使用栈来做类似的事情了。 **那么本题,确实可以使用栈的思路,但是没有必要使用栈,因为最后比较的时候还要比较栈里的元素,有点麻烦**。 @@ -50,7 +50,7 @@ 代码如下: -```C++ +```CPP class Solution { public: bool backspaceCompare(string S, string T) { @@ -78,7 +78,7 @@ public: 当然以上代码,大家可以发现有重复的逻辑处理S,处理T,可以把这块公共逻辑抽离出来,代码精简如下: -```C++ +```CPP class Solution { private: string getString(const string& S) { @@ -115,7 +115,7 @@ public: 代码如下: -```C++ +```CPP class Solution { public: bool backspaceCompare(string S, string T) { @@ -160,6 +160,32 @@ public: Java: +```java +// 普通方法(使用栈的思路) +class Solution { + public boolean backspaceCompare(String s, String t) { + StringBuilder ssb = new StringBuilder(); // 模拟栈 + StringBuilder tsb = new StringBuilder(); // 模拟栈 + // 分别处理两个 String + for (char c : s.toCharArray()) { + if (c != '#') { + ssb.append(c); // 模拟入栈 + } else if (ssb.length() > 0){ // 栈非空才能弹栈 + ssb.deleteCharAt(ssb.length() - 1); // 模拟弹栈 + } + } + for (char c : t.toCharArray()) { + if (c != '#') { + tsb.append(c); // 模拟入栈 + } else if (tsb.length() > 0){ // 栈非空才能弹栈 + tsb.deleteCharAt(tsb.length() - 1); // 模拟弹栈 + } + } + return ssb.toString().equals(tsb.toString()); + } +} +``` + Python: Go: @@ -170,5 +196,5 @@ JavaScript: * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0860.柠檬水找零.md b/problems/0860.柠檬水找零.md index 0d5a7075..46e05419 100644 --- a/problems/0860.柠檬水找零.md +++ b/problems/0860.柠檬水找零.md @@ -9,7 +9,7 @@ ## 860.柠檬水找零 -题目链接:https://leetcode-cn.com/problems/lemonade-change/ +[力扣题目链接](https://leetcode-cn.com/problems/lemonade-change/) 在柠檬水摊上,每一杯柠檬水的售价为 5 美元。 @@ -82,7 +82,7 @@ C++代码如下: -```C++ +```CPP class Solution { public: bool lemonadeChange(vector& bills) { @@ -260,4 +260,4 @@ var lemonadeChange = function(bills) { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0922.按奇偶排序数组II.md b/problems/0922.按奇偶排序数组II.md index bb609c12..97d7091e 100644 --- a/problems/0922.按奇偶排序数组II.md +++ b/problems/0922.按奇偶排序数组II.md @@ -32,7 +32,7 @@ 其实这道题可以用很朴实的方法,时间复杂度就就是O(n)了,C++代码如下: -```C++ +```CPP class Solution { public: vector sortArrayByParityII(vector& A) { @@ -64,7 +64,7 @@ public: 以上代码我是建了两个辅助数组,而且A数组还相当于遍历了两次,用辅助数组的好处就是思路清晰,优化一下就是不用这两个辅助树,代码如下: -```C++ +```CPP class Solution { public: vector sortArrayByParityII(vector& A) { @@ -93,7 +93,7 @@ public: 当然还可以在原数组上修改,连result数组都不用了。 -```C++ +```CPP class Solution { public: vector sortArrayByParityII(vector& A) { @@ -120,11 +120,61 @@ public: ## Java ```java +// 方法一 +class Solution { + public int[] sortArrayByParityII(int[] nums) { + // 分别存放 nums 中的奇数、偶数 + int len = nums.length; + int evenIndex = 0; + int oddIndex = 0; + int[] even = new int[len / 2]; + int[] odd = new int[len / 2]; + for (int i = 0; i < len; i++) { + if (nums[i] % 2 == 0) { + even[evenIndex++] = nums[i]; + } else { + odd[oddIndex++] = nums[i]; + } + } + // 把奇偶数组重新存回 nums + int index = 0; + for (int i = 0; i < even.length; i++) { + nums[index++] = even[i]; + nums[index++] = odd[i]; + } + return nums; + } +} ``` ## Python -```python +```python3 +#方法2 +class Solution: + def sortArrayByParityII(self, nums: List[int]) -> List[int]: + result = [0]*len(nums) + evenIndex = 0 + oddIndex = 1 + for i in range(len(nums)): + if nums[i] % 2: #奇数 + result[oddIndex] = nums[i] + oddIndex += 2 + else: #偶数 + result[evenIndex] = nums[i] + evenIndex += 2 + return result + +#方法3 +class Solution: + def sortArrayByParityII(self, nums: List[int]) -> List[int]: + oddIndex = 1 + for i in range(0,len(nums),2): #步长为2 + if nums[i] % 2: #偶数位遇到奇数 + while nums[oddIndex] % 2: #奇数位找偶数 + oddIndex += 2 + nums[i], nums[oddIndex] = nums[oddIndex], nums[i] + return nums ``` ## Go @@ -141,6 +191,5 @@ public: * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
- +
diff --git a/problems/0925.长按键入.md b/problems/0925.长按键入.md index 95b6325a..3502f2fb 100644 --- a/problems/0925.长按键入.md +++ b/problems/0925.长按键入.md @@ -8,6 +8,7 @@

欢迎大家参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!

# 925.长按键入 +[力扣题目链接](https://leetcode-cn.com/problems/long-pressed-name/) 你的朋友正在使用键盘输入他的名字 name。偶尔,在键入字符 c 时,按键可能会被长按,而字符可能被输入 1 次或多次。 @@ -59,7 +60,7 @@ 上面的逻辑想清楚了,不难写出如下C++代码: -```C++ +```CPP class Solution { public: bool isLongPressedName(string name, string typed) { @@ -98,9 +99,62 @@ public: # 其他语言版本 Java: - +```java +class Solution { + public boolean isLongPressedName(String name, String typed) { + int i = 0, j = 0; + int m = name.length(), n = typed.length(); + while (i< m && j < n) { + if (name.charAt(i) == typed.charAt(j)) { // 相同则同时向后匹配 + i++; j++; + } + else { + if (j == 0) return false; // 如果是第一位就不相同直接返回false + // 判断边界为n-1,若为n会越界,例如name:"kikcxmvzi" typed:"kiikcxxmmvvzzz" + while (j < n-1 && typed.charAt(j) == typed.charAt(j-1)) j++; + if (name.charAt(i) == typed.charAt(j)) { // j跨越重复项之后再次和name[i]匹配 + i++; j++; // 相同则同时向后匹配 + } + else return false; + } + } + // 说明name没有匹配完 + if (i < m) return false; + // 说明type没有匹配完 + while (j < n) { + if (typed.charAt(j) == typed.charAt(j-1)) j++; + else return false; + } + return true; + } +} +``` Python: - +```python +class Solution: + def isLongPressedName(self, name: str, typed: str) -> bool: + i, j = 0, 0 + m, n = len(name) , len(typed) + while i< m and j < n: + if name[i] == typed[j]: # 相同时向后匹配 + i += 1 + j += 1 + else: # 不相同 + if j == 0: return False # 如果第一位不相同,直接返回false + # 判断边界为n-1,若为n会越界,例如name:"kikcxmvzi" typed:"kiikcxxmmvvzzz" + while j < n - 1 and typed[j] == typed[j-1]: j += 1 + if name[i] == typed[j]: + i += 1 + j += 1 + else: return False + # 说明name没有匹配完 + if i < m: return False + # 说明type没有匹配完 + while j < n: + if typed[j] == typed[j-1]: j += 1 + else: return False + return True +``` Go: JavaScript: @@ -109,5 +163,5 @@ JavaScript: * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0941.有效的山脉数组.md b/problems/0941.有效的山脉数组.md index 167cfb1a..ef5739e3 100644 --- a/problems/0941.有效的山脉数组.md +++ b/problems/0941.有效的山脉数组.md @@ -1,9 +1,41 @@ -## 题目链接 +

+ + + + +

+

欢迎大家参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!

-https://leetcode-cn.com/problems/valid-mountain-array/ +# 941.有效的山脉数组 -## 思路 +[力扣题目链接](https://leetcode-cn.com/problems/valid-mountain-array/) + +给定一个整数数组 arr,如果它是有效的山脉数组就返回 true,否则返回 false。 + +让我们回顾一下,如果 A 满足下述条件,那么它是一个山脉数组: + +* arr.length >= 3 +* 在 0 < i < arr.length - 1 条件下,存在 i 使得: + * arr[0] < arr[1] < ... arr[i-1] < arr[i] + * arr[i] > arr[i+1] > ... > arr[arr.length - 1] + +![](https://code-thinking-1253855093.file.myqcloud.com/pics/20210729103604.png) + +示例 1: +* 输入:arr = [2,1] +* 输出:false + +示例 2: +* 输入:arr = [3,5,5] +* 输出:false + +示例 3: +* 输入:arr = [0,3,2,1] +* 输出:true + + +# 思路 判断是山峰,主要就是要严格的保存左边到中间,和右边到中间是递增的。 @@ -38,7 +70,12 @@ public: } }; ``` -Java 版本如下: + +如果想系统学一学双指针的话, 可以看一下这篇[双指针法:总结篇!](https://programmercarl.com/双指针总结.html) + +# 其他语言版本 + +## Java ```java class Solution { @@ -66,5 +103,26 @@ class Solution { } ``` -如果想系统学一学双指针的话, 可以看一下这篇[双指针法:总结篇!](https://mp.weixin.qq.com/s/_p7grwjISfMh0U65uOyCjA) +## Python +```python +``` + +## Go + +```go +``` + +## JavaScript + +```js +``` + +----------------------- +* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) +* B站视频:[代码随想录](https://space.bilibili.com/525438321) +* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) + + + +
diff --git a/problems/0968.监控二叉树.md b/problems/0968.监控二叉树.md index a0eb5883..384c9a78 100644 --- a/problems/0968.监控二叉树.md +++ b/problems/0968.监控二叉树.md @@ -9,7 +9,7 @@ ## 968.监控二叉树 -题目地址 : https://leetcode-cn.com/problems/binary-tree-cameras/ +[力扣题目链接](https://leetcode-cn.com/problems/binary-tree-cameras/) 给定一个二叉树,我们在树的节点上安装摄像头。 @@ -216,7 +216,7 @@ int minCameraCover(TreeNode* root) { ## C++代码 -```C++ +```CPP // 版本一 class Solution { private: @@ -270,7 +270,7 @@ public: 在以上代码的基础上,再进行精简,代码如下: -```C++ +```CPP // 版本二 class Solution { private: @@ -347,28 +347,88 @@ class Solution { Python: ```python +# Definition for a binary tree node. +# class TreeNode: +# def __init__(self, val=0, left=None, right=None): +# self.val = val +# self.left = left +# self.right = right class Solution: def minCameraCover(self, root: TreeNode) -> int: + # Greedy Algo: + # 从下往上安装摄像头:跳过leaves这样安装数量最少,局部最优 -> 全局最优 + # 先给leaves的父节点安装,然后每隔两层节点安装一个摄像头,直到Head + # 0: 该节点未覆盖 + # 1: 该节点有摄像头 + # 2: 该节点有覆盖 + result = 0 - def traversal(cur): + # 从下往上遍历:后序(左右中) + def traversal(curr: TreeNode) -> int: nonlocal result - if not cur: - return 2 - left = traversal(cur.left) - right = traversal(cur.right) - if left == 2 and right == 2: + + if not curr: return 2 + left = traversal(curr.left) + right = traversal(curr.right) + + # Case 1: + # 左右节点都有覆盖 + if left == 2 and right == 2: return 0 - elif left == 0 or right == 0: + + # Case 2: + # left == 0 && right == 0 左右节点无覆盖 + # left == 1 && right == 0 左节点有摄像头,右节点无覆盖 + # left == 0 && right == 1 左节点有无覆盖,右节点摄像头 + # left == 0 && right == 2 左节点无覆盖,右节点覆盖 + # left == 2 && right == 0 左节点覆盖,右节点无覆盖 + elif left == 0 or right == 0: result += 1 return 1 + + # Case 3: + # left == 1 && right == 2 左节点有摄像头,右节点有覆盖 + # left == 2 && right == 1 左节点有覆盖,右节点有摄像头 + # left == 1 && right == 1 左右节点都有摄像头 elif left == 1 or right == 1: return 2 - else: return -1 - if traversal(root) == 0: result += 1 + + # 其他情况前段代码均已覆盖 + + if traversal(root) == 0: + result += 1 + return result ``` Go: +```go +const inf = math.MaxInt64 / 2 +func minCameraCover(root *TreeNode) int { + var dfs func(*TreeNode) (a, b, c int) + dfs = func(node *TreeNode) (a, b, c int) { + if node == nil { + return inf, 0, 0 + } + lefta, leftb, leftc := dfs(node.Left) + righta, rightb, rightc := dfs(node.Right) + a = leftc + rightc + 1 + b = min(a, min(lefta+rightb, righta+leftb)) + c = min(a, leftb+rightb) + return + } + _, ans, _ := dfs(root) + return ans +} + +func min(a, b int) int { + if a <= b { + return a + } + return b +} + +``` Javascript: ```Javascript var minCameraCover = function(root) { @@ -406,9 +466,48 @@ var minCameraCover = function(root) { }; ``` +C: +```c +/* +**函数后序遍历二叉树。判断一个结点状态时,根据其左右孩子结点的状态进行判断 +**状态:0为没有被摄像头覆盖到。1为此结点处应设置摄像头。2为此结点已被摄像头覆盖 +*/ +int traversal(struct TreeNode* node, int* ans) { + //递归结束条件:传入结点为NULL,假设此结点能被摄像头覆盖。这样方便与对叶子结点的判断,将叶子结点设为0 + if(!node) + return 2; + //后序遍历二叉树,记录左右孩子的状态。根据左右孩子状态更新结点自身状态 + int left = traversal(node->left, ans); + int right = traversal(node->right, ans); + + //若左右孩子都可以被摄像头覆盖,将父亲结点状态设为0 + if(left == 2 && right == 2) { + return 0; + } + //若左右孩子有一个结点状态为没有被覆盖(0),则将父亲结点状态设置为摄像头 + if(left == 0 || right == 0) { + (*ans)++; + return 1; + } + //若左右孩子有一个为摄像头,证明父亲结点可以被覆盖。将父亲结点状态变为2 + if(left == 1 || right == 1) + return 2; + //逻辑不会走到-1,语句不会执行 + return -1; +} + +int minCameraCover(struct TreeNode* root){ + int ans = 0; + + //在对整个二叉树遍历后。头结点可能未被覆盖,这时候如果函数返回值为0,证明头结点未被覆盖。说明头结点也需要添置摄像头,ans++ + if(traversal(root, &ans) == 0) + ans++; + return ans; +} +``` ----------------------- * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/0977.有序数组的平方.md b/problems/0977.有序数组的平方.md index 0f9007d7..a57af3c1 100644 --- a/problems/0977.有序数组的平方.md +++ b/problems/0977.有序数组的平方.md @@ -10,7 +10,7 @@ # 977.有序数组的平方 -https://leetcode-cn.com/problems/squares-of-a-sorted-array/ +[力扣题目链接](https://leetcode-cn.com/problems/squares-of-a-sorted-array/) 给你一个按 非递减顺序 排序的整数数组 nums,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。 @@ -29,7 +29,7 @@ https://leetcode-cn.com/problems/squares-of-a-sorted-array/ 最直观的相反,莫过于:每个数平方之后,排个序,美滋滋,代码如下: -```C++ +```CPP class Solution { public: vector sortedSquares(vector& A) { @@ -64,7 +64,7 @@ public: 不难写出如下代码: -```C++ +```CPP class Solution { public: vector sortedSquares(vector& A) { @@ -224,8 +224,119 @@ const sortedSquares = function (nums) { } ``` +Swift: + +```swift +func sortedSquares(_ nums: [Int]) -> [Int] { + // 指向新数组最后一个元素 + var k = nums.count - 1 + // 指向原数组第一个元素 + var i = 0 + // 指向原数组最后一个元素 + var j = nums.count - 1 + // 初始化新数组(用-1填充) + var result = Array(repeating: -1, count: nums.count) + + for _ in 0.. nums[right]**2 + result << nums[left]**2 + left += 1 + else + result << nums[right]**2 + right -= 1 + end + end + result.reverse +end +``` + + +C: +```c +int* sortedSquares(int* nums, int numsSize, int* returnSize){ + //返回的数组大小就是原数组大小 + *returnSize = numsSize; + //创建两个指针,right指向数组最后一位元素,left指向数组第一位元素 + int right = numsSize - 1; + int left = 0; + + //最后要返回的结果数组 + int* ans = (int*)malloc(sizeof(int) * numsSize); + int index; + for(index = numsSize - 1; index >= 0; index--) { + //左指针指向元素的平方 + int lSquare = nums[left] * nums[left]; + //右指针指向元素的平方 + int rSquare = nums[right] * nums[right]; + //若左指针指向元素平方比右指针指向元素平方大,将左指针指向元素平方放入结果数组。左指针右移一位 + if(lSquare > rSquare) { + ans[index] = lSquare; + left++; + } + //若右指针指向元素平方比左指针指向元素平方大,将右指针指向元素平方放入结果数组。右指针左移一位 + else { + ans[index] = rSquare; + right--; + } + } + //返回结果数组 + return ans; +} +``` + +PHP: +```php +class Solution { + /** + * @param Integer[] $nums + * @return Integer[] + */ + function sortedSquares($nums) { + // 双指针法 + $res = []; + for ($i = 0; $i < count($nums); $i++) { + $res[$i] = 0; + } + $k = count($nums) - 1; + for ($i = 0, $j = count($nums) - 1; $i <= $j; ) { + if ($nums[$i] ** 2 < $nums[$j] ** 2) { + $res[$k--] = $nums[$j] ** 2; + $j--; + } + else { + $res[$k--] = $nums[$i] ** 2; + $i++; + } + } + return $res; + } +} +``` + + + ----------------------- * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/1002.查找常用字符.md b/problems/1002.查找常用字符.md index 875340c4..e02780da 100644 --- a/problems/1002.查找常用字符.md +++ b/problems/1002.查找常用字符.md @@ -10,7 +10,7 @@ # 1002. 查找常用字符 -https://leetcode-cn.com/problems/find-common-characters/ +[力扣题目链接](https://leetcode-cn.com/problems/find-common-characters/) 给定仅有小写字母组成的字符串数组 A,返回列表中的每个字符串中都显示的全部字符(包括重复字符)组成的列表。例如,如果一个字符在每个字符串中出现 3 次,但不是 4 次,则需要在最终答案中包含该字符 3 次。 @@ -23,7 +23,7 @@ https://leetcode-cn.com/problems/find-common-characters/ 【示例二】 输入:["cool","lock","cook"] 输出:["c","o"] -  + # 思路 @@ -40,9 +40,9 @@ https://leetcode-cn.com/problems/find-common-characters/ 可以看出这是指数级别的时间复杂度,非常高,而且代码实现也不容易,因为要统计 重复的字符,还要适当的替换或者去重。 -那我们还是哈希法吧。如果对哈希法不了解,可以看这篇:[关于哈希表,你该了解这些!](https://mp.weixin.qq.com/s/RSUANESA_tkhKhYe3ZR8Jg)。 +那我们还是哈希法吧。如果对哈希法不了解,可以看这篇:[关于哈希表,你该了解这些!](https://programmercarl.com/哈希表理论基础.html)。 -如果对用数组来做哈希法不了解的话,可以看这篇:[把数组当做哈希表来用,很巧妙!](https://mp.weixin.qq.com/s/ffS8jaVFNUWyfn_8T31IdA)。 +如果对用数组来做哈希法不了解的话,可以看这篇:[把数组当做哈希表来用,很巧妙!](https://programmercarl.com/0242.有效的字母异位词.html)。 了解了哈希法,理解了数组在哈希法中的应用之后,可以来看解题思路了。 @@ -97,7 +97,7 @@ for (int i = 0; i < 26; i++) { 整体C++代码如下: -```C++ +```CPP class Solution { public: vector commonChars(vector& A) { @@ -169,9 +169,168 @@ class Solution { } } ``` +Python +```python +class Solution: + def commonChars(self, words: List[str]) -> List[str]: + if not words: return [] + result = [] + hash = [0] * 26 # 用来统计所有字符串里字符出现的最小频率 + for i, c in enumerate(words[0]): # 用第一个字符串给hash初始化 + hash[ord(c) - ord('a')] += 1 + # 统计除第一个字符串外字符的出现频率 + for i in range(1, len(words)): + hashOtherStr = [0] * 26 + for j in range(len(words[i])): + hashOtherStr[ord(words[i][j]) - ord('a')] += 1 + # 更新hash,保证hash里统计26个字符在所有字符串里出现的最小次数 + for k in range(26): + hash[k] = min(hash[k], hashOtherStr[k]) + # 将hash统计的字符次数,转成输出形式 + for i in range(26): + while hash[i] != 0: # 注意这里是while,多个重复的字符 + result.extend(chr(i + ord('a'))) + hash[i] -= 1 + return result +``` + +Python 3 使用collections.Counter +```python +class Solution: + def commonChars(self, words: List[str]) -> List[str]: + tmp = collections.Counter(words[0]) + l = [] + for i in range(1,len(words)): + # 使用 & 取交集 + tmp = tmp & collections.Counter(words[i]) + + # 剩下的就是每个单词都出现的字符(键),个数(值) + for j in tmp: + v = tmp[j] + while(v): + l.append(j) + v -= 1 + return l +``` + +javaScript +```js +var commonChars = function (words) { + let res = [] + let size = 26 + let firstHash = new Array(size) + for (let i = 0; i < size; i++) { // 初始化 hash 数组 + firstHash[i] = 0 + } + + let a = "a".charCodeAt() + let firstWord = words[0] + for (let i = 0; i < firstWord.length; i++) { // 第 0 个单词的统计 + let idx = firstWord[i].charCodeAt() + firstHash[idx - a] += 1 + } + + for (let i = 1; i < words.length; i++) { // 1-n 个单词统计 + let otherHash = new Array(size) + for (let i = 0; i < size; i++) { // 初始化 hash 数组 + otherHash[i] = 0 + } + + for (let j = 0; j < words[i].length; j++) { + let idx = words[i][j].charCodeAt() + otherHash[idx - a] += 1 + } + for (let i = 0; i < size; i++) { + firstHash[i] = Math.min(firstHash[i], otherHash[i]) + } + } + for (let i = 0; i < size; i++) { + while (firstHash[i] > 0) { + res.push(String.fromCharCode(i + a)) + firstHash[i]-- + } + } + return res +}; +``` +GO +```golang +func commonChars(words []string) []string { + length:=len(words) + fre:=make([][]int,0)//统计每个字符串的词频 + res:=make([]string,0) + //统计词频 + for i:=0;ib{ + return b + } + return a +} +``` + +Swift: +```swift +func commonChars(_ words: [String]) -> [String] { + var res = [String]() + if words.count < 1 { + return res + } + let aUnicodeScalarValue = "a".unicodeScalars.first!.value + let lettersMaxCount = 26 + // 用于统计所有字符串每个字母出现的 最小 频率 + var hash = Array(repeating: 0, count: lettersMaxCount) + // 统计第一个字符串每个字母出现的次数 + for unicodeScalar in words.first!.unicodeScalars { + hash[Int(unicodeScalar.value - aUnicodeScalarValue)] += 1 + } + // 统计除第一个字符串每个字母出现的次数 + for idx in 1 ..< words.count { + var hashOtherStr = Array(repeating: 0, count: lettersMaxCount) + for unicodeScalar in words[idx].unicodeScalars { + hashOtherStr[Int(unicodeScalar.value - aUnicodeScalarValue)] += 1 + } + // 更新hash,保证hash里统计的字母为出现的最小频率 + for k in 0 ..< lettersMaxCount { + hash[k] = min(hash[k], hashOtherStr[k]) + } + } + // 将hash统计的字符次数,转成输出形式 + for i in 0 ..< lettersMaxCount { + while hash[i] != 0 { // 注意这里是while,多个重复的字符 + let currentUnicodeScalarValue: UInt32 = UInt32(i) + aUnicodeScalarValue + let currentUnicodeScalar: UnicodeScalar = UnicodeScalar(currentUnicodeScalarValue)! + let outputStr = String(currentUnicodeScalar) // UnicodeScalar -> String + res.append(outputStr) + hash[i] -= 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) -
+
diff --git a/problems/1005.K次取反后最大化的数组和.md b/problems/1005.K次取反后最大化的数组和.md index 3534e1e2..020476a9 100644 --- a/problems/1005.K次取反后最大化的数组和.md +++ b/problems/1005.K次取反后最大化的数组和.md @@ -9,7 +9,7 @@ ## 1005.K次取反后最大化的数组和 -题目地址:https://leetcode-cn.com/problems/maximize-sum-of-array-after-k-negations/ +[力扣题目链接](https://leetcode-cn.com/problems/maximize-sum-of-array-after-k-negations/) 给定一个整数数组 A,我们只能用以下方法修改该数组:我们选择某个索引 i 并将 A[i] 替换为 -A[i],然后总共重复这个过程 K 次。(我们可以多次选择同一个索引 i。) @@ -61,7 +61,7 @@ 对应C++代码如下: -```C++ +```CPP class Solution { static bool cmp(int a, int b) { return abs(a) > abs(b); @@ -110,18 +110,16 @@ class Solution { int len = nums.length; for (int i = 0; i < len; i++) { //从前向后遍历,遇到负数将其变为正数,同时K-- - if (nums[i] < 0 && k > 0) { + if (nums[i] < 0 && K > 0) { nums[i] = -nums[i]; - k--; + K--; } } // 如果K还大于0,那么反复转变数值最小的元素,将K用完 - if (k % 2 == 1) nums[len - 1] = -nums[len - 1]; - int result = 0; - for (int a : nums) { - result += a; - } - return result; + + if (K % 2 == 1) nums[len - 1] = -nums[len - 1]; + return Arrays.stream(nums).sum(); + } } ``` @@ -219,4 +217,4 @@ var largestSumAfterKNegations = function(nums, k) { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/1035.不相交的线.md b/problems/1035.不相交的线.md index cb74ef75..b71a5199 100644 --- a/problems/1035.不相交的线.md +++ b/problems/1035.不相交的线.md @@ -8,7 +8,7 @@ ## 1035.不相交的线 -题目链接: https://leetcode-cn.com/problems/uncrossed-lines/ +[力扣题目链接](https://leetcode-cn.com/problems/uncrossed-lines/) 我们在两条独立的水平线上按给定的顺序写下 A 和 B 中的整数。 @@ -35,17 +35,17 @@ 这么分析完之后,大家可以发现:**本题说是求绘制的最大连线数,其实就是求两个字符串的最长公共子序列的长度!** -那么本题就和我们刚刚讲过的这道题目[动态规划:1143.最长公共子序列](https://mp.weixin.qq.com/s/Qq0q4HaE4TyasCTj2WGFOg)就是一样一样的了。 +那么本题就和我们刚刚讲过的这道题目[动态规划:1143.最长公共子序列](https://programmercarl.com/1143.最长公共子序列.html)就是一样一样的了。 一样到什么程度呢? 把字符串名字改一下,其他代码都不用改,直接copy过来就行了。 -其实本题就是求最长公共子序列的长度,介于我们刚刚讲过[动态规划:1143.最长公共子序列](https://mp.weixin.qq.com/s/Qq0q4HaE4TyasCTj2WGFOg),所以本题我就不再做动规五部曲分析了。 +其实本题就是求最长公共子序列的长度,介于我们刚刚讲过[动态规划:1143.最长公共子序列](https://programmercarl.com/1143.最长公共子序列.html),所以本题我就不再做动规五部曲分析了。 -如果大家有点遗忘了最长公共子序列,就再看一下这篇:[动态规划:1143.最长公共子序列](https://mp.weixin.qq.com/s/Qq0q4HaE4TyasCTj2WGFOg) +如果大家有点遗忘了最长公共子序列,就再看一下这篇:[动态规划:1143.最长公共子序列](https://programmercarl.com/1143.最长公共子序列.html) 本题代码如下: -```C++ +```CPP class Solution { public: int maxUncrossedLines(vector& A, vector& B) { @@ -66,9 +66,9 @@ public: ## 总结 -看到代码大家也可以发现其实就是求两个字符串的最长公共子序列,但如果没有做过[1143.最长公共子序列](https://mp.weixin.qq.com/s/Qq0q4HaE4TyasCTj2WGFOg),本题其实还有很有难度的。 +看到代码大家也可以发现其实就是求两个字符串的最长公共子序列,但如果没有做过[1143.最长公共子序列](https://programmercarl.com/1143.最长公共子序列.html),本题其实还有很有难度的。 -这是Carl为什么要先讲[1143.最长公共子序列](https://mp.weixin.qq.com/s/Qq0q4HaE4TyasCTj2WGFOg)再讲本题,大家会发现一个正确的刷题顺序对算法学习是非常重要的! +这是Carl为什么要先讲[1143.最长公共子序列](https://programmercarl.com/1143.最长公共子序列.html)再讲本题,大家会发现一个正确的刷题顺序对算法学习是非常重要的! 这也是Carl做了很多题目(包括ACM和力扣)才总结出来的规律,大家仔细体会一下哈。 @@ -109,7 +109,28 @@ class Solution: return dp[-1][-1] ``` -Go: +JavaScript: + +```javascript +const maxUncrossedLines = (nums1, nums2) => { + // 两个数组长度 + const [m, n] = [nums1.length, nums2.length]; + // 创建dp数组并都初始化为0 + const dp = new Array(m + 1).fill(0).map(x => new Array(n + 1).fill(0)); + for (let i = 1; i <= m; i++) { + for (let j = 1; j <= n; j++) { + // 根据两种情况更新dp[i][j] + if (nums1[i - 1] === nums2[j - 1]) { + dp[i][j] = dp[i - 1][j - 1] + 1; + } else { + dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]); + } + } + } + // 返回dp数组中右下角的元素 + return dp[m][n]; +}; +``` @@ -118,4 +139,4 @@ Go: * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/1047.删除字符串中的所有相邻重复项.md b/problems/1047.删除字符串中的所有相邻重复项.md index c6a49376..b88fd618 100644 --- a/problems/1047.删除字符串中的所有相邻重复项.md +++ b/problems/1047.删除字符串中的所有相邻重复项.md @@ -13,7 +13,7 @@ # 1047. 删除字符串中的所有相邻重复项 -https://leetcode-cn.com/problems/remove-all-adjacent-duplicates-in-string/ +[力扣题目链接](https://leetcode-cn.com/problems/remove-all-adjacent-duplicates-in-string/) 给出由小写字母组成的字符串 S,重复项删除操作会选择两个相邻且相同的字母,并删除它们。 @@ -26,7 +26,7 @@ https://leetcode-cn.com/problems/remove-all-adjacent-duplicates-in-string/ * 输入:"abbaca" * 输出:"ca" * 解释:例如,在 "abbaca" 中,我们可以删除 "bb" 由于两字母相邻且相同,这是此时唯一可以执行删除操作的重复项。之后我们得到字符串 "aaca",其中又只有 "aa" 可以执行重复项删除操作,所以最后的字符串为 "ca"。 -  + 提示: * 1 <= S.length <= 20000 @@ -69,7 +69,7 @@ https://leetcode-cn.com/problems/remove-all-adjacent-duplicates-in-string/ C++代码 : -```C++ +```CPP class Solution { public: string removeDuplicates(string S) { @@ -97,7 +97,7 @@ public: 代码如下: -```C++ +```CPP class Solution { public: string removeDuplicates(string S) { @@ -197,15 +197,38 @@ class Solution { Python: ```python3 +# 方法一,使用栈,推荐! class Solution: def removeDuplicates(self, s: str) -> str: - t = list() - for i in s: - if t and t[-1] == i: - t.pop(-1) + res = list() + for item in s: + if res and res[-1] == item: + res.pop() else: - t.append(i) - return "".join(t) # 字符串拼接 + res.append(item) + return "".join(res) # 字符串拼接 +``` + +```python3 +# 方法二,使用双指针模拟栈,如果不让用栈可以作为备选方法。 +class Solution: + def removeDuplicates(self, s: str) -> str: + res = list(s) + slow = fast = 0 + length = len(res) + + while fast < length: + # 如果一样直接换,不一样会把后面的填在slow的位置 + res[slow] = res[fast] + + # 如果发现和前一个一样,就退一格指针 + if slow > 0 and res[slow] == res[slow - 1]: + slow -= 1 + else: + slow += 1 + fast += 1 + + return ''.join(res[0: slow]) ``` Go: @@ -252,4 +275,4 @@ var removeDuplicates = function(s) { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/1049.最后一块石头的重量II.md b/problems/1049.最后一块石头的重量II.md index 59c59189..795d923c 100644 --- a/problems/1049.最后一块石头的重量II.md +++ b/problems/1049.最后一块石头的重量II.md @@ -9,7 +9,7 @@ ## 1049. 最后一块石头的重量 II -题目链接:https://leetcode-cn.com/problems/last-stone-weight-ii/ +[力扣题目链接](https://leetcode-cn.com/problems/last-stone-weight-ii/) 题目难度:中等 @@ -29,7 +29,7 @@ 组合 7 和 8,得到 1,所以数组转化为 [2,1,1,1], 组合 2 和 1,得到 1,所以数组转化为 [1,1,1], 组合 1 和 1,得到 0,所以数组转化为 [1],这就是最优值。 -  + 提示: * 1 <= stones.length <= 30 @@ -39,12 +39,12 @@ 如果对背包问题不都熟悉先看这两篇: -* [动态规划:关于01背包问题,你该了解这些!](https://mp.weixin.qq.com/s/FwIiPPmR18_AJO5eiidT6w) -* [动态规划:关于01背包问题,你该了解这些!(滚动数组)](https://mp.weixin.qq.com/s/M4uHxNVKRKm5HPjkNZBnFA) +* [动态规划:关于01背包问题,你该了解这些!](https://programmercarl.com/背包理论基础01背包-1.html) +* [动态规划:关于01背包问题,你该了解这些!(滚动数组)](https://programmercarl.com/背包理论基础01背包-2.html) 本题其实就是尽量让石头分成重量相同的两堆,相撞之后剩下的石头最小,**这样就化解成01背包问题了**。 -是不是感觉和昨天讲解的[416. 分割等和子集](https://mp.weixin.qq.com/s/sYw3QtPPQ5HMZCJcT4EaLQ)非常像了。 +是不是感觉和昨天讲解的[416. 分割等和子集](https://programmercarl.com/0416.分割等和子集.html)非常像了。 本题物品的重量为store[i],物品的价值也为store[i]。 @@ -89,11 +89,11 @@ vector dp(15001, 0); 4. 确定遍历顺序 -在[动态规划:关于01背包问题,你该了解这些!(滚动数组)](https://mp.weixin.qq.com/s/M4uHxNVKRKm5HPjkNZBnFA)中就已经说明:如果使用一维dp数组,物品遍历的for循环放在外层,遍历背包的for循环放在内层,且内层for循环倒叙遍历! +在[动态规划:关于01背包问题,你该了解这些!(滚动数组)](https://programmercarl.com/背包理论基础01背包-2.html)中就已经说明:如果使用一维dp数组,物品遍历的for循环放在外层,遍历背包的for循环放在内层,且内层for循环倒叙遍历! 代码如下: -```C++ +```CPP for (int i = 0; i < stones.size(); i++) { // 遍历物品 for (int j = target; j >= stones[i]; j--) { // 遍历背包 dp[j] = max(dp[j], dp[j - stones[i]] + stones[i]); @@ -119,7 +119,7 @@ for (int i = 0; i < stones.size(); i++) { // 遍历物品 以上分析完毕,C++代码如下: -```C++ +```CPP class Solution { public: int lastStoneWeightII(vector& stones) { @@ -143,9 +143,9 @@ public: ## 总结 -本题其实和[416. 分割等和子集](https://mp.weixin.qq.com/s/sYw3QtPPQ5HMZCJcT4EaLQ)几乎是一样的,只是最后对dp[target]的处理方式不同。 +本题其实和[416. 分割等和子集](https://programmercarl.com/0416.分割等和子集.html)几乎是一样的,只是最后对dp[target]的处理方式不同。 -[416. 分割等和子集](https://mp.weixin.qq.com/s/sYw3QtPPQ5HMZCJcT4EaLQ)相当于是求背包是否正好装满,而本题是求背包最多能装多少。 +[416. 分割等和子集](https://programmercarl.com/0416.分割等和子集.html)相当于是求背包是否正好装满,而本题是求背包最多能装多少。 @@ -246,4 +246,4 @@ var lastStoneWeightII = function (stones) { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/1143.最长公共子序列.md b/problems/1143.最长公共子序列.md index e1fc1abb..b3b5e6c0 100644 --- a/problems/1143.最长公共子序列.md +++ b/problems/1143.最长公共子序列.md @@ -8,7 +8,7 @@ ## 1143.最长公共子序列 -题目链接: https://leetcode-cn.com/problems/longest-common-subsequence/ +[力扣题目链接](https://leetcode-cn.com/problems/longest-common-subsequence/) 给定两个字符串 text1 和 text2,返回这两个字符串的最长公共子序列的长度。 @@ -41,7 +41,7 @@ ## 思路 -本题和[动态规划:718. 最长重复子数组](https://mp.weixin.qq.com/s/U5WaWqBwdoxzQDotOdWqZg)区别在于这里不要求是连续的了,但要有相对顺序,即:"ace" 是 "abcde" 的子序列,但 "aec" 不是 "abcde" 的子序列。 +本题和[动态规划:718. 最长重复子数组](https://programmercarl.com/0718.最长重复子数组.html)区别在于这里不要求是连续的了,但要有相对顺序,即:"ace" 是 "abcde" 的子序列,但 "aec" 不是 "abcde" 的子序列。 继续动规五部曲分析如下: @@ -65,7 +65,7 @@ dp[i][j]:长度为[0, i - 1]的字符串text1与长度为[0, j - 1]的字符 代码如下: -```C++ +```CPP if (text1[i - 1] == text2[j - 1]) { dp[i][j] = dp[i - 1][j - 1] + 1; } else { @@ -107,7 +107,7 @@ vector> dp(text1.size() + 1, vector(text2.size() + 1, 0)); 以上分析完毕,C++代码如下: -```C++ +```CPP class Solution { public: int longestCommonSubsequence(string text1, string text2) { @@ -221,4 +221,4 @@ const longestCommonSubsequence = (text1, text2) => { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/1207.独一无二的出现次数.md b/problems/1207.独一无二的出现次数.md index c1720430..027c9f5a 100644 --- a/problems/1207.独一无二的出现次数.md +++ b/problems/1207.独一无二的出现次数.md @@ -8,7 +8,7 @@ # 1207.独一无二的出现次数 -链接:https://leetcode-cn.com/problems/unique-number-of-occurrences/ +[力扣题目链接](https://leetcode-cn.com/problems/unique-number-of-occurrences/) 给你一个整数数组 arr,请你帮忙统计数组中每个数的出现次数。 @@ -35,9 +35,9 @@ # 思路 -这道题目数组在是哈希法中的经典应用,如果对数组在哈希法中的使用还不熟悉的同学可以看这两篇:[数组在哈希法中的应用](https://mp.weixin.qq.com/s/ffS8jaVFNUWyfn_8T31IdA)和[哈希法:383. 赎金信](https://mp.weixin.qq.com/s/qAXqv--UERmiJNNpuphOUQ) +这道题目数组在是哈希法中的经典应用,如果对数组在哈希法中的使用还不熟悉的同学可以看这两篇:[数组在哈希法中的应用](https://programmercarl.com/0242.有效的字母异位词.html)和[哈希法:383. 赎金信](https://programmercarl.com/0383.赎金信.html) -进而可以学习一下[set在哈希法中的应用](https://mp.weixin.qq.com/s/aMSA5zrp3jJcLjuSB0Es2Q),以及[map在哈希法中的应用](https://mp.weixin.qq.com/s/vaMsLnH-f7_9nEK4Cuu3KQ) +进而可以学习一下[set在哈希法中的应用](https://programmercarl.com/0349.两个数组的交集.html),以及[map在哈希法中的应用](https://programmercarl.com/0001.两数之和.html) 回归本题,**本题强调了-1000 <= arr[i] <= 1000**,那么就可以用数组来做哈希,arr[i]作为哈希表(数组)的下标,那么arr[i]可以是负数,怎么办?负数不能做数组下标。 @@ -53,7 +53,7 @@ C++代码如下: -```C++ +```CPP class Solution { public: bool uniqueOccurrences(vector& arr) { @@ -100,7 +100,21 @@ class Solution { ``` Python: - +```python +class Solution: + def uniqueOccurrences(self, arr: List[int]) -> bool: + count = [0] * 2002 + for i in range(len(arr)): + count[arr[i] + 1000] += 1 # 防止负数作为下标 + freq = [False] * 1002 # 标记相同频率是否重复出现 + for i in range(2001): + if count[i] > 0: + if freq[count[i]] == False: + freq[count[i]] = True + else: + return False + return True +``` Go: JavaScript: @@ -109,5 +123,5 @@ JavaScript: * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/1221.分割平衡字符串.md b/problems/1221.分割平衡字符串.md new file mode 100644 index 00000000..c764e3ff --- /dev/null +++ b/problems/1221.分割平衡字符串.md @@ -0,0 +1,118 @@ +

+ + + + +

+

欢迎大家参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!

+ +# 1221. 分割平衡字符串 + +[力扣题目链接](https://leetcode-cn.com/problems/split-a-string-in-balanced-strings/) + +在一个 平衡字符串 中,'L' 和 'R' 字符的数量是相同的。 + +给你一个平衡字符串 s,请你将它分割成尽可能多的平衡字符串。 + +注意:分割得到的每个字符串都必须是平衡字符串。 + +返回可以通过分割得到的平衡字符串的 最大数量 。 + + +示例 1: + +* 输入:s = "RLRRLLRLRL" +* 输出:4 +* 解释:s 可以分割为 "RL"、"RRLL"、"RL"、"RL" ,每个子字符串中都包含相同数量的 'L' 和 'R' 。 + +示例 2: +* 输入:s = "RLLLLRRRLR" +* 输出:3 +* 解释:s 可以分割为 "RL"、"LLLRRR"、"LR" ,每个子字符串中都包含相同数量的 'L' 和 'R' 。 + +示例 3: +* 输入:s = "LLLLRRRR" +* 输出:1 +* 解释:s 只能保持原样 "LLLLRRRR". + +示例 4: +* 输入:s = "RLRRRLLRLL" +* 输出:2 +* 解释:s 可以分割为 "RL"、"RRRLLRLL" ,每个子字符串中都包含相同数量的 'L' 和 'R' 。 + +# 思路 + +这道题目看起来好像很复杂,其实是非常简单的贪心,关于贪心,我在这里[关于贪心算法,你该了解这些!](https://programmercarl.com/贪心算法理论基础.html)有详细的讲解。 + +从前向后遍历,只要遇到平衡子串,计数就+1,遍历一遍即可。 + +局部最优:从前向后遍历,只要遇到平衡子串 就统计 + +全局最优:统计了最多的平衡子串。 + +局部最优可以推出全局最优,举不出反例,那么就试试贪心。 + + +例如,LRLR 这本身就是平衡子串 , 但要遇到LR就可以分割。 + +C++代码如下: + +```CPP +class Solution { +public: + int balancedStringSplit(string s) { + int result = 0; + int count = 0; + for (int i = 0; i < s.size(); i++) { + if (s[i] == 'R') count++; + else count--; + if (count == 0) result++; + } + return result; + } +}; +``` + +# 拓展 + +一些同学可能想,你这个推理不靠谱,都没有数学证明。怎么就能说是合理的呢,怎么就能说明 局部最优可以推出全局最优呢? + +一般数学证明有如下两种方法: + +* 数学归纳法 +* 反证法 + +如果真的去严格数学证明其实不是在我们刷题或者 面试的考察范围内了。 + +所以贪心题目的思考过程是: 如果发现局部最优好像可以推出全局最优,那么就 尝试一下举反例,如果举不出反例,那么就试试贪心。 + + + +# 其他语言版本 + +## Java + +```java +``` + +## Python + +```python +``` + +## Go + +```go +``` + +## JavaScript + +```js +``` + +----------------------- +* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) +* B站视频:[代码随想录](https://space.bilibili.com/525438321) +* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) + +
diff --git a/problems/1356.根据数字二进制下1的数目排序.md b/problems/1356.根据数字二进制下1的数目排序.md index e464f716..06c29500 100644 --- a/problems/1356.根据数字二进制下1的数目排序.md +++ b/problems/1356.根据数字二进制下1的数目排序.md @@ -7,112 +7,115 @@

欢迎大家参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!

-# 1365.有多少小于当前数字的数字 + +# 1356. 根据数字二进制下 1 的数目排序 + +[力扣题目链接](https://leetcode-cn.com/problems/sort-integers-by-the-number-of-1-bits/) 题目链接:https://leetcode-cn.com/problems/sort-integers-by-the-number-of-1-bits/ -给你一个数组 nums,对于其中每个元素 nums[i],请你统计数组中比它小的所有数字的数目。 +给你一个整数数组 arr 。请你将数组中的元素按照其二进制表示中数字 1 的数目升序排序。 -换而言之,对于每个 nums[i] 你必须计算出有效的 j 的数量,其中 j 满足 j != i 且 nums[j] < nums[i] 。 +如果存在多个数字二进制中 1 的数目相同,则必须将它们按照数值大小升序排列。 -以数组形式返回答案。 -  +请你返回排序后的数组。 示例 1: -输入:nums = [8,1,2,2,3] -输出:[4,0,1,1,3] -解释: -对于 nums[0]=8 存在四个比它小的数字:(1,2,2 和 3)。 -对于 nums[1]=1 不存在比它小的数字。 -对于 nums[2]=2 存在一个比它小的数字:(1)。 -对于 nums[3]=2 存在一个比它小的数字:(1)。 -对于 nums[4]=3 存在三个比它小的数字:(1,2 和 2)。 +* 输入:arr = [0,1,2,3,4,5,6,7,8] +* 输出:[0,1,2,4,8,3,5,6,7] +* 解释:[0] 是唯一一个有 0 个 1 的数。 +[1,2,4,8] 都有 1 个 1 。 +[3,5,6] 有 2 个 1 。 +[7] 有 3 个 1 。按照 1 的个数排序得到的结果数组为 [0,1,2,4,8,3,5,6,7] + 示例 2: -输入:nums = [6,5,4,8] -输出:[2,1,0,3] +* 输入:arr = [1024,512,256,128,64,32,16,8,4,2,1] +* 输出:[1,2,4,8,16,32,64,128,256,512,1024] +* 解释:数组中所有整数二进制下都只有 1 个 1 ,所以你需要按照数值大小将它们排序。 示例 3: -输入:nums = [7,7,7,7] -输出:[0,0,0,0] -  -提示: -* 2 <= nums.length <= 500 -* 0 <= nums[i] <= 100 +* 输入:arr = [10000,10000] +* 输出:[10000,10000] + +示例 4: +* 输入:arr = [2,3,5,7,11,13,17,19] +* 输出:[2,3,5,17,7,11,13,19] + +示例 5: +* 输入:arr = [10,100,1000,10000] +* 输出:[10,100,10000,1000] + + # 思路 -两层for循环暴力查找,时间复杂度明显为O(n^2)。 +这道题其实是考察如何计算一个数的二进制中1的数量。 -那么我们来看一下如何优化。 +我提供两种方法: -首先要找小于当前数字的数字,那么从小到大排序之后,该数字之前的数字就都是比它小的了。 +* 方法一: -所以可以定义一个新数组,将数组排个序。 - -**排序之后,其实每一个数值的下标就代表这前面有几个比它小的了**。 - -代码如下: - -``` -vector vec = nums; -sort(vec.begin(), vec.end()); // 从小到大排序之后,元素下标就是小于当前数字的数字 -``` - -此时用一个哈希表hash(本题可以就用一个数组)来做数值和下标的映射。这样就可以通过数值快速知道下标(也就是前面有几个比它小的)。 - -此时有一个情况,就是数值相同怎么办? - -例如,数组:1 2 3 4 4 4 ,第一个数值4的下标是3,第二个数值4的下标是4了。 - -这里就需要一个技巧了,**在构造数组hash的时候,从后向前遍历,这样hash里存放的就是相同元素最左面的数值和下标了**。 -代码如下: +朴实无华挨个计算1的数量,最多就是循环n的二进制位数,32位。 ```C++ -int hash[101]; -for (int i = vec.size() - 1; i >= 0; i--) { // 从后向前,记录 vec[i] 对应的下标 - hash[vec[i]] = i; +int bitCount(int n) { + int count = 0; // 计数器 + while (n > 0) { + if((n & 1) == 1) count++; // 当前位是1,count++ + n >>= 1 ; // n向右移位 + } + return count; } ``` -最后在遍历原数组nums,用hash快速找到每一个数值 对应的 小于这个数值的个数。存放在将结果存放在另一个数组中。 +* 方法二 -代码如下: +这种方法,只循环n的二进制中1的个数次,比方法一高效的多 ```C++ -// 此时hash里保存的每一个元素数值 对应的 小于这个数值的个数 -for (int i = 0; i < nums.size(); i++) { - vec[i] = hash[nums[i]]; +int bitCount(int n) { + int count = 0; + while (n) { + n &= (n - 1); // 清除最低位的1 + count++; + } + return count; } ``` +以计算12的二进制1的数量为例,如图所示: -流程如图: + - +下面我就使用方法二,来做这道题目: -关键地方讲完了,整体C++代码如下: +## C++代码 ```C++ class Solution { +private: + static int bitCount(int n) { // 计算n的二进制中1的数量 + int count = 0; + while(n) { + n &= (n -1); // 清除最低位的1 + count++; + } + return count; + } + static bool cmp(int a, int b) { + int bitA = bitCount(a); + int bitB = bitCount(b); + if (bitA == bitB) return a < b; // 如果bit中1数量相同,比较数值大小 + return bitA < bitB; // 否则比较bit中1数量大小 + } public: - vector smallerNumbersThanCurrent(vector& nums) { - vector vec = nums; - sort(vec.begin(), vec.end()); // 从小到大排序之后,元素下标就是小于当前数字的数字 - int hash[101]; - for (int i = vec.size() - 1; i >= 0; i--) { // 从后向前,记录 vec[i] 对应的下标 - hash[vec[i]] = i; - } - // 此时hash里保存的每一个元素数值 对应的 小于这个数值的个数 - for (int i = 0; i < nums.size(); i++) { - vec[i] = hash[nums[i]]; - } - return vec; + vector sortByBits(vector& arr) { + sort(arr.begin(), arr.end(), cmp); + return arr; } }; ``` -可以排序之后加哈希,时间复杂度为O(nlogn) - # 其他语言版本 @@ -120,8 +123,36 @@ public: ## Java ```java +class Solution { + private int cntInt(int val){ + int count = 0; + while(val > 0) { + val = val & (val - 1); + count ++; + } + + return count; + } + + public int[] sortByBits(int[] arr) { + return Arrays.stream(arr).boxed() + .sorted(new Comparator(){ + @Override + public int compare(Integer o1, Integer o2) { + int cnt1 = cntInt(o1); + int cnt2 = cntInt(o2); + return (cnt1 == cnt2) ? Integer.compare(o1, o2) : Integer.compare(cnt1, cnt2); + } + }) + .mapToInt(Integer::intValue) + .toArray(); + } +} ``` + + + ## Python ```python @@ -141,6 +172,5 @@ public: * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
- +
diff --git a/problems/1365.有多少小于当前数字的数字.md b/problems/1365.有多少小于当前数字的数字.md index 6324329c..5cf6b2d8 100644 --- a/problems/1365.有多少小于当前数字的数字.md +++ b/problems/1365.有多少小于当前数字的数字.md @@ -10,14 +10,14 @@ # 1365.有多少小于当前数字的数字 -题目链接:https://leetcode-cn.com/problems/how-many-numbers-are-smaller-than-the-current-number/ +[力扣题目链接](https://leetcode-cn.com/problems/how-many-numbers-are-smaller-than-the-current-number/) 给你一个数组 nums,对于其中每个元素 nums[i],请你统计数组中比它小的所有数字的数目。 换而言之,对于每个 nums[i] 你必须计算出有效的 j 的数量,其中 j 满足 j != i 且 nums[j] < nums[i] 。 以数组形式返回答案。 -  + 示例 1: * 输入:nums = [8,1,2,2,3] @@ -36,7 +36,7 @@ 示例 3: * 输入:nums = [7,7,7,7] * 输出:[0,0,0,0] -  + 提示: * 2 <= nums.length <= 500 * 0 <= nums[i] <= 100 @@ -69,7 +69,7 @@ sort(vec.begin(), vec.end()); // 从小到大排序之后,元素下标就是 这里就需要一个技巧了,**在构造数组hash的时候,从后向前遍历,这样hash里存放的就是相同元素最左面的数值和下标了**。 代码如下: -```C++ +```CPP int hash[101]; for (int i = vec.size() - 1; i >= 0; i--) { // 从后向前,记录 vec[i] 对应的下标 hash[vec[i]] = i; @@ -80,7 +80,7 @@ for (int i = vec.size() - 1; i >= 0; i--) { // 从后向前,记录 vec[i] 对 代码如下: -```C++ +```CPP // 此时hash里保存的每一个元素数值 对应的 小于这个数值的个数 for (int i = 0; i < nums.size(); i++) { vec[i] = hash[nums[i]]; @@ -93,7 +93,7 @@ for (int i = 0; i < nums.size(); i++) { 关键地方讲完了,整体C++代码如下: -```C++ +```CPP class Solution { public: vector smallerNumbersThanCurrent(vector& nums) { @@ -139,7 +139,19 @@ public int[] smallerNumbersThanCurrent(int[] nums) { ``` Python: - +```python +class Solution: + def smallerNumbersThanCurrent(self, nums: List[int]) -> List[int]: + res = nums[:] + hash = dict() + res.sort() # 从小到大排序之后,元素下标就是小于当前数字的数字 + for i, num in enumerate(res): + if num not in hash.keys(): # 遇到了相同的数字,那么不需要更新该 number 的情况 + hash[num] = i + for i, num in enumerate(nums): + res[i] = hash[num] + return res +``` Go: JavaScript: @@ -150,4 +162,4 @@ JavaScript: * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/1382.将二叉搜索树变平衡.md b/problems/1382.将二叉搜索树变平衡.md index 268f21c5..bce58c33 100644 --- a/problems/1382.将二叉搜索树变平衡.md +++ b/problems/1382.将二叉搜索树变平衡.md @@ -9,7 +9,7 @@ # 1382.将二叉搜索树变平衡 -题目地址:https://leetcode-cn.com/problems/balance-a-binary-search-tree/ +[力扣题目链接](https://leetcode-cn.com/problems/balance-a-binary-search-tree/) 给你一棵二叉搜索树,请你返回一棵 平衡后 的二叉搜索树,新生成的树应该与原来的树有着相同的节点值。 @@ -35,14 +35,14 @@ 这道题目,可以中序遍历把二叉树转变为有序数组,然后在根据有序数组构造平衡二叉搜索树。 建议做这道题之前,先看如下两篇题解: -* [98.验证二叉搜索树](https://mp.weixin.qq.com/s/8odY9iUX5eSi0eRFSXFD4Q) 学习二叉搜索树的特性 -* [108.将有序数组转换为二叉搜索树](https://mp.weixin.qq.com/s/sy3ygnouaZVJs8lhFgl9mw) 学习如何通过有序数组构造二叉搜索树 +* [98.验证二叉搜索树](https://programmercarl.com/0098.验证二叉搜索树.html) 学习二叉搜索树的特性 +* [108.将有序数组转换为二叉搜索树](https://programmercarl.com/0108.将有序数组转换为二叉搜索树.html) 学习如何通过有序数组构造二叉搜索树 这两道题目做过之后,本题分分钟就可以做出来了。 代码如下: -```C++ +```CPP class Solution { private: vector vec; @@ -55,7 +55,7 @@ private: vec.push_back(cur->val); traversal(cur->right); } - 有序数组转平衡二叉树 + // 有序数组转平衡二叉树 TreeNode* getTree(vector& nums, int left, int right) { if (left > right) return nullptr; int mid = left + ((right - left) / 2); @@ -76,9 +76,53 @@ public: # 其他语言版本 Java: - +```java +class Solution { + ArrayList res = new ArrayList(); + // 有序树转成有序数组 + private void travesal(TreeNode cur) { + if (cur == null) return; + travesal(cur.left); + res.add(cur.val); + travesal(cur.right); + } + // 有序数组转成平衡二叉树 + private TreeNode getTree(ArrayList nums, int left, int right) { + if (left > right) return null; + int mid = left + (right - left) / 2; + TreeNode root = new TreeNode(nums.get(mid)); + root.left = getTree(nums, left, mid - 1); + root.right = getTree(nums, mid + 1, right); + return root; + } + public TreeNode balanceBST(TreeNode root) { + travesal(root); + return getTree(res, 0, res.size() - 1); + } +} +``` Python: - +```python +class Solution: + def balanceBST(self, root: TreeNode) -> TreeNode: + res = [] + # 有序树转成有序数组 + def traversal(cur: TreeNode): + if not cur: return + traversal(cur.left) + res.append(cur.val) + traversal(cur.right) + # 有序数组转成平衡二叉树 + def getTree(nums: List, left, right): + if left > right: return + mid = left + (right -left) // 2 + root = TreeNode(nums[mid]) + root.left = getTree(nums, left, mid - 1) + root.right = getTree(nums, mid + 1, right) + return root + traversal(root) + return getTree(res, 0, len(res) - 1) +``` Go: JavaScript: @@ -87,6 +131,6 @@ JavaScript: * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/O(n)的算法居然超时了,此时的n究竟是多大?.md b/problems/O(n)的算法居然超时了,此时的n究竟是多大?.md index 2b995c59..6897c01f 100644 --- a/problems/O(n)的算法居然超时了,此时的n究竟是多大?.md +++ b/problems/O(n)的算法居然超时了,此时的n究竟是多大?.md @@ -67,7 +67,7 @@ 实现三个函数,时间复杂度分别是 O(n) , O(n^2), O(nlogn),使用加法运算来统一测试。 -```C++ +```CPP // O(n) void function1(long long n) { long long k = 0; @@ -78,7 +78,7 @@ void function1(long long n) { ``` -```C++ +```CPP // O(n^2) void function2(long long n) { long long k = 0; @@ -91,7 +91,7 @@ void function2(long long n) { } ``` -```C++ +```CPP // O(nlogn) void function3(long long n) { long long k = 0; @@ -105,7 +105,7 @@ void function3(long long n) { ``` 来看一下这三个函数随着n的规模变化,耗时会产生多大的变化,先测function1 ,就把 function2 和 function3 注释掉 -```C++ +```CPP int main() { long long n; // 数据规模 while (1) { @@ -154,7 +154,7 @@ O(nlogn)的算法,1s内大概计算机可以运行 2 * (10^7)次计算,符 # 完整测试代码 -```C++ +```CPP #include #include #include @@ -235,4 +235,4 @@ Go: * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/为了绝杀编辑距离,卡尔做了三步铺垫.md b/problems/为了绝杀编辑距离,卡尔做了三步铺垫.md index 353bd68e..2eb253ba 100644 --- a/problems/为了绝杀编辑距离,卡尔做了三步铺垫.md +++ b/problems/为了绝杀编辑距离,卡尔做了三步铺垫.md @@ -14,7 +14,7 @@ ## 判断子序列 -[动态规划:392.判断子序列](https://mp.weixin.qq.com/s/2pjT4B4fjfOx5iB6N6xyng) 给定字符串 s 和 t ,判断 s 是否为 t 的子序列。 +[动态规划:392.判断子序列](https://programmercarl.com/0392.判断子序列.html) 给定字符串 s 和 t ,判断 s 是否为 t 的子序列。 这道题目 其实是可以用双指针或者贪心的的,但是我在开篇的时候就说了这是编辑距离的入门题目,因为从题意中我们也可以发现,只需要计算删除的情况,不用考虑增加和替换的情况。 @@ -33,9 +33,9 @@ else dp[i][j] = dp[i][j - 1]; ## 不同的子序列 -[动态规划:115.不同的子序列](https://mp.weixin.qq.com/s/1SULY2XVSROtk_hsoVLu8A) 给定一个字符串 s 和一个字符串 t ,计算在 s 的子序列中 t 出现的个数。 +[动态规划:115.不同的子序列](https://programmercarl.com/0115.不同的子序列.html) 给定一个字符串 s 和一个字符串 t ,计算在 s 的子序列中 t 出现的个数。 -本题虽然也只有删除操作,不用考虑替换增加之类的,但相对于[动态规划:392.判断子序列](https://mp.weixin.qq.com/s/2pjT4B4fjfOx5iB6N6xyng)就有难度了,这道题目双指针法可就做不了。 +本题虽然也只有删除操作,不用考虑替换增加之类的,但相对于[动态规划:392.判断子序列](https://programmercarl.com/0392.判断子序列.html)就有难度了,这道题目双指针法可就做不了。 当s[i - 1] 与 t[j - 1]相等时,dp[i][j]可以有两部分组成。 @@ -58,7 +58,7 @@ else dp[i][j] = dp[i][j - 1]; 状态转移方程: -```C++ +```CPP if (s[i - 1] == t[j - 1]) { dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j]; } else { @@ -68,9 +68,9 @@ if (s[i - 1] == t[j - 1]) { ## 两个字符串的删除操作 -[动态规划:583.两个字符串的删除操作](https://mp.weixin.qq.com/s/a8BerpqSf76DCqkPDJrpYg)给定两个单词 word1 和 word2,找到使得 word1 和 word2 相同所需的最小步数,每步可以删除任意一个字符串中的一个字符。 +[动态规划:583.两个字符串的删除操作](https://programmercarl.com/0583.两个字符串的删除操作.html)给定两个单词 word1 和 word2,找到使得 word1 和 word2 相同所需的最小步数,每步可以删除任意一个字符串中的一个字符。 -本题和[动态规划:115.不同的子序列](https://mp.weixin.qq.com/s/1SULY2XVSROtk_hsoVLu8A)相比,其实就是两个字符串可以都可以删除了,情况虽说复杂一些,但整体思路是不变的。 +本题和[动态规划:115.不同的子序列](https://programmercarl.com/0115.不同的子序列.html)相比,其实就是两个字符串可以都可以删除了,情况虽说复杂一些,但整体思路是不变的。 * 当word1[i - 1] 与 word2[j - 1]相同的时候 @@ -89,7 +89,7 @@ if (s[i - 1] == t[j - 1]) { 那最后当然是取最小值,所以当word1[i - 1] 与 word2[j - 1]不相同的时候,递推公式:dp[i][j] = min({dp[i - 1][j - 1] + 2, dp[i - 1][j] + 1, dp[i][j - 1] + 1}); 状态转移方程: -```C++ +```CPP if (word1[i - 1] == word2[j - 1]) { dp[i][j] = dp[i - 1][j - 1]; } else { @@ -100,10 +100,10 @@ if (word1[i - 1] == word2[j - 1]) { ## 编辑距离 -[动态规划:72.编辑距离](https://mp.weixin.qq.com/s/8aG71XjSgZG6kZbiAdkJnQ) 给你两个单词 word1 和 word2,请你计算出将 word1 转换成 word2 所使用的最少操作数 。 +[动态规划:72.编辑距离](https://programmercarl.com/0072.编辑距离.html) 给你两个单词 word1 和 word2,请你计算出将 word1 转换成 word2 所使用的最少操作数 。 -编辑距离终于来了,**有了前面三道题目的铺垫,应该有思路了**,本题是两个字符串可以增删改,比 [动态规划:判断子序列](https://mp.weixin.qq.com/s/2pjT4B4fjfOx5iB6N6xyng),[动态规划:不同的子序列](https://mp.weixin.qq.com/s/1SULY2XVSROtk_hsoVLu8A),[动态规划:两个字符串的删除操作](https://mp.weixin.qq.com/s/a8BerpqSf76DCqkPDJrpYg)都要复杂的多。 +编辑距离终于来了,**有了前面三道题目的铺垫,应该有思路了**,本题是两个字符串可以增删改,比 [动态规划:判断子序列](https://programmercarl.com/0392.判断子序列.html),[动态规划:不同的子序列](https://programmercarl.com/0115.不同的子序列.html),[动态规划:两个字符串的删除操作](https://programmercarl.com/0583.两个字符串的删除操作.html)都要复杂的多。 在确定递推公式的时候,首先要考虑清楚编辑的几种操作,整理如下: @@ -150,7 +150,7 @@ if (word1[i - 1] != word2[j - 1]),此时就需要编辑了,如何编辑呢 递归公式代码如下: -```C++ +```CPP if (word1[i - 1] == word2[j - 1]) { dp[i][j] = dp[i - 1][j - 1]; } @@ -161,13 +161,39 @@ else { ## 总结 -心思的录友应该会发现我用了三道题做铺垫,才最后引出了[动态规划:72.编辑距离](https://mp.weixin.qq.com/s/8aG71XjSgZG6kZbiAdkJnQ) ,Carl的良苦用心呀,你们体会到了嘛! +心思的录友应该会发现我用了三道题做铺垫,才最后引出了[动态规划:72.编辑距离](https://programmercarl.com/0072.编辑距离.html) ,Carl的良苦用心呀,你们体会到了嘛! ## 其他语言版本 Java: - +```java +class Solution { + public int minDistance(String word1, String word2) { + int m = word1.length(); + int n = word2.length(); + int[][] dp = new int[m+1][n+1]; + for(int i = 1; i <= m; i++){ + dp[i][0] = i; + } + for(int i = 1; i <= n; i++){ + dp[0][i] = i; + } + for(int i = 1; i <= m; i++){ + for(int j = 1; j <= n; j++){ + int left = dp[i][j-1]+1; + int mid = dp[i-1][j-1]; + int right = dp[i-1][j]+1; + if(word1.charAt(i-1) != word2.charAt(j-1)){ + mid ++; + } + dp[i][j] = Math.min(left,Math.min(mid,right)); + } + } + return dp[m][n]; + } +} +``` Python: @@ -181,4 +207,4 @@ Go: * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/二叉树中递归带着回溯.md b/problems/二叉树中递归带着回溯.md index 372dc40c..7b0ccad7 100644 --- a/problems/二叉树中递归带着回溯.md +++ b/problems/二叉树中递归带着回溯.md @@ -11,15 +11,15 @@ > 补充一波 -昨天的总结篇中[还在玩耍的你,该总结啦!(本周小结之二叉树)](https://mp.weixin.qq.com/s/QMBUTYnoaNfsVHlUADEzKg),有两处问题需要说明一波。 +昨天的总结篇中[还在玩耍的你,该总结啦!(本周小结之二叉树)](https://programmercarl.com/周总结/20201003二叉树周末总结.html),有两处问题需要说明一波。 ## 求相同的树 -[还在玩耍的你,该总结啦!(本周小结之二叉树)](https://mp.weixin.qq.com/s/QMBUTYnoaNfsVHlUADEzKg)中求100.相同的树的代码中,我笔误贴出了 求对称树的代码了,细心的同学应该都发现了。 +[还在玩耍的你,该总结啦!(本周小结之二叉树)](https://programmercarl.com/周总结/20201003二叉树周末总结.html)中求100.相同的树的代码中,我笔误贴出了 求对称树的代码了,细心的同学应该都发现了。 那么如下我再给出求100. 相同的树 的代码,如下: -```C++ +```CPP class Solution { public: bool compare(TreeNode* tree1, TreeNode* tree2) { @@ -42,17 +42,17 @@ public: }; ``` -以上的代码相对于:[二叉树:我对称么?](https://mp.weixin.qq.com/s/Kgf0gjvlDlNDfKIH2b1Oxg) 仅仅修改了变量的名字(为了符合判断相同树的语境)和 遍历的顺序。 +以上的代码相对于:[二叉树:我对称么?](https://programmercarl.com/0101.对称二叉树.html) 仅仅修改了变量的名字(为了符合判断相同树的语境)和 遍历的顺序。 -大家应该会体会到:**认清[判断对称树](https://mp.weixin.qq.com/s/Kgf0gjvlDlNDfKIH2b1Oxg)本质之后, 对称树的代码 稍作修改 就可以直接用来AC 100.相同的树。** +大家应该会体会到:**认清[判断对称树](https://programmercarl.com/0101.对称二叉树.html)本质之后, 对称树的代码 稍作修改 就可以直接用来AC 100.相同的树。** ## 递归中隐藏着回溯 -在[二叉树:找我的所有路径?](https://mp.weixin.qq.com/s/Osw4LQD2xVUnCJ-9jrYxJA)中我强调了本题其实是用到了回溯的,并且给出了第一个版本的代码,把回溯的过程充分的提现了出来。 +在[二叉树:找我的所有路径?](https://programmercarl.com/0257.二叉树的所有路径.html)中我强调了本题其实是用到了回溯的,并且给出了第一个版本的代码,把回溯的过程充分的提现了出来。 如下的代码充分的体现出回溯:(257. 二叉树的所有路径) -```C++ +```CPP class Solution { private: @@ -120,13 +120,13 @@ public: 为了把这份精简代码的回溯过程展现出来,大家可以试一试把: -``` +```CPP if (cur->left) traversal(cur->left, path + "->", result); // 左 回溯就隐藏在这里 ``` 改成如下代码: -``` +```CPP path += "->"; traversal(cur->left, path, result); // 左 ``` @@ -149,7 +149,7 @@ if (cur->right) { 如果想把回溯加上,就要 在上面代码的基础上,加上回溯,就可以AC了。 -``` +```CPP if (cur->left) { path += "->"; traversal(cur->left, path, result); // 左 @@ -166,7 +166,7 @@ if (cur->right) { **大家应该可以感受出来,如果把 `path + "->"`作为函数参数就是可以的,因为并有没有改变path的数值,执行完递归函数之后,path依然是之前的数值(相当于回溯了)** -如果有点遗忘了,建议把这篇[二叉树:找我的所有路径?](https://mp.weixin.qq.com/s/Osw4LQD2xVUnCJ-9jrYxJA)在仔细看一下,然后再看这里的总结,相信会豁然开朗。 +如果有点遗忘了,建议把这篇[二叉树:找我的所有路径?](https://programmercarl.com/0257.二叉树的所有路径.html)在仔细看一下,然后再看这里的总结,相信会豁然开朗。 这里我尽量把逻辑的每一个细节都抠出来展现了,希望对大家有所帮助! @@ -448,4 +448,4 @@ func traversal(root *TreeNode,result *[]string,path *[]int){ * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/二叉树总结篇.md b/problems/二叉树总结篇.md index d4af8aae..ee046366 100644 --- a/problems/二叉树总结篇.md +++ b/problems/二叉树总结篇.md @@ -22,105 +22,106 @@ ## 二叉树的理论基础 -* [关于二叉树,你该了解这些!](https://mp.weixin.qq.com/s/_ymfWYvTNd2GvWvC5HOE4A):二叉树的种类、存储方式、遍历方式、定义方式 +* [关于二叉树,你该了解这些!](https://programmercarl.com/二叉树理论基础.html):二叉树的种类、存储方式、遍历方式、定义方式 ## 二叉树的遍历方式 * 深度优先遍历 - * [二叉树:前中后序递归法](https://mp.weixin.qq.com/s/PwVIfxDlT3kRgMASWAMGhA):递归三部曲初次亮相 - * [二叉树:前中后序迭代法(一)](https://mp.weixin.qq.com/s/c_zCrGHIVlBjUH_hJtghCg):通过栈模拟递归 - * [二叉树:前中后序迭代法(二)统一风格](https://mp.weixin.qq.com/s/WKg0Ty1_3SZkztpHubZPRg) + * [二叉树:前中后序递归法](https://programmercarl.com/二叉树的递归遍历.html):递归三部曲初次亮相 + * [二叉树:前中后序迭代法(一)](https://programmercarl.com/二叉树的迭代遍历.html):通过栈模拟递归 + * [二叉树:前中后序迭代法(二)统一风格](https://programmercarl.com/二叉树的统一迭代法.html) * 广度优先遍历 - * [二叉树的层序遍历](https://mp.weixin.qq.com/s/Gb3BjakIKGNpup2jYtTzog):通过队列模拟 + * [二叉树的层序遍历](https://programmercarl.com/0102.二叉树的层序遍历.html):通过队列模拟 ## 求二叉树的属性 -* [二叉树:是否对称](https://mp.weixin.qq.com/s/Kgf0gjvlDlNDfKIH2b1Oxg) +* [二叉树:是否对称](https://programmercarl.com/0101.对称二叉树.html) * 递归:后序,比较的是根节点的左子树与右子树是不是相互翻转 * 迭代:使用队列/栈将两个节点顺序放入容器中进行比较 -* [二叉树:求最大深度](https://mp.weixin.qq.com/s/guKwV-gSNbA1CcbvkMtHBg) +* [二叉树:求最大深度](https://programmercarl.com/0104.二叉树的最大深度.html) * 递归:后序,求根节点最大高度就是最大深度,通过递归函数的返回值做计算树的高度 * 迭代:层序遍历 -* [二叉树:求最小深度](https://mp.weixin.qq.com/s/BH8-gPC3_QlqICDg7rGSGA) +* [二叉树:求最小深度](https://programmercarl.com/0111.二叉树的最小深度.html) * 递归:后序,求根节点最小高度就是最小深度,注意最小深度的定义 * 迭代:层序遍历 -* [二叉树:求有多少个节点](https://mp.weixin.qq.com/s/2_eAjzw-D0va9y4RJgSmXw) +* [二叉树:求有多少个节点](https://programmercarl.com/0222.完全二叉树的节点个数.html) * 递归:后序,通过递归函数的返回值计算节点数量 * 迭代:层序遍历 -* [二叉树:是否平衡](https://mp.weixin.qq.com/s/isUS-0HDYknmC0Rr4R8mww) +* [二叉树:是否平衡](https://programmercarl.com/0110.平衡二叉树.html) * 递归:后序,注意后序求高度和前序求深度,递归过程判断高度差 * 迭代:效率很低,不推荐 -* [二叉树:找所有路径](https://mp.weixin.qq.com/s/Osw4LQD2xVUnCJ-9jrYxJA) +* [二叉树:找所有路径](https://programmercarl.com/0257.二叉树的所有路径.html) * 递归:前序,方便让父节点指向子节点,涉及回溯处理根节点到叶子的所有路径 * 迭代:一个栈模拟递归,一个栈来存放对应的遍历路径 -* [二叉树:递归中如何隐藏着回溯](https://mp.weixin.qq.com/s/ivLkHzWdhjQQD1rQWe6zWA) - * 详解[二叉树:找所有路径](https://mp.weixin.qq.com/s/Osw4LQD2xVUnCJ-9jrYxJA)中递归如何隐藏着回溯 -* [二叉树:求左叶子之和](https://mp.weixin.qq.com/s/gBAgmmFielojU5Wx3wqFTA) +* [二叉树:递归中如何隐藏着回溯](https://programmercarl.com/二叉树中递归带着回溯.html) + * 详解[二叉树:找所有路径](https://programmercarl.com/0257.二叉树的所有路径.html)中递归如何隐藏着回溯 +* [二叉树:求左叶子之和](https://programmercarl.com/0404.左叶子之和.html) * 递归:后序,必须三层约束条件,才能判断是否是左叶子。 * 迭代:直接模拟后序遍历 -* [二叉树:求左下角的值](https://mp.weixin.qq.com/s/MH2gbLvzQ91jHPKqiub0Nw) +* [二叉树:求左下角的值](https://programmercarl.com/0513.找树左下角的值.html) * 递归:顺序无所谓,优先左孩子搜索,同时找深度最大的叶子节点。 * 迭代:层序遍历找最后一行最左边 -* [二叉树:求路径总和](https://mp.weixin.qq.com/s/6TWAVjxQ34kVqROWgcRFOg) +* [二叉树:求路径总和](https://programmercarl.com/0112.路径总和.html) * 递归:顺序无所谓,递归函数返回值为bool类型是为了搜索一条边,没有返回值是搜索整棵树。 * 迭代:栈里元素不仅要记录节点指针,还要记录从头结点到该节点的路径数值总和 ## 二叉树的修改与构造 -* [翻转二叉树](https://mp.weixin.qq.com/s/6gY1MiXrnm-khAAJiIb5Bg) +* [翻转二叉树](https://programmercarl.com/0226.翻转二叉树.html) * 递归:前序,交换左右孩子 * 迭代:直接模拟前序遍历 -* [构造二叉树](https://mp.weixin.qq.com/s/7r66ap2s-shvVvlZxo59xg) +* [构造二叉树](https://programmercarl.com/0106.从中序与后序遍历序列构造二叉树.html) * 递归:前序,重点在于找分割点,分左右区间构造 * 迭代:比较复杂,意义不大 -* [构造最大的二叉树](https://mp.weixin.qq.com/s/1iWJV6Aov23A7xCF4nV88w) +* [构造最大的二叉树](https://programmercarl.com/0654.最大二叉树.html) * 递归:前序,分割点为数组最大值,分左右区间构造 * 迭代:比较复杂,意义不大 -* [合并两个二叉树](https://mp.weixin.qq.com/s/3f5fbjOFaOX_4MXzZ97LsQ) +* [合并两个二叉树](https://programmercarl.com/0617.合并二叉树.html) * 递归:前序,同时操作两个树的节点,注意合并的规则 * 迭代:使用队列,类似层序遍历 ## 求二叉搜索树的属性 -* [二叉搜索树中的搜索](https://mp.weixin.qq.com/s/vsKrWRlETxCVsiRr8v_hHg) +* [二叉搜索树中的搜索](https://programmercarl.com/0700.二叉搜索树中的搜索.html) * 递归:二叉搜索树的递归是有方向的 * 迭代:因为有方向,所以迭代法很简单 -* [是不是二叉搜索树](https://mp.weixin.qq.com/s/8odY9iUX5eSi0eRFSXFD4Q) +* [是不是二叉搜索树](https://programmercarl.com/0098.验证二叉搜索树.html) * 递归:中序,相当于变成了判断一个序列是不是递增的 * 迭代:模拟中序,逻辑相同 -* [求二叉搜索树的最小绝对差](https://mp.weixin.qq.com/s/Hwzml6698uP3qQCC1ctUQQ) +* [求二叉搜索树的最小绝对差](https://programmercarl.com/0530.二叉搜索树的最小绝对差.html) * 递归:中序,双指针操作 * 迭代:模拟中序,逻辑相同 -* [求二叉搜索树的众数](https://mp.weixin.qq.com/s/KSAr6OVQIMC-uZ8MEAnGHg) +* [求二叉搜索树的众数](https://programmercarl.com/0501.二叉搜索树中的众数.html) + * 递归:中序,清空结果集的技巧,遍历一遍便可求众数集合 - * 迭代:模拟中序,逻辑相同 -* [二叉搜索树转成累加树](https://mp.weixin.qq.com/s/hZtJh4T5lIGBarY-lZJf6Q) + * [二叉搜索树转成累加树](https://programmercarl.com/0538.把二叉搜索树转换为累加树.html) + * 递归:中序,双指针操作累加 * 迭代:模拟中序,逻辑相同 ## 二叉树公共祖先问题 -* [二叉树的公共祖先问题](https://mp.weixin.qq.com/s/n6Rk3nc_X3TSkhXHrVmBTQ) +* [二叉树的公共祖先问题](https://programmercarl.com/0236.二叉树的最近公共祖先.html) * 递归:后序,回溯,找到左子树出现目标值,右子树节点目标值的节点。 * 迭代:不适合模拟回溯 -* [二叉搜索树的公共祖先问题](https://mp.weixin.qq.com/s/Ja9dVw2QhBcg_vV-1fkiCg) +* [二叉搜索树的公共祖先问题](https://programmercarl.com/0235.二叉搜索树的最近公共祖先.html) * 递归:顺序无所谓,如果节点的数值在目标区间就是最近公共祖先 * 迭代:按序遍历 ## 二叉搜索树的修改与构造 -* [二叉搜索树中的插入操作](https://mp.weixin.qq.com/s/lwKkLQcfbCNX2W-5SOeZEA) +* [二叉搜索树中的插入操作](https://programmercarl.com/0701.二叉搜索树中的插入操作.html) * 递归:顺序无所谓,通过递归函数返回值添加节点 * 迭代:按序遍历,需要记录插入父节点,这样才能做插入操作 -* [二叉搜索树中的删除操作](https://mp.weixin.qq.com/s/-p-Txvch1FFk3ygKLjPAKw) +* [二叉搜索树中的删除操作](https://programmercarl.com/0450.删除二叉搜索树中的节点.html) * 递归:前序,想清楚删除非叶子节点的情况 * 迭代:有序遍历,较复杂 -* [修剪二叉搜索树](https://mp.weixin.qq.com/s/QzmGfYUMUWGkbRj7-ozHoQ) +* [修剪二叉搜索树](https://programmercarl.com/0669.修剪二叉搜索树.html) * 递归:前序,通过递归函数返回值删除节点 * 迭代:有序遍历,较复杂 -* [构造二叉搜索树](https://mp.weixin.qq.com/s/sy3ygnouaZVJs8lhFgl9mw) +* [构造二叉搜索树](https://programmercarl.com/0108.将有序数组转换为二叉搜索树.html) * 递归:前序,数组中间节点分割 * 迭代:较复杂,通过三个队列来模拟 @@ -130,10 +131,10 @@ **每周小结都会对大家的疑问做统一解答,并且对每周的内容进行拓展和补充,所以一定要看,将细碎知识点一网打尽!** -* [本周小结!(二叉树系列一)](https://mp.weixin.qq.com/s/JWmTeC7aKbBfGx4TY6uwuQ) -* [本周小结!(二叉树系列二)](https://mp.weixin.qq.com/s/QMBUTYnoaNfsVHlUADEzKg) -* [本周小结!(二叉树系列三)](https://mp.weixin.qq.com/s/JLLpx3a_8jurXcz6ovgxtg) -* [本周小结!(二叉树系列四)](https://mp.weixin.qq.com/s/CbdtOTP0N-HIP7DR203tSg) +* [本周小结!(二叉树系列一)](https://programmercarl.com/周总结/20200927二叉树周末总结.html) +* [本周小结!(二叉树系列二)](https://programmercarl.com/周总结/20201003二叉树周末总结.html) +* [本周小结!(二叉树系列三)](https://programmercarl.com/周总结/20201010二叉树周末总结.html) +* [本周小结!(二叉树系列四)](https://programmercarl.com/周总结/20201017二叉树周末总结.html) ## 最后总结 @@ -145,7 +146,7 @@ * 求二叉搜索树的属性,一定是中序了,要不白瞎了有序性了。 -注意在普通二叉树的属性中,我用的是一般为后序,例如单纯求深度就用前序, [二叉树:找所有路径](https://mp.weixin.qq.com/s/Osw4LQD2xVUnCJ-9jrYxJA)也用了前序,这是为了方便让父节点指向子节点。 +注意在普通二叉树的属性中,我用的是一般为后序,例如单纯求深度就用前序,[二叉树:找所有路径](https://programmercarl.com/0257.二叉树的所有路径.html)也用了前序,这是为了方便让父节点指向子节点。 所以求普通二叉树的属性还是要具体问题具体分析。 @@ -174,4 +175,4 @@ Go: * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/二叉树理论基础.md b/problems/二叉树理论基础.md index a720672e..94a0d67f 100644 --- a/problems/二叉树理论基础.md +++ b/problems/二叉树理论基础.md @@ -215,7 +215,7 @@ class TreeNode: ``` Go: -``` +```go type TreeNode struct { Val int Left *TreeNode @@ -223,10 +223,18 @@ type TreeNode struct { } ``` +JavaScript: +```javascript +function TreeNode(val, left, right) { + this.val = (val===undefined ? 0 : val) + this.left = (left===undefined ? null : left) + this.right = (right===undefined ? null : right) +} +``` ----------------------- * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/二叉树的统一迭代法.md b/problems/二叉树的统一迭代法.md index 9e24fef6..d8299e10 100644 --- a/problems/二叉树的统一迭代法.md +++ b/problems/二叉树的统一迭代法.md @@ -12,9 +12,9 @@ > 统一写法是一种什么感觉 -此时我们在[二叉树:一入递归深似海,从此offer是路人](https://mp.weixin.qq.com/s/Ww60X5mIKWdMQV4cN3ejOA)中用递归的方式,实现了二叉树前中后序的遍历。 +此时我们在[二叉树:一入递归深似海,从此offer是路人](https://programmercarl.com/二叉树的递归遍历.html)中用递归的方式,实现了二叉树前中后序的遍历。 -在[二叉树:听说递归能做的,栈也能做!](https://mp.weixin.qq.com/s/OH7aCVJ5-Gi32PkNCoZk4A)中用栈实现了二叉树前后中序的迭代遍历(非递归)。 +在[二叉树:听说递归能做的,栈也能做!](https://programmercarl.com/二叉树的迭代遍历.html)中用栈实现了二叉树前后中序的迭代遍历(非递归)。 之后我们发现**迭代法实现的先中后序,其实风格也不是那么统一,除了先序和后序,有关联,中序完全就是另一个风格了,一会用栈遍历,一会又用指针来遍历。** @@ -24,7 +24,7 @@ **重头戏来了,接下来介绍一下统一写法。** -我们以中序遍历为例,在[二叉树:听说递归能做的,栈也能做!](https://mp.weixin.qq.com/s/OH7aCVJ5-Gi32PkNCoZk4A)中提到说使用栈的话,**无法同时解决访问节点(遍历节点)和处理节点(将元素放进结果集)不一致的情况**。 +我们以中序遍历为例,在[二叉树:听说递归能做的,栈也能做!](https://programmercarl.com/二叉树的迭代遍历.html)中提到说使用栈的话,**无法同时解决访问节点(遍历节点)和处理节点(将元素放进结果集)不一致的情况**。 **那我们就将访问的节点放入栈中,把要处理的节点也放入栈中但是要做标记。** @@ -34,7 +34,7 @@ 中序遍历代码如下:(详细注释) -```C++ +```CPP class Solution { public: vector inorderTraversal(TreeNode* root) { @@ -77,7 +77,7 @@ public: 迭代法前序遍历代码如下: (**注意此时我们和中序遍历相比仅仅改变了两行代码的顺序**) -```C++ +```CPP class Solution { public: vector preorderTraversal(TreeNode* root) { @@ -108,7 +108,7 @@ public: 后续遍历代码如下: (**注意此时我们和中序遍历相比仅仅改变了两行代码的顺序**) -```C++ +```CPP class Solution { public: vector postorderTraversal(TreeNode* root) { @@ -530,4 +530,4 @@ var postorderTraversal = function(root, res = []) { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/二叉树的迭代遍历.md b/problems/二叉树的迭代遍历.md index 20397773..84363610 100644 --- a/problems/二叉树的迭代遍历.md +++ b/problems/二叉树的迭代遍历.md @@ -19,7 +19,7 @@ 为什么可以用迭代法(非递归的方式)来实现二叉树的前后中序遍历呢? -我们在[栈与队列:匹配问题都是栈的强项](https://mp.weixin.qq.com/s/1-x6r1wGA9mqIHW5LrMvBg)中提到了,**递归的实现就是:每一次递归调用都会把函数的局部变量、参数值和返回地址等压入调用栈中**,然后递归返回的时候,从栈顶弹出上一次递归的各项参数,所以这就是递归为什么可以返回上一层位置的原因。 +我们在[栈与队列:匹配问题都是栈的强项](https://programmercarl.com/1047.删除字符串中的所有相邻重复项.html)中提到了,**递归的实现就是:每一次递归调用都会把函数的局部变量、参数值和返回地址等压入调用栈中**,然后递归返回的时候,从栈顶弹出上一次递归的各项参数,所以这就是递归为什么可以返回上一层位置的原因。 此时大家应该知道我们用栈也可以是实现二叉树的前后中序遍历了。 @@ -27,7 +27,7 @@ 我们先看一下前序遍历。 -前序遍历是中左右,每次先处理的是中间节点,那么先将跟节点放入栈中,然后将右孩子加入栈,再加入左孩子。 +前序遍历是中左右,每次先处理的是中间节点,那么先将根节点放入栈中,然后将右孩子加入栈,再加入左孩子。 为什么要先加入 右孩子,再加入左孩子呢? 因为这样出栈的时候才是中左右的顺序。 @@ -37,7 +37,7 @@ 不难写出如下代码: (**注意代码中空节点不入栈**) -```C++ +```CPP class Solution { public: vector preorderTraversal(TreeNode* root) { @@ -84,7 +84,7 @@ public: **中序遍历,可以写出如下代码:** -```C++ +```CPP class Solution { public: vector inorderTraversal(TreeNode* root) { @@ -116,7 +116,7 @@ public: **所以后序遍历只需要前序遍历的代码稍作修改就可以了,代码如下:** -```C++ +```CPP class Solution { public: vector postorderTraversal(TreeNode* root) { @@ -140,7 +140,7 @@ public: # 总结 -此时我们用迭代法写出了二叉树的前后中序遍历,大家可以看出前序和中序是完全两种代码风格,并不想递归写法那样代码稍做调整,就可以实现前后中序。 +此时我们用迭代法写出了二叉树的前后中序遍历,大家可以看出前序和中序是完全两种代码风格,并不像递归写法那样代码稍做调整,就可以实现前后中序。 **这是因为前序遍历中访问节点(遍历节点)和处理节点(将元素放进result数组中)可以同步处理,但是中序就无法做到同步!** @@ -473,4 +473,4 @@ var postorderTraversal = function(root, res = []) { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/二叉树的递归遍历.md b/problems/二叉树的递归遍历.md index 2b5a44dd..93f4000e 100644 --- a/problems/二叉树的递归遍历.md +++ b/problems/二叉树的递归遍历.md @@ -58,7 +58,7 @@ traversal(cur->right, vec); // 右 前序遍历: -```C++ +```CPP class Solution { public: void traversal(TreeNode* cur, vector& vec) { @@ -79,7 +79,7 @@ public: 中序遍历: -```C++ +```CPP void traversal(TreeNode* cur, vector& vec) { if (cur == NULL) return; traversal(cur->left, vec); // 左 @@ -90,7 +90,7 @@ void traversal(TreeNode* cur, vector& vec) { 后序遍历: -```C++ +```CPP void traversal(TreeNode* cur, vector& vec) { if (cur == NULL) return; traversal(cur->left, vec); // 左 @@ -222,7 +222,7 @@ Go: 前序遍历: ```go -func PreorderTraversal(root *TreeNode) (res []int) { +func preorderTraversal(root *TreeNode) (res []int) { var traversal func(node *TreeNode) traversal = func(node *TreeNode) { if node == nil { @@ -240,7 +240,7 @@ func PreorderTraversal(root *TreeNode) (res []int) { 中序遍历: ```go -func InorderTraversal(root *TreeNode) (res []int) { +func inorderTraversal(root *TreeNode) (res []int) { var traversal func(node *TreeNode) traversal = func(node *TreeNode) { if node == nil { @@ -257,7 +257,7 @@ func InorderTraversal(root *TreeNode) (res []int) { 后序遍历: ```go -func PostorderTraversal(root *TreeNode) (res []int) { +func postorderTraversal(root *TreeNode) (res []int) { var traversal func(node *TreeNode) traversal = func(node *TreeNode) { if node == nil { @@ -360,11 +360,59 @@ var postorderTraversal = function(root) { }; ``` +C: +```c +//前序遍历: +void preOrderTraversal(struct TreeNode* root, int* ret, int* returnSize) { + if(root == NULL) + return; + ret[(*returnSize)++] = root->val; + preOrderTraverse(root->left, ret, returnSize); + preOrderTraverse(root->right, ret, returnSize); +} +int* preorderTraversal(struct TreeNode* root, int* returnSize){ + int* ret = (int*)malloc(sizeof(int) * 100); + *returnSize = 0; + preOrderTraversal(root, ret, returnSize); + return ret; +} +//中序遍历: +void inOrder(struct TreeNode* node, int* ret, int* returnSize) { + if(!node) + return; + inOrder(node->left, ret, returnSize); + ret[(*returnSize)++] = node->val; + inOrder(node->right, ret, returnSize); +} + +int* inorderTraversal(struct TreeNode* root, int* returnSize){ + int* ret = (int*)malloc(sizeof(int) * 100); + *returnSize = 0; + inOrder(root, ret, returnSize); + return ret; +} + +//后序遍历: +void postOrder(struct TreeNode* node, int* ret, int* returnSize) { + if(node == NULL) + return; + postOrder(node->left, ret, returnSize); + postOrder(node->right, ret, returnSize); + ret[(*returnSize)++] = node->val; +} + +int* postorderTraversal(struct TreeNode* root, int* returnSize){ + int* ret= (int*)malloc(sizeof(int) * 100); + *returnSize = 0; + postOrder(root, ret, returnSize); + return ret; +} +``` ----------------------- * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/关于时间复杂度,你不知道的都在这里!.md b/problems/关于时间复杂度,你不知道的都在这里!.md index fe378228..7ff9b470 100644 --- a/problems/关于时间复杂度,你不知道的都在这里!.md +++ b/problems/关于时间复杂度,你不知道的都在这里!.md @@ -177,4 +177,4 @@ Go: * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/其他/参与本项目.md b/problems/其他/参与本项目.md new file mode 100644 index 00000000..cfa75439 --- /dev/null +++ b/problems/其他/参与本项目.md @@ -0,0 +1,11 @@ + +优化已有代码 +![](https://code-thinking-1253855093.file.myqcloud.com/pics/20210821161813.png) + +**push代码之前 一定要 先pull最新代码**,否则提交的pr可能会有删除其他录友代码的操作。 + +一个pr 不要修改过多文件,因为一旦有一个 文件修改有问题,就不能合入,影响其他文件的合入了。 + +git add之前,要git diff 查看一下,本次提交所修改的代码是不是 自己修改的,是否 误删,或者误加的文件。 + +提交代码,不要使用git push -f 这种命令,要足够了解 -f 意味着什么。 diff --git a/problems/前序/BAT级别技术面试流程和注意事项都在这里了.md b/problems/前序/BAT级别技术面试流程和注意事项都在这里了.md index fbcdf970..c5797739 100644 --- a/problems/前序/BAT级别技术面试流程和注意事项都在这里了.md +++ b/problems/前序/BAT级别技术面试流程和注意事项都在这里了.md @@ -221,4 +221,4 @@ leetcode是专门针对算法练习的题库,leetcode现在也推出了中文 * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/前序/On的算法居然超时了,此时的n究竟是多大?.md b/problems/前序/On的算法居然超时了,此时的n究竟是多大?.md index f6218777..d9cc8d45 100644 --- a/problems/前序/On的算法居然超时了,此时的n究竟是多大?.md +++ b/problems/前序/On的算法居然超时了,此时的n究竟是多大?.md @@ -67,7 +67,7 @@ 实现三个函数,时间复杂度分别是 O(n) , O(n^2), O(nlogn),使用加法运算来统一测试。 -```C++ +```CPP // O(n) void function1(long long n) { long long k = 0; @@ -78,7 +78,7 @@ void function1(long long n) { ``` -```C++ +```CPP // O(n^2) void function2(long long n) { long long k = 0; @@ -91,7 +91,7 @@ void function2(long long n) { } ``` -```C++ +```CPP // O(nlogn) void function3(long long n) { long long k = 0; @@ -105,7 +105,7 @@ void function3(long long n) { ``` 来看一下这三个函数随着n的规模变化,耗时会产生多大的变化,先测function1 ,就把 function2 和 function3 注释掉 -```C++ +```CPP int main() { long long n; // 数据规模 while (1) { @@ -154,7 +154,7 @@ O(nlogn)的算法,1s内大概计算机可以运行 2 * (10^7)次计算,符 # 完整测试代码 -```C++ +```CPP #include #include #include @@ -228,4 +228,4 @@ int main() { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/前序/上海互联网公司总结.md b/problems/前序/上海互联网公司总结.md index 386a0a93..08c15895 100644 --- a/problems/前序/上海互联网公司总结.md +++ b/problems/前序/上海互联网公司总结.md @@ -112,7 +112,7 @@ ## 总结 -大家如果看了[北京有这些互联网公司,你都知道么?](https://mp.weixin.qq.com/s/BKrjK4myNB-FYbMqW9f3yw)和[深圳原来有这么多互联网公司,你都知道么?](https://mp.weixin.qq.com/s/3VJHF2zNohBwDBxARFIn-Q)就可以看出中国互联网氛围最浓的当然是北京,其次就是上海! +大家如果看了[北京有这些互联网公司,你都知道么?](https://programmercarl.com/前序/北京互联网公司总结.html)和[深圳原来有这么多互联网公司,你都知道么?](https://programmercarl.com/前序/深圳互联网公司总结.html)就可以看出中国互联网氛围最浓的当然是北京,其次就是上海! 很多人说深圳才是第二,上海没有产生BAT之类的企业。 @@ -133,4 +133,4 @@ * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/前序/什么是核心代码模式,什么又是ACM模式?.md b/problems/前序/什么是核心代码模式,什么又是ACM模式?.md index 5a19a72a..3c5fb4e4 100644 --- a/problems/前序/什么是核心代码模式,什么又是ACM模式?.md +++ b/problems/前序/什么是核心代码模式,什么又是ACM模式?.md @@ -13,7 +13,7 @@ 而力扣上是核心代码模式,就是把要处理的数据都已经放入容器里,可以直接写逻辑,例如这样: -```C++ +```CPP class Solution { public: int minimumTotal(vector>& triangle) { @@ -55,7 +55,7 @@ public: 这道题如果要是力扣上的核心代码模式,OJ应该直接给出如下代码: -```C++ +```CPP class Solution { public: int getDays(vector& work, vector& gym) { @@ -72,7 +72,7 @@ ACM模式要求写出来的代码是直接可以本地运行的,所以我们 拿本题来说,为了让代码可以运行,需要include这些库函数: -```C++ +```CPP #include #include using namespace std; @@ -87,7 +87,7 @@ using namespace std; 完整代码如下: -```C++ +```CPP #include #include using namespace std; @@ -122,4 +122,4 @@ int main() { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/前序/代码风格.md b/problems/前序/代码风格.md index a9eabd0c..a71ac420 100644 --- a/problems/前序/代码风格.md +++ b/problems/前序/代码风格.md @@ -108,7 +108,7 @@ while (n) { ``` 以下是我刚写的力扣283.移动零的代码,大家可以看一下整体风格,注意空格的细节! -```C++ +```CPP class Solution { public: void moveZeroes(vector& nums) { @@ -149,4 +149,4 @@ Google规范是 大括号和 控制语句保持同一行的,我个人也很认 * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/前序/关于时间复杂度,你不知道的都在这里!.md b/problems/前序/关于时间复杂度,你不知道的都在这里!.md index d6471b99..d4869b14 100644 --- a/problems/前序/关于时间复杂度,你不知道的都在这里!.md +++ b/problems/前序/关于时间复杂度,你不知道的都在这里!.md @@ -173,4 +173,4 @@ O(2 * n^2 + 10 * n + 1000) < O(3 * n^2),所以说最后省略掉常数项系 * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/前序/关于空间复杂度,可能有几个疑问?.md b/problems/前序/关于空间复杂度,可能有几个疑问?.md index 0208aa91..510979c9 100644 --- a/problems/前序/关于空间复杂度,可能有几个疑问?.md +++ b/problems/前序/关于空间复杂度,可能有几个疑问?.md @@ -10,9 +10,9 @@ # 空间复杂度分析 -* [关于时间复杂度,你不知道的都在这里!](https://mp.weixin.qq.com/s/LWBfehW1gMuEnXtQjJo-sw) -* [O(n)的算法居然超时了,此时的n究竟是多大?](https://mp.weixin.qq.com/s/73ryNsuPFvBQkt6BbhNzLA) -* [通过一道面试题目,讲一讲递归算法的时间复杂度!](https://mp.weixin.qq.com/s/I6ZXFbw09NR31F5CJR_geQ) +* [关于时间复杂度,你不知道的都在这里!](https://programmercarl.com/前序/关于时间复杂度,你不知道的都在这里!.html) +* [O(n)的算法居然超时了,此时的n究竟是多大?](https://programmercarl.com/前序/On的算法居然超时了,此时的n究竟是多大?.html) +* [通过一道面试题目,讲一讲递归算法的时间复杂度!](https://programmercarl.com/前序/通过一道面试题目,讲一讲递归算法的时间复杂度!.html) 那么一直还没有讲空间复杂度,所以打算陆续来补上,内容不难,大家可以读一遍文章就有整体的了解了。 @@ -30,7 +30,7 @@ 2. 空间复杂度是准确算出程序运行时所占用的内存么? -不要以为空间复杂度就已经精准的掌握了程序的内存使用大小,很有多因素会影响程序真正内存使用大小,例如编译器的内存对齐,编程语言容器的底层实现等等这些都会影响到程序内存的开销。 +不要以为空间复杂度就已经精准的掌握了程序的内存使用大小,很多因素会影响程序真正内存使用大小,例如编译器的内存对齐,编程语言容器的底层实现等等这些都会影响到程序内存的开销。 所以空间复杂度是预先大体评估程序内存使用的大小。 @@ -42,7 +42,7 @@ 来看一下例子,什么时候的空间复杂度是O(1)呢,C++代码如下: -```C++ +```CPP int j = 0; for (int i = 0; i < n; i++) { j++; @@ -54,7 +54,7 @@ for (int i = 0; i < n; i++) { 什么时候的空间复杂度是O(n)? 当消耗空间和输入参数n保持线性增长,这样的空间复杂度为O(n),来看一下这段C++代码 -```C++ +```CPP int* a = new int(n); for (int i = 0; i < n; i++) { a[i] = i; @@ -76,4 +76,4 @@ for (int i = 0; i < n; i++) { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/前序/刷了这么多题,你了解自己代码的内存消耗么?.md b/problems/前序/刷了这么多题,你了解自己代码的内存消耗么?.md index 6d3b4931..3fccfb22 100644 --- a/problems/前序/刷了这么多题,你了解自己代码的内存消耗么?.md +++ b/problems/前序/刷了这么多题,你了解自己代码的内存消耗么?.md @@ -77,7 +77,7 @@ 可以看一下这段C++代码输出的各个数据类型大小是多少? -```C++ +```CPP struct node{ int num; char cha; @@ -153,4 +153,4 @@ char型的数据和int型的数据挨在一起,该int数据从地址1开始, * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/前序/力扣上的代码想在本地编译运行?.md b/problems/前序/力扣上的代码想在本地编译运行?.md index f4a9b0f3..970af7ff 100644 --- a/problems/前序/力扣上的代码想在本地编译运行?.md +++ b/problems/前序/力扣上的代码想在本地编译运行?.md @@ -23,11 +23,11 @@ 其实挺简单的,大家看一遍就会了。 -我拿我们刚讲过的这道题[动态规划:使用最小花费爬楼梯](https://mp.weixin.qq.com/s/djZB9gkyLFAKcQcSvKDorA)来做示范。 +我拿我们刚讲过的这道题[动态规划:使用最小花费爬楼梯](https://programmercarl.com/0746.使用最小花费爬楼梯.html)来做示范。 力扣746. 使用最小花费爬楼梯,完整的可以在直接本地运行的C++代码如下: -```C++ +```CPP #include #include using namespace std; @@ -70,4 +70,4 @@ int main() { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/前序/北京互联网公司总结.md b/problems/前序/北京互联网公司总结.md index 4dcaa691..0e22dad6 100644 --- a/problems/前序/北京互联网公司总结.md +++ b/problems/前序/北京互联网公司总结.md @@ -105,7 +105,7 @@ 可能是我写总结写习惯了,什么文章都要有一个总结,哈哈,那么我就总结一下。 -北京的互联网氛围绝对是最好的(暂不讨论户口和房价问题),大家如果看了[深圳原来有这么多互联网公司,你都知道么?](https://mp.weixin.qq.com/s/3VJHF2zNohBwDBxARFIn-Q)这篇之后,**会发现北京互联网外企和二线互联网公司数量多的优势,在深圳的互联网公司断档比较严重,如果去不了为数不多的一线公司,可选择的余地就非常少了,而北京选择的余地就很多!** +北京的互联网氛围绝对是最好的(暂不讨论户口和房价问题),大家如果看了[深圳原来有这么多互联网公司,你都知道么?](https://programmercarl.com/前序/深圳互联网公司总结.html)这篇之后,**会发现北京互联网外企和二线互联网公司数量多的优势,在深圳的互联网公司断档比较严重,如果去不了为数不多的一线公司,可选择的余地就非常少了,而北京选择的余地就很多!** 相对来说,深圳的硬件企业更多一些,因为珠三角制造业配套比较完善。而大多数互联网公司其实就是媒体公司,当然要靠近政治文化中心,这也是有原因的。 @@ -119,4 +119,4 @@ * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/前序/广州互联网公司总结.md b/problems/前序/广州互联网公司总结.md index e9b2af00..ae41c899 100644 --- a/problems/前序/广州互联网公司总结.md +++ b/problems/前序/广州互联网公司总结.md @@ -51,7 +51,7 @@ ## 总结 -同在广东省,难免不了要和深圳对比,大家如果看了这篇:[深圳原来有这么多互联网公司,你都知道么?](https://mp.weixin.qq.com/s/3VJHF2zNohBwDBxARFIn-Q)就能感受到鲜明的对比了。 +同在广东省,难免不了要和深圳对比,大家如果看了这篇:[深圳原来有这么多互联网公司,你都知道么?](https://programmercarl.com/前序/深圳互联网公司总结.html)就能感受到鲜明的对比了。 广州大厂高端岗位其实比较少,本土只有微信和网易,微信呢毕竟还是腾讯的分部,而网易被很多人认为是杭州企业,其实网易总部在广州。 @@ -82,4 +82,4 @@ * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/前序/成都互联网公司总结.md b/problems/前序/成都互联网公司总结.md index 2435ccb2..d44800cd 100644 --- a/problems/前序/成都互联网公司总结.md +++ b/problems/前序/成都互联网公司总结.md @@ -80,4 +80,4 @@ * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/前序/杭州互联网公司总结.md b/problems/前序/杭州互联网公司总结.md index e2691469..326a176b 100644 --- a/problems/前序/杭州互联网公司总结.md +++ b/problems/前序/杭州互联网公司总结.md @@ -67,11 +67,10 @@ * 大搜车(总部)中国领先的汽车交易服务供应商 * 二更(总部)自媒体 * 丁香园(总部) - ## 总结 -杭州距离上海非常近,难免不了和上海做对比,上海是金融之都,如果看了[上海有这些互联网公司,你都知道么?](https://mp.weixin.qq.com/s/iW4_rXQzc0fJDuSmPTUVdQ)就会发现上海互联网也是仅次于北京的。 +杭州距离上海非常近,难免不了和上海做对比,上海是金融之都,如果看了[上海有这些互联网公司,你都知道么?](https://programmercarl.com/前序/上海互联网公司总结.html)就会发现上海互联网也是仅次于北京的。 而杭州是阿里的大本营,到处都有阿里的影子,虽然有网易在,但是也基本是盖过去了,很多中小公司也都是阿里某某高管出来创业的。 @@ -91,4 +90,4 @@ * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/前序/深圳互联网公司总结.md b/problems/前序/深圳互联网公司总结.md index 4b68dad6..9e089315 100644 --- a/problems/前序/深圳互联网公司总结.md +++ b/problems/前序/深圳互联网公司总结.md @@ -85,4 +85,4 @@ * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/前序/程序员写文档工具.md b/problems/前序/程序员写文档工具.md index b06ce0ad..b76fb036 100644 --- a/problems/前序/程序员写文档工具.md +++ b/problems/前序/程序员写文档工具.md @@ -140,4 +140,4 @@ Markdown支持部分html,例如这样 * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/前序/程序员简历.md b/problems/前序/程序员简历.md index a9abcdc4..f47516dc 100644 --- a/problems/前序/程序员简历.md +++ b/problems/前序/程序员简历.md @@ -136,4 +136,4 @@ Carl校招社招都拿过大厂的offer,同时也看过很多应聘者的简 * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/前序/递归算法的时间与空间复杂度分析.md b/problems/前序/递归算法的时间与空间复杂度分析.md index f1501e8a..7a690781 100644 --- a/problems/前序/递归算法的时间与空间复杂度分析.md +++ b/problems/前序/递归算法的时间与空间复杂度分析.md @@ -9,7 +9,7 @@ # 递归算法的时间与空间复杂度分析! -之前在[通过一道面试题目,讲一讲递归算法的时间复杂度!](https://mp.weixin.qq.com/s/I6ZXFbw09NR31F5CJR_geQ)中详细讲解了递归算法的时间复杂度,但没有讲空间复杂度。 +之前在[通过一道面试题目,讲一讲递归算法的时间复杂度!](https://programmercarl.com/前序/通过一道面试题目,讲一讲递归算法的时间复杂度!.html)中详细讲解了递归算法的时间复杂度,但没有讲空间复杂度。 本篇讲通过求斐波那契数列和二分法再来深入分析一波递归算法的时间和空间复杂度,细心看完,会刷新对递归的认知! @@ -18,7 +18,7 @@ 先来看一下求斐波那契数的递归写法。 -```C++ +```CPP int fibonacci(int i) { if(i <= 0) return 0; if(i == 1) return 1; @@ -50,7 +50,7 @@ int fibonacci(int i) { 以下为C++代码,来测一下,让我们输入n的时候,这段递归求斐波那契代码的耗时。 -```C++ +```CPP #include #include #include @@ -99,7 +99,7 @@ int main() 其实罪魁祸首就是这里的两次递归,导致了时间复杂度以指数上升。 -```C++ +```CPP return fibonacci(i-1) + fibonacci(i-2); ``` @@ -107,7 +107,7 @@ return fibonacci(i-1) + fibonacci(i-2); 来看一下如下代码: -```C++ +```CPP // 版本二 int fibonacci(int first, int second, int n) { if (n <= 0) { @@ -138,7 +138,7 @@ int fibonacci(int first, int second, int n) { 此时再来测一下耗时情况验证一下: -```C++ +```CPP #include #include #include @@ -166,7 +166,7 @@ void time_consumption() { system_clock::now().time_since_epoch() ); - fibonacci_3(0, 1, n); + fibonacci_3(1, 1, n); milliseconds end_time = duration_cast( system_clock::now().time_since_epoch() @@ -208,7 +208,7 @@ int main() 那么每次递归的空间复杂度是O(1), 调用栈深度为n,所以这段递归代码的空间复杂度就是O(n)。 -```C++ +```CPP int fibonacci(int i) { if(i <= 0) return 0; if(i == 1) return 1; @@ -227,7 +227,7 @@ int fibonacci(int i) { 带大家再分析一段二分查找的递归实现。 -```C++ +```CPP int binary_search( int arr[], int l, int r, int x) { if (r >= l) { int mid = l + (r - l) / 2; @@ -272,4 +272,4 @@ int binary_search( int arr[], int l, int r, int x) { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/前序/通过一道面试题目,讲一讲递归算法的时间复杂度!.md b/problems/前序/通过一道面试题目,讲一讲递归算法的时间复杂度!.md index 16ba8361..b3aef43c 100644 --- a/problems/前序/通过一道面试题目,讲一讲递归算法的时间复杂度!.md +++ b/problems/前序/通过一道面试题目,讲一讲递归算法的时间复杂度!.md @@ -27,7 +27,7 @@ 想一下这么简单的一道题目,代码应该如何写呢。最直观的方式应该就是,一个for循环求出结果,代码如下: -```C++ +```CPP int function1(int x, int n) { int result = 1; // 注意 任何数的0次方等于1 for (int i = 0; i < n; i++) { @@ -44,7 +44,7 @@ int function1(int x, int n) { 那么就可以写出了如下这样的一个递归的算法,使用递归解决了这个问题。 -``` +```CPP int function2(int x, int n) { if (n == 0) { return 1; // return 1 同样是因为0次方是等于1的 @@ -62,7 +62,7 @@ int function2(int x, int n) { 这个时间复杂度就没有达到面试官的预期。于是又写出了如下的递归算法的代码: -``` +```CPP int function3(int x, int n) { if (n == 0) { return 1; @@ -101,7 +101,7 @@ int function3(int x, int n) { 于是又写出如下递归算法的代码: -``` +```CPP int function4(int x, int n) { if (n == 0) { return 1; @@ -132,7 +132,7 @@ int function4(int x, int n) { 对于function3 这样的递归实现,很容易让人感觉这是O(logn)的时间复杂度,其实这是O(n)的算法! -``` +```CPP int function3(int x, int n) { if (n == 0) { return 1; @@ -145,16 +145,14 @@ int function3(int x, int n) { ``` 可以看出这道题目非常简单,但是又很考究算法的功底,特别是对递归的理解,这也是我面试别人的时候用过的一道题,所以整个情景我才写的如此逼真,哈哈。 -大厂面试的时候最喜欢用“简单题”来考察候选人的算法功底,注意这里的“简单题”可并不一定真的简单​哦! +大厂面试的时候最喜欢用“简单题”来考察候选人的算法功底,注意这里的“简单题”可并不一定真的简单哦! 如果认真读完本篇,相信大家对递归算法的有一个新的认识的,同一道题目,同样是递归,效率可是不一样的! - - ----------------------- * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/剑指Offer05.替换空格.md b/problems/剑指Offer05.替换空格.md index 3e4e3631..47907319 100644 --- a/problems/剑指Offer05.替换空格.md +++ b/problems/剑指Offer05.替换空格.md @@ -9,7 +9,7 @@ # 题目:剑指Offer 05.替换空格 -https://leetcode-cn.com/problems/ti-huan-kong-ge-lcof/ +[力扣题目链接](https://leetcode-cn.com/problems/ti-huan-kong-ge-lcof/) 请实现一个函数,把字符串 s 中的每个空格替换成"%20"。 @@ -46,7 +46,7 @@ i指向新长度的末尾,j指向旧长度的末尾。 C++代码如下: -```C++ +```CPP class Solution { public: string replaceSpace(string s) { @@ -81,12 +81,12 @@ public: 此时算上本题,我们已经做了七道双指针相关的题目了分别是: -* [27.移除元素](https://mp.weixin.qq.com/s/RMkulE4NIb6XsSX83ra-Ww) -* [15.三数之和](https://mp.weixin.qq.com/s/QfTNEByq1YlNSXRKEumwHg) -* [18.四数之和](https://mp.weixin.qq.com/s/SBU3THi1Kv6Sar7htqCB2Q) -* [206.翻转链表](https://mp.weixin.qq.com/s/ckEvIVGcNLfrz6OLOMoT0A) -* [142.环形链表II](https://mp.weixin.qq.com/s/gt_VH3hQTqNxyWcl1ECSbQ) -* [344.反转字符串](https://mp.weixin.qq.com/s/_rNm66OJVl92gBDIbGpA3w) +* [27.移除元素](https://programmercarl.com/0027.移除元素.html) +* [15.三数之和](https://programmercarl.com/0015.三数之和.html) +* [18.四数之和](https://programmercarl.com/0018.四数之和.html) +* [206.翻转链表](https://programmercarl.com/0206.翻转链表.html) +* [142.环形链表II](https://programmercarl.com/0142.环形链表II.html) +* [344.反转字符串](https://programmercarl.com/0344.反转字符串.html) # 拓展 @@ -202,45 +202,27 @@ func replaceSpace(s string) string { python: ```python -class Solution(object): - def replaceSpace(self, s): - """ - :type s: str - :rtype: str - """ - list_s = list(s) - - # 记录原本字符串的长度 - original_end = len(s) - - # 将空格改成%20 使得字符串总长增长 2n,n为原本空格数量。 - # 所以记录空格数量就可以得到目标字符串的长度 - n_space = 0 - for ss in s: - if ss == ' ': - n_space += 1 - - list_s += ['0'] * 2 * n_space - - # 设置左右指针位置 - left, right = original_end - 1, len(list_s) - 1 - - # 循环直至左指针越界 - while left >= 0: - if list_s[left] == ' ': - list_s[right] = '0' - list_s[right - 1] = '2' - list_s[right - 2] = '%' - right -= 3 - else: - list_s[right] = list_s[left] - right -= 1 - - left -= 1 +class Solution: + def replaceSpace(self, s: str) -> str: + counter = s.count(' ') - # 将list变回str,输出 - s = ''.join(list_s) - return s + res = list(s) + # 每碰到一个空格就多拓展两个格子,1 + 2 = 3个位置存’%20‘ + res.extend([' '] * counter * 2) + + # 原始字符串的末尾,拓展后的末尾 + left, right = len(s) - 1, len(res) - 1 + + while left >= 0: + if res[left] != ' ': + res[right] = res[left] + right -= 1 + else: + # [right - 2, right), 左闭右开 + res[right - 2: right + 1] = '%20' + right -= 3 + left -= 1 + return ''.join(res) ``` @@ -282,10 +264,53 @@ javaScript: }; ``` +Swift: + +```swift +func replaceSpace(_ s: String) -> String { + var strArr = Array(s) + var count = 0 + + // 统计空格的个数 + for i in strArr { + if i == " " { + count += 1 + } + } + // left 指向旧数组的最后一个元素 + var left = strArr.count - 1 + // right 指向扩容后数组的最后一个元素(这里还没对数组进行实际上的扩容) + var right = strArr.count + count * 2 - 1 + + // 实际对数组扩容 + for _ in 0..<(count * 2) { + strArr.append(" ") + } + + while left < right { + if strArr[left] == " " { + strArr[right] = "0" + strArr[right - 1] = "2" + strArr[right - 2] = "%" + left -= 1 + right -= 3 + } else { + strArr[right] = strArr[left] + left -= 1 + right -= 1 + } + } + + return String(strArr) +} +``` + + + ----------------------- * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/剑指Offer58-II.左旋转字符串.md b/problems/剑指Offer58-II.左旋转字符串.md index fe4c3473..d8aaca67 100644 --- a/problems/剑指Offer58-II.左旋转字符串.md +++ b/problems/剑指Offer58-II.左旋转字符串.md @@ -11,11 +11,10 @@ # 题目:剑指Offer58-II.左旋转字符串 -https://leetcode-cn.com/problems/zuo-xuan-zhuan-zi-fu-chuan-lcof/ +[力扣题目链接](https://leetcode-cn.com/problems/zuo-xuan-zhuan-zi-fu-chuan-lcof/) 字符串的左旋转操作是把字符串前面的若干个字符转移到字符串的尾部。请定义一个函数实现字符串左旋转操作的功能。比如,输入字符串"abcdefg"和数字2,该函数将返回左旋转两位得到的结果"cdefgab"。 - 示例 1: 输入: s = "abcdefg", k = 2 输出: "cdefgab" @@ -34,7 +33,7 @@ https://leetcode-cn.com/problems/zuo-xuan-zhuan-zi-fu-chuan-lcof/ 不能使用额外空间的话,模拟在本串操作要实现左旋转字符串的功能还是有点困难的。 -那么我们可以想一下上一题目[字符串:花式反转还不够!](https://mp.weixin.qq.com/s/4j6vPFHkFAXnQhmSkq2X9g)中讲过,使用整体反转+局部反转就可以实现,反转单词顺序的目的。 +那么我们可以想一下上一题目[字符串:花式反转还不够!](https://programmercarl.com/0151.翻转字符串里的单词.html)中讲过,使用整体反转+局部反转就可以实现,反转单词顺序的目的。 这道题目也非常类似,依然可以通过局部反转+整体反转 达到左旋转的目的。 @@ -58,7 +57,7 @@ https://leetcode-cn.com/problems/zuo-xuan-zhuan-zi-fu-chuan-lcof/ C++代码如下: -```C++ +```CPP class Solution { public: string reverseLeftWords(string s, int n) { @@ -76,13 +75,13 @@ public: 此时我们已经反转好多次字符串了,来一起回顾一下吧。 -在这篇文章[344.反转字符串](https://mp.weixin.qq.com/s/_rNm66OJVl92gBDIbGpA3w),第一次讲到反转一个字符串应该怎么做,使用了双指针法。 +在这篇文章[344.反转字符串](https://programmercarl.com/0344.反转字符串.html),第一次讲到反转一个字符串应该怎么做,使用了双指针法。 -然后发现[541. 反转字符串II](https://mp.weixin.qq.com/s/pzXt6PQ029y7bJ9YZB2mVQ),这里开始给反转加上了一些条件,当需要固定规律一段一段去处理字符串的时候,要想想在在for循环的表达式上做做文章。 +然后发现[541. 反转字符串II](https://programmercarl.com/0541.反转字符串II.html),这里开始给反转加上了一些条件,当需要固定规律一段一段去处理字符串的时候,要想想在在for循环的表达式上做做文章。 -后来在[151.翻转字符串里的单词](https://mp.weixin.qq.com/s/4j6vPFHkFAXnQhmSkq2X9g)中,要对一句话里的单词顺序进行反转,发现先整体反转再局部反转 是一个很妙的思路。 +后来在[151.翻转字符串里的单词](https://programmercarl.com/0151.翻转字符串里的单词.html)中,要对一句话里的单词顺序进行反转,发现先整体反转再局部反转 是一个很妙的思路。 -最后再讲到本题,本题则是先局部反转再 整体反转,与[151.翻转字符串里的单词](https://mp.weixin.qq.com/s/4j6vPFHkFAXnQhmSkq2X9g)类似,但是也是一种新的思路。 +最后再讲到本题,本题则是先局部反转再 整体反转,与[151.翻转字符串里的单词](https://programmercarl.com/0151.翻转字符串里的单词.html)类似,但是也是一种新的思路。 好了,反转字符串一共就介绍到这里,相信大家此时对反转字符串的常见操作已经很了解了。 @@ -125,20 +124,53 @@ python: class Solution: def reverseLeftWords(self, s: str, n: int) -> str: return s[n:] + s[0:n] - +``` +```python # 方法二:也可以使用上文描述的方法,有些面试中不允许使用切片,那就使用上文作者提到的方法 -# class Solution: -# def reverseLeftWords(self, s: str, n: int) -> str: -# s = list(s) -# s[0:n] = list(reversed(s[0:n])) -# s[n:] = list(reversed(s[n:])) -# s.reverse() +class Solution: + def reverseLeftWords(self, s: str, n: int) -> str: + s = list(s) + s[0:n] = list(reversed(s[0:n])) + s[n:] = list(reversed(s[n:])) + s.reverse() + + return "".join(s) -# return "".join(s) +``` +```python +# 方法三:如果连reversed也不让使用,那么自己手写一个 +class Solution: + def reverseLeftWords(self, s: str, n: int) -> str: + def reverse_sub(lst, left, right): + while left < right: + lst[left], lst[right] = lst[right], lst[left] + left += 1 + right -= 1 + + res = list(s) + end = len(res) - 1 + reverse_sub(res, 0, n - 1) + reverse_sub(res, n, end) + reverse_sub(res, 0, end) + return ''.join(res) +# 同方法二 # 时间复杂度:O(n) # 空间复杂度:O(n),python的string为不可变,需要开辟同样大小的list空间来修改 + +``` + +```python 3 +#方法四:考虑不能用切片的情况下,利用模+下标实现 +class Solution: + def reverseLeftWords(self, s: str, n: int) -> str: + new_s = '' + for i in range(len(s)): + j = (i+n)%len(s) + new_s = new_s + s[j] + return new_s + ``` Go: @@ -182,6 +214,34 @@ var reverseLeftWords = function (s, n) { }; ``` +Swift: + +```swift +func reverseLeftWords(_ s: String, _ n: Int) -> String { + var ch = Array(s) + let len = ch.count + // 反转区间[0, n - 1] + reverseString(&ch, startIndex: 0, endIndex: n - 1) + // 反转区间[n, len - 1] + reverseString(&ch, startIndex: n, endIndex: len - 1) + // 反转区间[0, len - 1],也就是整个字符串反转 + reverseString(&ch, startIndex: 0, endIndex: len - 1) + return String(ch) +} + +func reverseString(_ s: inout [Character], startIndex: Int, endIndex: Int) { + var start = startIndex + var end = endIndex + while start < end { + (s[start], s[end]) = (s[end], s[start]) + start += 1 + end -= 1 + } +} +``` + + + @@ -189,4 +249,4 @@ var reverseLeftWords = function (s, n) { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/动态规划-股票问题总结篇.md b/problems/动态规划-股票问题总结篇.md index 590a8008..a3443ecb 100644 --- a/problems/动态规划-股票问题总结篇.md +++ b/problems/动态规划-股票问题总结篇.md @@ -10,21 +10,21 @@ ![股票问题总结](https://code-thinking.cdn.bcebos.com/pics/%E8%82%A1%E7%A5%A8%E9%97%AE%E9%A2%98%E6%80%BB%E7%BB%93.jpg) -* [动态规划:121.买卖股票的最佳时机](https://mp.weixin.qq.com/s/keWo5qYJY4zmHn3amfXdfQ) -* [动态规划:122.买卖股票的最佳时机II](https://mp.weixin.qq.com/s/d4TRWFuhaY83HPa6t5ZL-w) -* [动态规划:123.买卖股票的最佳时机III](https://mp.weixin.qq.com/s/Sbs157mlVDtAR0gbLpdKzg) -* [动态规划:188.买卖股票的最佳时机IV](https://mp.weixin.qq.com/s/jtxZJWAo2y5sUsW647Z5cw) -* [动态规划:309.最佳买卖股票时机含冷冻期](https://mp.weixin.qq.com/s/TczJGFAPnkjH9ET8kwH1OA) -* [动态规划:714.买卖股票的最佳时机含手续费](https://mp.weixin.qq.com/s/2Cd_uINjerZ25VHH0K2IBQ) +* [动态规划:121.买卖股票的最佳时机](https://programmercarl.com/0121.买卖股票的最佳时机.html) +* [动态规划:122.买卖股票的最佳时机II](https://programmercarl.com/0122.买卖股票的最佳时机II(动态规划).html) +* [动态规划:123.买卖股票的最佳时机III](https://programmercarl.com/0123.买卖股票的最佳时机III.html) +* [动态规划:188.买卖股票的最佳时机IV](https://programmercarl.com/0188.买卖股票的最佳时机IV.html) +* [动态规划:309.最佳买卖股票时机含冷冻期](https://programmercarl.com/0309.最佳买卖股票时机含冷冻期.html) +* [动态规划:714.买卖股票的最佳时机含手续费](https://programmercarl.com/0714.买卖股票的最佳时机含手续费(动态规划).html) ## 卖股票的最佳时机 -[动态规划:121.买卖股票的最佳时机](https://mp.weixin.qq.com/s/keWo5qYJY4zmHn3amfXdfQ),**股票只能买卖一次,问最大利润**。 +[动态规划:121.买卖股票的最佳时机](https://programmercarl.com/0121.买卖股票的最佳时机.html),**股票只能买卖一次,问最大利润**。 【贪心解法】 取最左最小值,取最右最大值,那么得到的差值就是最大利润,代码如下: -```C++ +```CPP class Solution { public: int maxProfit(vector& prices) { @@ -56,7 +56,7 @@ public: 代码如下: -```C++ +```CPP // 版本一 class Solution { public: @@ -79,7 +79,7 @@ public: 使用滚动数组,代码如下: -```C++ +```CPP // 版本二 class Solution { public: @@ -103,14 +103,14 @@ public: ## 买卖股票的最佳时机II -[动态规划:122.买卖股票的最佳时机II](https://mp.weixin.qq.com/s/d4TRWFuhaY83HPa6t5ZL-w)可以多次买卖股票,问最大收益。 +[动态规划:122.买卖股票的最佳时机II](https://programmercarl.com/0122.买卖股票的最佳时机II(动态规划).html)可以多次买卖股票,问最大收益。 【贪心解法】 收集每天的正利润便可,代码如下: -```C++ +```CPP class Solution { public: int maxProfit(vector& prices) { @@ -138,15 +138,15 @@ dp数组定义: * 第i-1天就持有股票,那么就保持现状,所得现金就是昨天持有股票的所得现金 即:dp[i - 1][0] * 第i天买入股票,所得现金就是昨天不持有股票的所得现金减去 今天的股票价格 即:dp[i - 1][1] - prices[i] -**注意这里和[121. 买卖股票的最佳时机](https://mp.weixin.qq.com/s/keWo5qYJY4zmHn3amfXdfQ)唯一不同的地方,就是推导dp[i][0]的时候,第i天买入股票的情况**。 +**注意这里和[121. 买卖股票的最佳时机](https://programmercarl.com/0121.买卖股票的最佳时机.html)唯一不同的地方,就是推导dp[i][0]的时候,第i天买入股票的情况**。 -在[121. 买卖股票的最佳时机](https://mp.weixin.qq.com/s/keWo5qYJY4zmHn3amfXdfQ)中,因为股票全程只能买卖一次,所以如果买入股票,那么第i天持有股票即dp[i][0]一定就是 -prices[i]。 +在[121. 买卖股票的最佳时机](https://programmercarl.com/0121.买卖股票的最佳时机.html)中,因为股票全程只能买卖一次,所以如果买入股票,那么第i天持有股票即dp[i][0]一定就是 -prices[i]。 而本题,因为一只股票可以买卖多次,所以当第i天买入股票的时候,所持有的现金可能有之前买卖过的利润。 代码如下:(注意代码中的注释,标记了和121.买卖股票的最佳时机唯一不同的地方) -```C++ +```CPP class Solution { public: int maxProfit(vector& prices) { @@ -169,7 +169,7 @@ public: ## 买卖股票的最佳时机III -[动态规划:123.买卖股票的最佳时机III](https://mp.weixin.qq.com/s/Sbs157mlVDtAR0gbLpdKzg)最多买卖两次,问最大收益。 +[动态规划:123.买卖股票的最佳时机III](https://programmercarl.com/0123.买卖股票的最佳时机III.html)最多买卖两次,问最大收益。 【动态规划】 @@ -204,7 +204,7 @@ dp[i][4] = max(dp[i - 1][4], dp[i - 1][3] + prices[i]); 代码如下: -```C++ +```CPP // 版本一 class Solution { public: @@ -230,7 +230,7 @@ public: 当然,大家可以看到力扣官方题解里的一种优化空间写法,我这里给出对应的C++版本: -```C++ +```CPP // 版本二 class Solution { public: @@ -257,7 +257,7 @@ public: ## 买卖股票的最佳时机IV -[动态规划:188.买卖股票的最佳时机IV](https://mp.weixin.qq.com/s/jtxZJWAo2y5sUsW647Z5cw) 最多买卖k笔交易,问最大收益。 +[动态规划:188.买卖股票的最佳时机IV](https://programmercarl.com/0188.买卖股票的最佳时机IV.html) 最多买卖k笔交易,问最大收益。 使用二维数组 dp[i][j] :第i天的状态为j,所剩下的最大现金是dp[i][j] @@ -291,7 +291,7 @@ dp[i][2] = max(dp[i - 1][i] + prices[i], dp[i][2]) 同理可以类比剩下的状态,代码如下: -```C++ +```CPP for (int j = 0; j < 2 * k - 1; j += 2) { dp[i][j + 1] = max(dp[i - 1][j + 1], dp[i - 1][j] - prices[i]); dp[i][j + 2] = max(dp[i - 1][j + 2], dp[i - 1][j + 1] + prices[i]); @@ -300,7 +300,7 @@ for (int j = 0; j < 2 * k - 1; j += 2) { 整体代码如下: -```C++ +```CPP class Solution { public: int maxProfit(int k, vector& prices) { @@ -325,12 +325,12 @@ public: ## 最佳买卖股票时机含冷冻期 -[动态规划:309.最佳买卖股票时机含冷冻期](https://mp.weixin.qq.com/s/TczJGFAPnkjH9ET8kwH1OA) 可以多次买卖但每次卖出有冷冻期1天。 +[动态规划:309.最佳买卖股票时机含冷冻期](https://programmercarl.com/0309.最佳买卖股票时机含冷冻期.html)可以多次买卖但每次卖出有冷冻期1天。 -相对于[动态规划:122.买卖股票的最佳时机II](https://mp.weixin.qq.com/s/d4TRWFuhaY83HPa6t5ZL-w),本题加上了一个冷冻期。 +相对于[动态规划:122.买卖股票的最佳时机II](https://programmercarl.com/0122.买卖股票的最佳时机II(动态规划).html),本题加上了一个冷冻期。 -在[动态规划:122.买卖股票的最佳时机II](https://mp.weixin.qq.com/s/d4TRWFuhaY83HPa6t5ZL-w) 中有两个状态,持有股票后的最多现金,和不持有股票的最多现金。本题则可以花费为四个状态 +在[动态规划:122.买卖股票的最佳时机II](https://programmercarl.com/0122.买卖股票的最佳时机II(动态规划).html) 中有两个状态,持有股票后的最多现金,和不持有股票的最多现金。本题则可以花费为四个状态 dp[i][j]:第i天状态为j,所剩的最多现金为dp[i][j]。 @@ -375,7 +375,7 @@ p[i][3] = dp[i - 1][2]; 综上分析,递推代码如下: -```C++ +```CPP dp[i][0] = max(dp[i - 1][0], max(dp[i - 1][3]- prices[i], dp[i - 1][1]) - prices[i]; dp[i][1] = max(dp[i - 1][1], dp[i - 1][3]); dp[i][2] = dp[i - 1][0] + prices[i]; @@ -384,7 +384,7 @@ dp[i][3] = dp[i - 1][2]; 整体代码如下: -```C++ +```CPP class Solution { public: int maxProfit(vector& prices) { @@ -408,10 +408,10 @@ public: ## 买卖股票的最佳时机含手续费 -[动态规划:714.买卖股票的最佳时机含手续费](https://mp.weixin.qq.com/s/2Cd_uINjerZ25VHH0K2IBQ) 可以多次买卖,但每次有手续费。 +[动态规划:714.买卖股票的最佳时机含手续费](https://programmercarl.com/0714.买卖股票的最佳时机含手续费(动态规划).html) 可以多次买卖,但每次有手续费。 -相对于[动态规划:122.买卖股票的最佳时机II](https://mp.weixin.qq.com/s/d4TRWFuhaY83HPa6t5ZL-w),本题只需要在计算卖出操作的时候减去手续费就可以了,代码几乎是一样的。 +相对于[动态规划:122.买卖股票的最佳时机II](https://programmercarl.com/0122.买卖股票的最佳时机II(动态规划).html),本题只需要在计算卖出操作的时候减去手续费就可以了,代码几乎是一样的。 唯一差别在于递推公式部分,所以本篇也就不按照动规五部曲详细讲解了,主要讲解一下递推公式部分。 @@ -435,11 +435,11 @@ dp[i][1] 表示第i天不持有股票所得最多现金 所以:dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] + prices[i] - fee); -**本题和[动态规划:122.买卖股票的最佳时机II](https://mp.weixin.qq.com/s/d4TRWFuhaY83HPa6t5ZL-w)的区别就是这里需要多一个减去手续费的操作**。 +**本题和[动态规划:122.买卖股票的最佳时机II](https://programmercarl.com/0122.买卖股票的最佳时机II(动态规划).html)的区别就是这里需要多一个减去手续费的操作**。 以上分析完毕,代码如下: -```C++ +```CPP class Solution { public: int maxProfit(vector& prices, int fee) { @@ -486,4 +486,4 @@ Go: * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/动态规划总结篇.md b/problems/动态规划总结篇.md index 797f426a..e7e57351 100644 --- a/problems/动态规划总结篇.md +++ b/problems/动态规划总结篇.md @@ -10,7 +10,7 @@ 如今动态规划已经讲解了42道经典题目,共50篇文章,是时候做一篇总结了。 -关于动态规划,在专题第一篇[关于动态规划,你该了解这些!](https://mp.weixin.qq.com/s/ocZwfPlCWrJtVGACqFNAag)就说了动规五部曲,**而且强调了五部对解动规题目至关重要!** +关于动态规划,在专题第一篇[关于动态规划,你该了解这些!](https://programmercarl.com/动态规划理论基础.html)就说了动规五部曲,**而且强调了五部对解动规题目至关重要!** 这是Carl做过一百多道动规题目总结出来的经验结晶啊,如果大家跟着「代码随想哦」刷过动规专题,一定会对这动规五部曲的作用感受极其深刻。 @@ -35,7 +35,7 @@ 动规五部曲里,哪一部没想清楚,这道题目基本就做不出来,即使做出来了也没有想清楚,而是朦朦胧胧的就把题目过了。 * 如果想不清楚dp数组的具体含义,递归公式从何谈起,甚至初始化的时候就写错了。 -* 例如[动态规划:不同路径还不够,要有障碍!](https://mp.weixin.qq.com/s/lhqF0O4le9-wvalptOVOww) 在这道题目中,初始化才是重头戏 +* 例如[动态规划:不同路径还不够,要有障碍!](https://programmercarl.com/0063.不同路径II.html) 在这道题目中,初始化才是重头戏 * 如果看过背包系列,特别是完全背包,那么两层for循环先后顺序绝对可以搞懵很多人,反而递归公式是简单的。 * 至于推导dp数组的重要性,动规专题里几乎每篇Carl都反复强调,当程序结果不对的时候,一定要自己推导公式,看看和程序打印的日志是否一样。 @@ -43,72 +43,72 @@ ## 动划基础 -* [关于动态规划,你该了解这些!](https://mp.weixin.qq.com/s/ocZwfPlCWrJtVGACqFNAag) -* [动态规划:斐波那契数](https://mp.weixin.qq.com/s/ko0zLJplF7n_4TysnPOa_w) -* [动态规划:爬楼梯](https://mp.weixin.qq.com/s/Ohop0jApSII9xxOMiFhGIw) -* [动态规划:使用最小花费爬楼梯](https://mp.weixin.qq.com/s/djZB9gkyLFAKcQcSvKDorA) -* [动态规划:不同路径](https://mp.weixin.qq.com/s/MGgGIt4QCpFMROE9X9he_A) -* [动态规划:不同路径还不够,要有障碍!](https://mp.weixin.qq.com/s/lhqF0O4le9-wvalptOVOww) -* [动态规划:整数拆分,你要怎么拆?](https://mp.weixin.qq.com/s/cVbyHrsWH_Rfzlj-ESr01A) -* [动态规划:不同的二叉搜索树](https://mp.weixin.qq.com/s/8VE8pDrGxTf8NEVYBDwONw) +* [关于动态规划,你该了解这些!](https://programmercarl.com/动态规划理论基础.html) +* [动态规划:斐波那契数](https://programmercarl.com/0509.斐波那契数.html) +* [动态规划:爬楼梯](https://programmercarl.com/0070.爬楼梯.html) +* [动态规划:使用最小花费爬楼梯](https://programmercarl.com/0746.使用最小花费爬楼梯.html) +* [动态规划:不同路径](https://programmercarl.com/0062.不同路径.html) +* [动态规划:不同路径还不够,要有障碍!](https://programmercarl.com/0063.不同路径II.html) +* [动态规划:整数拆分,你要怎么拆?](https://programmercarl.com/0343.整数拆分.html) +* [动态规划:不同的二叉搜索树](https://programmercarl.com/0096.不同的二叉搜索树.html) ## 背包问题系列 背包问题大纲 -* [动态规划:关于01背包问题,你该了解这些!](https://mp.weixin.qq.com/s/FwIiPPmR18_AJO5eiidT6w) -* [动态规划:关于01背包问题,你该了解这些!(滚动数组)](https://mp.weixin.qq.com/s/M4uHxNVKRKm5HPjkNZBnFA) -* [动态规划:分割等和子集可以用01背包!](https://mp.weixin.qq.com/s/sYw3QtPPQ5HMZCJcT4EaLQ) -* [动态规划:最后一块石头的重量 II](https://mp.weixin.qq.com/s/WbwAo3jaUaNJjvhHgq0BGg) -* [动态规划:目标和!](https://mp.weixin.qq.com/s/2pWmaohX75gwxvBENS-NCw) -* [动态规划:一和零!](https://mp.weixin.qq.com/s/x-u3Dsp76DlYqtCe0xEKJw) -* [动态规划:关于完全背包,你该了解这些!](https://mp.weixin.qq.com/s/akwyxlJ4TLvKcw26KB9uJw) -* [动态规划:给你一些零钱,你要怎么凑?](https://mp.weixin.qq.com/s/PlowDsI4WMBOzf3q80AksQ) -* [动态规划:Carl称它为排列总和!](https://mp.weixin.qq.com/s/Iixw0nahJWQgbqVNk8k6gA) -* [动态规划:以前我没得选,现在我选择再爬一次!](https://mp.weixin.qq.com/s/e_wacnELo-2PG76EjrUakA) -* [动态规划: 给我个机会,我再兑换一次零钱](https://mp.weixin.qq.com/s/dyk-xNilHzNtVdPPLObSeQ) -* [动态规划:一样的套路,再求一次完全平方数](https://mp.weixin.qq.com/s/VfJT78p7UGpDZsapKF_QJQ) -* [动态规划:单词拆分](https://mp.weixin.qq.com/s/3Spx1B6MbIYjS8YkVbByzA) -* [动态规划:关于多重背包,你该了解这些!](https://mp.weixin.qq.com/s/b-UUUmbvG7URWyCjQkiuuQ) -* [听说背包问题很难? 这篇总结篇来拯救你了](https://mp.weixin.qq.com/s/ZOehl3U1mDiyOQjFG1wNJA) +* [动态规划:关于01背包问题,你该了解这些!](https://programmercarl.com/背包理论基础01背包-1.html) +* [动态规划:关于01背包问题,你该了解这些!(滚动数组)](https://programmercarl.com/背包理论基础01背包-2.html) +* [动态规划:分割等和子集可以用01背包!](https://programmercarl.com/0416.分割等和子集.html) +* [动态规划:最后一块石头的重量 II](https://programmercarl.com/1049.最后一块石头的重量II.html) +* [动态规划:目标和!](https://programmercarl.com/0494.目标和.html) +* [动态规划:一和零!](https://programmercarl.com/0474.一和零.html) +* [动态规划:关于完全背包,你该了解这些!](https://programmercarl.com/背包问题理论基础完全背包.html) +* [动态规划:给你一些零钱,你要怎么凑?](https://programmercarl.com/0518.零钱兑换II.html) +* [动态规划:Carl称它为排列总和!](https://programmercarl.com/0377.组合总和Ⅳ.html) +* [动态规划:以前我没得选,现在我选择再爬一次!](https://programmercarl.com/0070.爬楼梯完全背包版本.html) +* [动态规划: 给我个机会,我再兑换一次零钱](https://programmercarl.com/0322.零钱兑换.html) +* [动态规划:一样的套路,再求一次完全平方数](https://programmercarl.com/0279.完全平方数.html) +* [动态规划:单词拆分](https://programmercarl.com/0139.单词拆分.html) +* [动态规划:关于多重背包,你该了解这些!](https://programmercarl.com/背包问题理论基础多重背包.html) +* [听说背包问题很难? 这篇总结篇来拯救你了](https://programmercarl.com/背包总结篇.html) ## 打家劫舍系列 -* [动态规划:开始打家劫舍!](https://mp.weixin.qq.com/s/UZ31WdLEEFmBegdgLkJ8Dw) -* [动态规划:继续打家劫舍!](https://mp.weixin.qq.com/s/kKPx4HpH3RArbRcxAVHbeQ) -* [动态规划:还要打家劫舍!](https://mp.weixin.qq.com/s/BOJ1lHsxbQxUZffXlgglEQ) +* [动态规划:开始打家劫舍!](https://programmercarl.com/0198.打家劫舍.html) +* [动态规划:继续打家劫舍!](https://programmercarl.com/0213.打家劫舍II.html) +* [动态规划:还要打家劫舍!](https://programmercarl.com/0337.打家劫舍III.html) ## 股票系列 股票问题总结 -* [动态规划:买卖股票的最佳时机](https://mp.weixin.qq.com/s/keWo5qYJY4zmHn3amfXdfQ) -* [动态规划:本周我们都讲了这些(系列六)](https://mp.weixin.qq.com/s/GVu-6eF0iNkpVDKRXTPOTA) -* [动态规划:买卖股票的最佳时机II](https://mp.weixin.qq.com/s/d4TRWFuhaY83HPa6t5ZL-w) -* [动态规划:买卖股票的最佳时机III](https://mp.weixin.qq.com/s/Sbs157mlVDtAR0gbLpdKzg) -* [动态规划:买卖股票的最佳时机IV](https://mp.weixin.qq.com/s/jtxZJWAo2y5sUsW647Z5cw) -* [动态规划:最佳买卖股票时机含冷冻期](https://mp.weixin.qq.com/s/TczJGFAPnkjH9ET8kwH1OA) -* [动态规划:本周我们都讲了这些(系列七)](https://mp.weixin.qq.com/s/vdzDlrEvhXWRzblTnOnzKg) -* [动态规划:买卖股票的最佳时机含手续费](https://mp.weixin.qq.com/s/2Cd_uINjerZ25VHH0K2IBQ) -* [动态规划:股票系列总结篇](https://mp.weixin.qq.com/s/sC5XyEtDQWkonKnbCvZhDw) +* [动态规划:买卖股票的最佳时机](https://programmercarl.com/0121.买卖股票的最佳时机.html) +* [动态规划:本周我们都讲了这些(系列六)](https://programmercarl.com/周总结/20210225动规周末总结.html) +* [动态规划:买卖股票的最佳时机II](https://programmercarl.com/0122.买卖股票的最佳时机II(动态规划).html) +* [动态规划:买卖股票的最佳时机III](https://programmercarl.com/0123.买卖股票的最佳时机III.html) +* [动态规划:买卖股票的最佳时机IV](https://programmercarl.com/0188.买卖股票的最佳时机IV.html) +* [动态规划:最佳买卖股票时机含冷冻期](https://programmercarl.com/0309.最佳买卖股票时机含冷冻期.html) +* [动态规划:本周我们都讲了这些(系列七)](https://programmercarl.com/周总结/20210304动规周末总结.html) +* [动态规划:买卖股票的最佳时机含手续费](https://programmercarl.com/0714.买卖股票的最佳时机含手续费(动态规划).html) +* [动态规划:股票系列总结篇](https://programmercarl.com/动态规划-股票问题总结篇.html) ## 子序列系列 -* [动态规划:最长递增子序列](https://mp.weixin.qq.com/s/f8nLO3JGfgriXep_gJQpqQ) -* [动态规划:最长连续递增序列](https://mp.weixin.qq.com/s/c0Nn0TtjkTISVdqRsyMmyA) -* [动态规划:最长重复子数组](https://mp.weixin.qq.com/s/U5WaWqBwdoxzQDotOdWqZg) -* [动态规划:最长公共子序列](https://mp.weixin.qq.com/s/Qq0q4HaE4TyasCTj2WGFOg) -* [动态规划:不相交的线](https://mp.weixin.qq.com/s/krfYzSYEO8jIoVfyHzR0rw) -* [动态规划:最大子序和](https://mp.weixin.qq.com/s/2Xtyi2L4r8sM-BcxgUKmcA) -* [动态规划:判断子序列](https://mp.weixin.qq.com/s/2pjT4B4fjfOx5iB6N6xyng) -* [动态规划:不同的子序列](https://mp.weixin.qq.com/s/1SULY2XVSROtk_hsoVLu8A) -* [动态规划:两个字符串的删除操作](https://mp.weixin.qq.com/s/a8BerpqSf76DCqkPDJrpYg) -* [动态规划:编辑距离](https://mp.weixin.qq.com/s/8aG71XjSgZG6kZbiAdkJnQ) -* [为了绝杀编辑距离,我做了三步铺垫,你都知道么?](https://mp.weixin.qq.com/s/kbs4kCUzg8gPFttF9H3Yyw) -* [动态规划:回文子串](https://mp.weixin.qq.com/s/2WetyP6IYQ6VotegepVpEw) -* [动态规划:最长回文子序列](https://mp.weixin.qq.com/s/jbd3p4QPm5Kh1s2smTzWag) +* [动态规划:最长递增子序列](https://programmercarl.com/0300.最长上升子序列.html) +* [动态规划:最长连续递增序列](https://programmercarl.com/0674.最长连续递增序列.html) +* [动态规划:最长重复子数组](https://programmercarl.com/0718.最长重复子数组.html) +* [动态规划:最长公共子序列](https://programmercarl.com/1143.最长公共子序列.html) +* [动态规划:不相交的线](https://programmercarl.com/1035.不相交的线.html) +* [动态规划:最大子序和](https://programmercarl.com/0053.最大子序和(动态规划).html) +* [动态规划:判断子序列](https://programmercarl.com/0392.判断子序列.html) +* [动态规划:不同的子序列](https://programmercarl.com/0115.不同的子序列.html) +* [动态规划:两个字符串的删除操作](https://programmercarl.com/0583.两个字符串的删除操作.html) +* [动态规划:编辑距离](https://programmercarl.com/0072.编辑距离.html) +* [为了绝杀编辑距离,我做了三步铺垫,你都知道么?](https://programmercarl.com/为了绝杀编辑距离,卡尔做了三步铺垫.html) +* [动态规划:回文子串](https://programmercarl.com/0647.回文子串.html) +* [动态规划:最长回文子序列](https://programmercarl.com/0516.最长回文子序列.html) ## 动规结束语 @@ -136,5 +136,5 @@ * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/动态规划理论基础.md b/problems/动态规划理论基础.md index 250fa57d..fd7977a6 100644 --- a/problems/动态规划理论基础.md +++ b/problems/动态规划理论基础.md @@ -12,7 +12,7 @@ 所以动态规划中每一个状态一定是由上一个状态推导出来的,**这一点就区分于贪心**,贪心没有状态推导,而是从局部直接选最优的, -在[关于贪心算法,你该了解这些!](https://mp.weixin.qq.com/s/O935TaoHE9Eexwe_vSbRAg)中我举了一个背包问题的例子。 +在[关于贪心算法,你该了解这些!](https://programmercarl.com/贪心算法理论基础.html)中我举了一个背包问题的例子。 例如:有N件物品和一个最多能背重量为W 的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。**每件物品只能用一次**,求解将哪些物品装入背包里物品价值总和最大。 @@ -140,4 +140,4 @@ Go: * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/双指针总结.md b/problems/双指针总结.md index b03d3ff2..11d6ffa4 100644 --- a/problems/双指针总结.md +++ b/problems/双指针总结.md @@ -12,7 +12,7 @@ # 数组篇 -在[数组:就移除个元素很难么?](https://mp.weixin.qq.com/s/RMkulE4NIb6XsSX83ra-Ww)中,原地移除数组上的元素,我们说到了数组上的元素,不能真正的删除,只能覆盖。 +在[数组:就移除个元素很难么?](https://programmercarl.com/0027.移除元素.html)中,原地移除数组上的元素,我们说到了数组上的元素,不能真正的删除,只能覆盖。 一些同学可能会写出如下代码(伪代码): @@ -30,11 +30,11 @@ for (int i = 0; i < array.size(); i++) { # 字符串篇 -在[字符串:这道题目,使用库函数一行代码搞定](https://mp.weixin.qq.com/s/_rNm66OJVl92gBDIbGpA3w)中讲解了反转字符串,注意这里强调要原地反转,要不然就失去了题目的意义。 +在[字符串:这道题目,使用库函数一行代码搞定](https://programmercarl.com/0344.反转字符串.html)中讲解了反转字符串,注意这里强调要原地反转,要不然就失去了题目的意义。 使用双指针法,**定义两个指针(也可以说是索引下表),一个从字符串前面,一个从字符串后面,两个指针同时向中间移动,并交换元素。**,时间复杂度是O(n)。 -在[替换空格](https://mp.weixin.qq.com/s/69HNjR4apcRSAo_KyknPjA) 中介绍使用双指针填充字符串的方法,如果想把这道题目做到极致,就不要只用额外的辅助空间了! +在[替换空格](https://programmercarl.com/剑指Offer05.替换空格.html) 中介绍使用双指针填充字符串的方法,如果想把这道题目做到极致,就不要只用额外的辅助空间了! 思路就是**首先扩充数组到每个空格替换成"%20"之后的大小。然后双指针从后向前替换空格。** @@ -44,7 +44,7 @@ for (int i = 0; i < array.size(); i++) { **其实很多数组(字符串)填充类的问题,都可以先预先给数组扩容带填充后的大小,然后在从后向前进行操作。** -那么在[字符串:花式反转还不够!](https://mp.weixin.qq.com/s/4j6vPFHkFAXnQhmSkq2X9g)中,我们使用双指针法,用O(n)的时间复杂度完成字符串删除类的操作,因为题目要产出冗余空格。 +那么在[字符串:花式反转还不够!](https://programmercarl.com/0151.翻转字符串里的单词.html)中,我们使用双指针法,用O(n)的时间复杂度完成字符串删除类的操作,因为题目要产出冗余空格。 **在删除冗余空格的过程中,如果不注意代码效率,很容易写成了O(n^2)的时间复杂度。其实使用双指针法O(n)就可以搞定。** @@ -54,19 +54,19 @@ for (int i = 0; i < array.size(); i++) { 翻转链表是现场面试,白纸写代码的好题,考察了候选者对链表以及指针的熟悉程度,而且代码也不长,适合在白纸上写。 -在[链表:听说过两天反转链表又写不出来了?](https://mp.weixin.qq.com/s/ckEvIVGcNLfrz6OLOMoT0A)中,讲如何使用双指针法来翻转链表,**只需要改变链表的next指针的指向,直接将链表反转 ,而不用重新定义一个新的链表。** +在[链表:听说过两天反转链表又写不出来了?](https://programmercarl.com/0206.翻转链表.html)中,讲如何使用双指针法来翻转链表,**只需要改变链表的next指针的指向,直接将链表反转 ,而不用重新定义一个新的链表。** 思路还是很简单的,代码也不长,但是想在白纸上一次性写出bugfree的代码,并不是容易的事情。 -在链表中求环,应该是双指针在链表里最经典的应用,在[链表:环找到了,那入口呢?](https://mp.weixin.qq.com/s/gt_VH3hQTqNxyWcl1ECSbQ)中讲解了如何通过双指针判断是否有环,而且还要找到环的入口。 +在链表中求环,应该是双指针在链表里最经典的应用,在[链表:环找到了,那入口呢?](https://programmercarl.com/0142.环形链表II.html)中讲解了如何通过双指针判断是否有环,而且还要找到环的入口。 **使用快慢指针(双指针法),分别定义 fast 和 slow指针,从头结点出发,fast指针每次移动两个节点,slow指针每次移动一个节点,如果 fast 和 slow指针在途中相遇 ,说明这个链表有环。** -那么找到环的入口,其实需要点简单的数学推理,我在文章中把找环的入口清清楚楚的推理的一遍,如果对找环入口不够清楚的同学建议自己看一看[链表:环找到了,那入口呢?](https://mp.weixin.qq.com/s/gt_VH3hQTqNxyWcl1ECSbQ)。 +那么找到环的入口,其实需要点简单的数学推理,我在文章中把找环的入口清清楚楚的推理的一遍,如果对找环入口不够清楚的同学建议自己看一看[链表:环找到了,那入口呢?](https://programmercarl.com/0142.环形链表II.html)。 # N数之和篇 -在[哈希表:解决了两数之和,那么能解决三数之和么?](https://mp.weixin.qq.com/s/QfTNEByq1YlNSXRKEumwHg)中,讲到使用哈希法可以解决1.两数之和的问题 +在[哈希表:解决了两数之和,那么能解决三数之和么?](https://programmercarl.com/0015.三数之和.html)中,讲到使用哈希法可以解决1.两数之和的问题 其实使用双指针也可以解决1.两数之和的问题,只不过1.两数之和求的是两个元素的下标,没法用双指针,如果改成求具体两个元素的数值就可以了,大家可以尝试用双指针做一个leetcode上两数之和的题目,就可以体会到我说的意思了。 @@ -82,7 +82,7 @@ for (int i = 0; i < array.size(); i++) { 只用双指针法时间复杂度为O(n^2),但比哈希法的O(n^2)效率高得多,哈希法在使用两层for循环的时候,能做的剪枝操作很有限。 -在[双指针法:一样的道理,能解决四数之和](https://mp.weixin.qq.com/s/SBU3THi1Kv6Sar7htqCB2Q)中,讲到了四数之和,其实思路是一样的,**在三数之和的基础上再套一层for循环,依然是使用双指针法。** +在[双指针法:一样的道理,能解决四数之和](https://programmercarl.com/0018.四数之和.html)中,讲到了四数之和,其实思路是一样的,**在三数之和的基础上再套一层for循环,依然是使用双指针法。** 对于三数之和使用双指针法就是将原本暴力O(n^3)的解法,降为O(n^2)的解法,四数之和的双指针解法就是将原本暴力O(n^4)的解法,降为O(n^3)的解法。 @@ -100,4 +100,4 @@ for (int i = 0; i < array.size(); i++) { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/周总结/20200927二叉树周末总结.md b/problems/周总结/20200927二叉树周末总结.md index a6d73c01..5a7e398a 100644 --- a/problems/周总结/20200927二叉树周末总结.md +++ b/problems/周总结/20200927二叉树周末总结.md @@ -7,13 +7,13 @@ ## 周一 -本周我们开始讲解了二叉树,在[关于二叉树,你该了解这些!](https://mp.weixin.qq.com/s/_ymfWYvTNd2GvWvC5HOE4A)中讲解了二叉树的理论基础。 +本周我们开始讲解了二叉树,在[关于二叉树,你该了解这些!](https://programmercarl.com/二叉树理论基础.html)中讲解了二叉树的理论基础。 有同学会把红黑树和二叉平衡搜索树弄分开了,其实红黑树就是一种二叉平衡搜索树,这两个树不是独立的,所以C++中map、multimap、set、multiset的底层实现机制是二叉平衡搜索树,再具体一点是红黑树。 对于二叉树节点的定义,C++代码如下: -```C++ +```CPP struct TreeNode { int val; TreeNode *left; @@ -35,7 +35,7 @@ TreeNode* a = new TreeNode(9); 没有构造函数的话就要这么写: -```C++ +```CPP TreeNode* a = new TreeNode(); a->val = 9; a->left = NULL; @@ -48,9 +48,9 @@ morris遍历是二叉树遍历算法的超强进阶算法,morris遍历可以 ## 周二 -在[二叉树:一入递归深似海,从此offer是路人](https://mp.weixin.qq.com/s/PwVIfxDlT3kRgMASWAMGhA)中讲到了递归三要素,以及前中后序的递归写法。 +在[二叉树:一入递归深似海,从此offer是路人](https://programmercarl.com/二叉树的递归遍历.html)中讲到了递归三要素,以及前中后序的递归写法。 -文章中我给出了leetcode上三道二叉树的前中后序题目,但是看完[二叉树:一入递归深似海,从此offer是路人](https://mp.weixin.qq.com/s/PwVIfxDlT3kRgMASWAMGhA),依然可以解决n叉树的前后序遍历,在leetcode上分别是 +文章中我给出了leetcode上三道二叉树的前中后序题目,但是看完[二叉树:一入递归深似海,从此offer是路人](https://programmercarl.com/二叉树的递归遍历.html),依然可以解决n叉树的前后序遍历,在leetcode上分别是 * 589. N叉树的前序遍历 * 590. N叉树的后序遍历 @@ -58,7 +58,7 @@ morris遍历是二叉树遍历算法的超强进阶算法,morris遍历可以 ## 周三 -在[二叉树:听说递归能做的,栈也能做!](https://mp.weixin.qq.com/s/c_zCrGHIVlBjUH_hJtghCg)中我们开始用栈来实现递归的写法,也就是所谓的迭代法。 +在[二叉树:听说递归能做的,栈也能做!](https://programmercarl.com/二叉树的迭代遍历.html)中我们开始用栈来实现递归的写法,也就是所谓的迭代法。 细心的同学发现文中前后序遍历空节点是否入栈写法是不同的 @@ -66,7 +66,7 @@ morris遍历是二叉树遍历算法的超强进阶算法,morris遍历可以 拿前序遍历来举例,空节点入栈: -```C++ +```CPP class Solution { public: vector preorderTraversal(TreeNode* root) { @@ -88,7 +88,7 @@ public: 前序遍历空节点不入栈的代码:(注意注释部分和上文的区别) -```C++ +```CPP class Solution { public: vector preorderTraversal(TreeNode* root) { @@ -121,7 +121,7 @@ public: ## 周四 -在[二叉树:前中后序迭代方式的写法就不能统一一下么?](https://mp.weixin.qq.com/s/WKg0Ty1_3SZkztpHubZPRg)中我们使用空节点作为标记,给出了统一的前中后序迭代法。 +在[二叉树:前中后序迭代方式的写法就不能统一一下么?](https://programmercarl.com/二叉树的统一迭代法.html)中我们使用空节点作为标记,给出了统一的前中后序迭代法。 此时又多了一种前中后序的迭代写法,那么有同学问了:前中后序迭代法是不是一定要统一来写,这样才算是规范。 @@ -131,7 +131,7 @@ public: ## 周五 -在[二叉树:层序遍历登场!](https://mp.weixin.qq.com/s/Gb3BjakIKGNpup2jYtTzog)中我们介绍了二叉树的另一种遍历方式(图论中广度优先搜索在二叉树上的应用)即:层序遍历。 +在[二叉树:层序遍历登场!](https://programmercarl.com/0102.二叉树的层序遍历.html)中我们介绍了二叉树的另一种遍历方式(图论中广度优先搜索在二叉树上的应用)即:层序遍历。 看完这篇文章,去leetcode上怒刷五题,文章中 编号107题目的样例图放错了(原谅我匆忙之间总是手抖),但不影响大家理解。 @@ -141,7 +141,7 @@ public: ## 周六 -在[二叉树:你真的会翻转二叉树么?](https://mp.weixin.qq.com/s/6gY1MiXrnm-khAAJiIb5Bg)中我们把翻转二叉树这么一道简单又经典的问题,充分的剖析了一波,相信就算做过这道题目的同学,看完本篇之后依然有所收获! +在[二叉树:你真的会翻转二叉树么?](https://programmercarl.com/0226.翻转二叉树.html)中我们把翻转二叉树这么一道简单又经典的问题,充分的剖析了一波,相信就算做过这道题目的同学,看完本篇之后依然有所收获! **文中我指的是递归的中序遍历是不行的,因为使用递归的中序遍历,某些节点的左右孩子会翻转两次。** @@ -203,3 +203,4 @@ public: **本周我们都是讲解了二叉树,从理论基础到遍历方式,从递归到迭代,从深度遍历到广度遍历,最后再用了一个翻转二叉树的题目把我们之前讲过的遍历方式都串了起来。** +
diff --git a/problems/周总结/20201003二叉树周末总结.md b/problems/周总结/20201003二叉树周末总结.md index 0cb8b654..a0b8c2dd 100644 --- a/problems/周总结/20201003二叉树周末总结.md +++ b/problems/周总结/20201003二叉树周末总结.md @@ -7,7 +7,7 @@ ## 周一 -本周刚开始我们讲解了判断二叉树是否对称的写法, [二叉树:我对称么?](https://mp.weixin.qq.com/s/Kgf0gjvlDlNDfKIH2b1Oxg)。 +本周刚开始我们讲解了判断二叉树是否对称的写法, [二叉树:我对称么?](https://programmercarl.com/0101.对称二叉树.html)。 这道题目的本质是要比较两个树(这两个树是根节点的左右子树),遍历两棵树而且要比较内侧和外侧节点,所以准确的来说是一个树的遍历顺序是左右中,一个树的遍历顺序是右左中。 @@ -17,11 +17,11 @@ * 100.相同的树 * 572.另一个树的子树 -**[二叉树:我对称么?](https://mp.weixin.qq.com/s/Kgf0gjvlDlNDfKIH2b1Oxg)中的递归法和迭代法只需要稍作修改其中一个树的遍历顺序,便可刷了100.相同的树。** +**[二叉树:我对称么?](https://programmercarl.com/0101.对称二叉树.html)中的递归法和迭代法只需要稍作修改其中一个树的遍历顺序,便可刷了100.相同的树。** 100.相同的树的递归代码如下: -```C++ +```CPP class Solution { public: bool compare(TreeNode* left, TreeNode* right) { @@ -48,7 +48,7 @@ public: 100.相同的树,精简之后代码如下: -```C++ +```CPP class Solution { public: bool compare(TreeNode* left, TreeNode* right) { @@ -67,7 +67,7 @@ public: 100.相同的树,迭代法代码如下: -```C++ +```CPP class Solution { public: @@ -102,14 +102,14 @@ public: ## 周二 -在[二叉树:看看这些树的最大深度](https://mp.weixin.qq.com/s/guKwV-gSNbA1CcbvkMtHBg)中,我们讲解了如何求二叉树的最大深度。 +在[二叉树:看看这些树的最大深度](https://programmercarl.com/0104.二叉树的最大深度.html)中,我们讲解了如何求二叉树的最大深度。 本题可以使用前序,也可以使用后序遍历(左右中),使用前序求的就是深度,使用后序呢求的是高度。 -**而根节点的高度就是二叉树的最大深度**,所以本题中我们通过后序求的根节点高度来求的二叉树最大深度,所以[二叉树:看看这些树的最大深度](https://mp.weixin.qq.com/s/guKwV-gSNbA1CcbvkMtHBg)中使用的是后序遍历。 +**而根节点的高度就是二叉树的最大深度**,所以本题中我们通过后序求的根节点高度来求的二叉树最大深度,所以[二叉树:看看这些树的最大深度](https://programmercarl.com/0104.二叉树的最大深度.html)中使用的是后序遍历。 本题当然也可以使用前序,代码如下:(**充分表现出求深度回溯的过程**) -```C++ +```CPP class Solution { public: int result; @@ -143,7 +143,7 @@ public: 注意以上代码是为了把细节体现出来,简化一下代码如下: -```C++ +```CPP class Solution { public: int result; @@ -169,7 +169,7 @@ public: ## 周三 -在[二叉树:看看这些树的最小深度](https://mp.weixin.qq.com/s/BH8-gPC3_QlqICDg7rGSGA)中,我们讲解如何求二叉树的最小深度, 这道题目要是稍不留心很容易犯错。 +在[二叉树:看看这些树的最小深度](https://programmercarl.com/0111.二叉树的最小深度.html)中,我们讲解如何求二叉树的最小深度, 这道题目要是稍不留心很容易犯错。 **注意这里最小深度是从根节点到最近叶子节点的最短路径上的节点数量。注意是叶子节点。** @@ -177,19 +177,19 @@ public: **求二叉树的最小深度和求二叉树的最大深度的差别主要在于处理左右孩子不为空的逻辑。** -注意到这一点之后 递归法和迭代法 都可以参照[二叉树:看看这些树的最大深度](https://mp.weixin.qq.com/s/guKwV-gSNbA1CcbvkMtHBg)写出来。 +注意到这一点之后 递归法和迭代法 都可以参照[二叉树:看看这些树的最大深度](https://programmercarl.com/0104.二叉树的最大深度.html)写出来。 ## 周四 -我们在[二叉树:我有多少个节点?](https://mp.weixin.qq.com/s/2_eAjzw-D0va9y4RJgSmXw)中,讲解了如何求二叉树的节点数量。 +我们在[二叉树:我有多少个节点?](https://programmercarl.com/0222.完全二叉树的节点个数.html)中,讲解了如何求二叉树的节点数量。 -这一天是十一长假的第一天,又是双节,所以简单一些,只要把之前两篇[二叉树:看看这些树的最大深度](https://mp.weixin.qq.com/s/guKwV-gSNbA1CcbvkMtHBg), [二叉树:看看这些树的最小深度](https://mp.weixin.qq.com/s/BH8-gPC3_QlqICDg7rGSGA)都认真看了的话,这道题目可以分分钟刷掉了。 +这一天是十一长假的第一天,又是双节,所以简单一些,只要把之前两篇[二叉树:看看这些树的最大深度](https://programmercarl.com/0104.二叉树的最大深度.html), [二叉树:看看这些树的最小深度](https://programmercarl.com/0111.二叉树的最小深度.html)都认真看了的话,这道题目可以分分钟刷掉了。 估计此时大家对这一类求二叉树节点数量以及求深度应该非常熟练了。 ## 周五 -在[二叉树:我平衡么?](https://mp.weixin.qq.com/s/isUS-0HDYknmC0Rr4R8mww)中讲解了如何判断二叉树是否是平衡二叉树 +在[二叉树:我平衡么?](https://programmercarl.com/0110.平衡二叉树.html)中讲解了如何判断二叉树是否是平衡二叉树 今天讲解一道判断平衡二叉树的题目,其实 方法上我们之前讲解深度的时候都讲过了,但是这次我们通过这道题目彻底搞清楚二叉树高度与深度的问题,以及对应的遍历方式。 @@ -212,7 +212,7 @@ public: ## 周六 -在[二叉树:找我的所有路径?](https://mp.weixin.qq.com/s/Osw4LQD2xVUnCJ-9jrYxJA)中正式涉及到了回溯,很多同学过了这道题目,可能都不知道自己使用了回溯,其实回溯和递归都是相伴相生的。最后我依然给出了迭代法的版本。 +在[二叉树:找我的所有路径?](https://programmercarl.com/0257.二叉树的所有路径.html)中正式涉及到了回溯,很多同学过了这道题目,可能都不知道自己使用了回溯,其实回溯和递归都是相伴相生的。最后我依然给出了迭代法的版本。 我在题解中第一个版本的代码会把回溯的过程充分体现出来,如果大家直接看简洁的代码版本,很可能就会忽略的回溯的存在。 @@ -223,12 +223,12 @@ public: 文中我明确的说了:**回溯就隐藏在traversal(cur->left, path + "->", result);中的 path + "->"。 每次函数调用完,path依然是没有加上"->" 的,这就是回溯了。** 如果还不理解的话,可以把 -``` +```CPP traversal(cur->left, path + "->", result); ``` 改成 -``` +```CPP string tmp = path + "->"; traversal(cur->left, tmp, result); ``` @@ -255,3 +255,4 @@ traversal(cur->left, tmp, result); * 知乎:[代码随想录](https://www.zhihu.com/people/sun-xiu-yang-64) ![](https://img-blog.csdnimg.cn/2021013018121150.png) +
diff --git a/problems/周总结/20201010二叉树周末总结.md b/problems/周总结/20201010二叉树周末总结.md index d62fa5a5..913ad963 100644 --- a/problems/周总结/20201010二叉树周末总结.md +++ b/problems/周总结/20201010二叉树周末总结.md @@ -4,7 +4,7 @@ ## 周一 -在[二叉树:以为使用了递归,其实还隐藏着回溯](https://mp.weixin.qq.com/s/ivLkHzWdhjQQD1rQWe6zWA)中,通过leetcode [257.二叉树的所有路径这道题目](https://mp.weixin.qq.com/s/Osw4LQD2xVUnCJ-9jrYxJA),讲解了递归如何隐藏着回溯,一些代码会把回溯的过程都隐藏了起来了,甚至刷过这道题的同学可能都不知道自己用了回溯。 +在[二叉树:以为使用了递归,其实还隐藏着回溯](https://programmercarl.com/二叉树中递归带着回溯.html)中,通过leetcode [257.二叉树的所有路径这道题目](https://programmercarl.com/0257.二叉树的所有路径.html),讲解了递归如何隐藏着回溯,一些代码会把回溯的过程都隐藏了起来了,甚至刷过这道题的同学可能都不知道自己用了回溯。 文章中第一版代码把每一个细节都展示了输出来了,大家可以清晰的看到回溯的过程。 @@ -15,7 +15,7 @@ ## 周二 -在文章[二叉树:做了这么多题目了,我的左叶子之和是多少?](https://mp.weixin.qq.com/s/gBAgmmFielojU5Wx3wqFTA) 中提供了另一个判断节点属性的思路,平时我们习惯了使用通过节点的左右孩子判断本节点的属性,但发现使用这个思路无法判断左叶子。 +在文章[二叉树:做了这么多题目了,我的左叶子之和是多少?](https://programmercarl.com/0404.左叶子之和.html) 中提供了另一个判断节点属性的思路,平时我们习惯了使用通过节点的左右孩子判断本节点的属性,但发现使用这个思路无法判断左叶子。 此时需要相连的三层之间构成的约束条件,也就是要通过节点的父节点以及孩子节点来判断本节点的属性。 @@ -24,7 +24,7 @@ ## 周三 -在[二叉树:我的左下角的值是多少?](https://mp.weixin.qq.com/s/MH2gbLvzQ91jHPKqiub0Nw)中的题目如果使用递归的写法还是有点难度的,层次遍历反而很简单。 +在[二叉树:我的左下角的值是多少?](https://programmercarl.com/0513.找树左下角的值.html)中的题目如果使用递归的写法还是有点难度的,层次遍历反而很简单。 题目其实就是要在树的**最后一行**找到**最左边的值**。 @@ -32,26 +32,26 @@ 在这篇文章中,我们使用递归算法实实在在的求了一次深度,然后使用靠左的遍历,保证求得靠左的最大深度,而且又一次使用了回溯。 -如果对二叉树的高度与深度又有点模糊了,在看这里[二叉树:我平衡么?](https://mp.weixin.qq.com/s/isUS-0HDYknmC0Rr4R8mww),回忆一下吧。 +如果对二叉树的高度与深度又有点模糊了,在看这里[二叉树:我平衡么?](https://programmercarl.com/0110.平衡二叉树.html),回忆一下吧。 -[二叉树:我的左下角的值是多少?](https://mp.weixin.qq.com/s/MH2gbLvzQ91jHPKqiub0Nw)中把我们之前讲过的内容都过了一遍,此外,还用前序遍历的技巧求得了靠左的最大深度。 +[二叉树:我的左下角的值是多少?](https://programmercarl.com/0513.找树左下角的值.html)中把我们之前讲过的内容都过了一遍,此外,还用前序遍历的技巧求得了靠左的最大深度。 **求二叉树的各种最值,就想应该采用什么样的遍历顺序,确定了遍历循序,其实就和数组求最值一样容易了。** ## 周四 -在[二叉树:递归函数究竟什么时候需要返回值,什么时候不要返回值?](https://mp.weixin.qq.com/s/6TWAVjxQ34kVqROWgcRFOg)中通过两道题目,彻底说清楚递归函数的返回值问题。 +在[二叉树:递归函数究竟什么时候需要返回值,什么时候不要返回值?](https://programmercarl.com/0112.路径总和.html)中通过两道题目,彻底说清楚递归函数的返回值问题。 一般情况下:**如果需要搜索整颗二叉树,那么递归函数就不要返回值,如果要搜索其中一条符合条件的路径,递归函数就需要返回值,因为遇到符合条件的路径了就要及时返回。** 特别是有些时候 递归函数的返回值是bool类型,一些同学会疑惑为啥要加这个,其实就是为了找到一条边立刻返回。 -其实还有一种就是后序遍历需要根据左右递归的返回值推出中间节点的状态,这种需要有返回值,例如[222.完全二叉树](https://mp.weixin.qq.com/s/2_eAjzw-D0va9y4RJgSmXw),[110.平衡二叉树](https://mp.weixin.qq.com/s/isUS-0HDYknmC0Rr4R8mww),这几道我们之前也讲过。 +其实还有一种就是后序遍历需要根据左右递归的返回值推出中间节点的状态,这种需要有返回值,例如[222.完全二叉树](https://programmercarl.com/0222.完全二叉树的节点个数.html),[110.平衡二叉树](https://programmercarl.com/0110.平衡二叉树.html),这几道我们之前也讲过。 ## 周五 -之前都是讲解遍历二叉树,这次该构造二叉树了,在[二叉树:构造二叉树登场!](https://mp.weixin.qq.com/s/7r66ap2s-shvVvlZxo59xg)中,我们通过前序和中序,后序和中序,构造了唯一的一颗二叉树。 +之前都是讲解遍历二叉树,这次该构造二叉树了,在[二叉树:构造二叉树登场!](https://programmercarl.com/0106.从中序与后序遍历序列构造二叉树.html)中,我们通过前序和中序,后序和中序,构造了唯一的一颗二叉树。 **构造二叉树有三个注意的点:** @@ -65,7 +65,7 @@ ## 周六 -知道了如何构造二叉树,那么使用一个套路就可以解决文章[二叉树:构造一棵最大的二叉树](https://mp.weixin.qq.com/s/1iWJV6Aov23A7xCF4nV88w)中的问题。 +知道了如何构造二叉树,那么使用一个套路就可以解决文章[二叉树:构造一棵最大的二叉树](https://programmercarl.com/0654.最大二叉树.html)中的问题。 **注意类似用数组构造二叉树的题目,每次分隔尽量不要定义新的数组,而是通过下表索引直接在原数组上操作,这样可以节约时间和空间上的开销。** @@ -77,13 +77,14 @@ 本周我们深度讲解了如下知识点: -1. [递归中如何隐藏着回溯](https://mp.weixin.qq.com/s/ivLkHzWdhjQQD1rQWe6zWA) -2. [如何通过三层关系确定左叶子](https://mp.weixin.qq.com/s/gBAgmmFielojU5Wx3wqFTA) -3. [如何通过二叉树深度来判断左下角的值](https://mp.weixin.qq.com/s/MH2gbLvzQ91jHPKqiub0Nw) -4. [递归函数究竟什么时候需要返回值,什么时候不要返回值?](https://mp.weixin.qq.com/s/6TWAVjxQ34kVqROWgcRFOg) -5. [前序和中序,后序和中序构造唯一二叉树](https://mp.weixin.qq.com/s/7r66ap2s-shvVvlZxo59xg) -6. [使用数组构造某一特性的二叉树](https://mp.weixin.qq.com/s/1iWJV6Aov23A7xCF4nV88w) +1. [递归中如何隐藏着回溯](https://programmercarl.com/二叉树中递归带着回溯.html) +2. [如何通过三层关系确定左叶子](https://programmercarl.com/0404.左叶子之和.html) +3. [如何通过二叉树深度来判断左下角的值](https://programmercarl.com/0513.找树左下角的值.html) +4. [递归函数究竟什么时候需要返回值,什么时候不要返回值?](https://programmercarl.com/0112.路径总和.html) +5. [前序和中序,后序和中序构造唯一二叉树](https://programmercarl.com/0106.从中序与后序遍历序列构造二叉树.html) +6. [使用数组构造某一特性的二叉树](https://programmercarl.com/0654.最大二叉树.html) **如果大家一路跟下来,一定收获满满,如果周末不做这个总结,大家可能都不知道自己收获满满,啊哈!** +
diff --git a/problems/周总结/20201017二叉树周末总结.md b/problems/周总结/20201017二叉树周末总结.md index e642bfb2..4b219834 100644 --- a/problems/周总结/20201017二叉树周末总结.md +++ b/problems/周总结/20201017二叉树周末总结.md @@ -6,9 +6,9 @@ ## 周一 -在[二叉树:合并两个二叉树](https://mp.weixin.qq.com/s/3f5fbjOFaOX_4MXzZ97LsQ)中讲解了如何合并两个二叉树,平时我们都习惯了操作一个二叉树,一起操作两个树可能还有点陌生。 +在[二叉树:合并两个二叉树](https://programmercarl.com/0617.合并二叉树.html)中讲解了如何合并两个二叉树,平时我们都习惯了操作一个二叉树,一起操作两个树可能还有点陌生。 -其实套路是一样,只不过一起操作两个树的指针,我们之前讲过求 [二叉树:我对称么?](https://mp.weixin.qq.com/s/Kgf0gjvlDlNDfKIH2b1Oxg)的时候,已经初步涉及到了 一起遍历两颗二叉树了。 +其实套路是一样,只不过一起操作两个树的指针,我们之前讲过求 [二叉树:我对称么?](https://programmercarl.com/0101.对称二叉树.html)的时候,已经初步涉及到了 一起遍历两颗二叉树了。 **迭代法中,一般一起操作两个树都是使用队列模拟类似层序遍历,同时处理两个树的节点,这种方式最好理解,如果用模拟递归的思路的话,要复杂一些。** @@ -16,7 +16,7 @@ 周二开始讲解一个新的树,二叉搜索树,开始要换一个思路了,如果没有利用好二叉搜索树的特性,就容易把简单题做成了难题了。 -学习[二叉搜索树的特性](https://mp.weixin.qq.com/s/vsKrWRlETxCVsiRr8v_hHg),还是比较容易的。 +学习[二叉搜索树的特性](https://programmercarl.com/0700.二叉搜索树中的搜索.html),还是比较容易的。 大多是二叉搜索树的题目,其实都离不开中序遍历,因为这样就是有序的。 @@ -24,7 +24,7 @@ ## 周三 -了解了二搜索树的特性之后, 开始验证[一颗二叉树是不是二叉搜索树](https://mp.weixin.qq.com/s/8odY9iUX5eSi0eRFSXFD4Q)。 +了解了二搜索树的特性之后, 开始验证[一颗二叉树是不是二叉搜索树](https://programmercarl.com/0098.验证二叉搜索树.html)。 首先在此强调一下二叉搜索树的特性: @@ -46,15 +46,15 @@ **在二叉树中通过两个前后指针作比较,会经常用到**。 -本文[二叉树:我是不是一棵二叉搜索树](https://mp.weixin.qq.com/s/8odY9iUX5eSi0eRFSXFD4Q)中迭代法中为什么没有周一那篇那么简洁了呢,因为本篇是验证二叉搜索树,前提默认它是一棵普通二叉树,所以还是要回归之前老办法。 +本文[二叉树:我是不是一棵二叉搜索树](https://programmercarl.com/0098.验证二叉搜索树.html)中迭代法中为什么没有周一那篇那么简洁了呢,因为本篇是验证二叉搜索树,前提默认它是一棵普通二叉树,所以还是要回归之前老办法。 ## 周四 -了解了[二叉搜索树](https://mp.weixin.qq.com/s/vsKrWRlETxCVsiRr8v_hHg),并且知道[如何判断二叉搜索树](https://mp.weixin.qq.com/s/8odY9iUX5eSi0eRFSXFD4Q),本篇就很简单了。 +了解了[二叉搜索树](https://programmercarl.com/0700.二叉搜索树中的搜索.html),并且知道[如何判断二叉搜索树](https://programmercarl.com/0098.验证二叉搜索树.html),本篇就很简单了。 **要知道二叉搜索树和中序遍历是好朋友!** -在[二叉树:搜索树的最小绝对差](https://mp.weixin.qq.com/s/Hwzml6698uP3qQCC1ctUQQ)中强调了要利用搜索树的特性,把这道题目想象成在一个有序数组上求两个数最小差值,这就是一道送分题了。 +在[二叉树:搜索树的最小绝对差](https://programmercarl.com/0530.二叉搜索树的最小绝对差.html)中强调了要利用搜索树的特性,把这道题目想象成在一个有序数组上求两个数最小差值,这就是一道送分题了。 **需要明确:在有序数组求任意两数最小值差等价于相邻两数的最小值差**。 @@ -64,7 +64,7 @@ 此时大家应该知道遇到二叉搜索树,就想是有序数组,那么在二叉搜索树中求二叉搜索树众数就很简单了。 -在[二叉树:我的众数是多少?](https://mp.weixin.qq.com/s/KSAr6OVQIMC-uZ8MEAnGHg)中我给出了如果是普通二叉树,应该如何求众数的集合,然后进一步讲解了二叉搜索树应该如何求众数集合。 +在[二叉树:我的众数是多少?](https://programmercarl.com/0501.二叉搜索树中的众数.html)中我给出了如果是普通二叉树,应该如何求众数的集合,然后进一步讲解了二叉搜索树应该如何求众数集合。 在求众数集合的时候有一个技巧,因为题目中众数是可以有多个的,所以一般的方法需要遍历两遍才能求出众数的集合。 @@ -74,7 +74,7 @@ ## 周六 -在[二叉树:公共祖先问题](https://mp.weixin.qq.com/s/n6Rk3nc_X3TSkhXHrVmBTQ)中,我们开始讲解如何在二叉树中求公共祖先的问题,本来是打算和二叉搜索树一起讲的,但发现篇幅过长,所以先讲二叉树的公共祖先问题。 +在[二叉树:公共祖先问题](https://programmercarl.com/0236.二叉树的最近公共祖先.html)中,我们开始讲解如何在二叉树中求公共祖先的问题,本来是打算和二叉搜索树一起讲的,但发现篇幅过长,所以先讲二叉树的公共祖先问题。 **如果找到一个节点,发现左子树出现结点p,右子树出现节点q,或者 左子树出现结点q,右子树出现节点p,那么该节点就是节点p和q的最近公共祖先。** @@ -92,13 +92,13 @@ ## 总结 -本周我们讲了[如何合并两个二叉树](https://mp.weixin.qq.com/s/3f5fbjOFaOX_4MXzZ97LsQ),了解了如何操作两个二叉树。 +本周我们讲了[如何合并两个二叉树](https://programmercarl.com/0617.合并二叉树.html),了解了如何操作两个二叉树。 -然后开始另一种树:二叉搜索树,了解[二叉搜索树的特性](https://mp.weixin.qq.com/s/vsKrWRlETxCVsiRr8v_hHg),然后[判断一棵二叉树是不是二叉搜索树](https://mp.weixin.qq.com/s/8odY9iUX5eSi0eRFSXFD4Q)。 +然后开始另一种树:二叉搜索树,了解[二叉搜索树的特性](https://programmercarl.com/0700.二叉搜索树中的搜索.html),然后[判断一棵二叉树是不是二叉搜索树](https://programmercarl.com/0098.验证二叉搜索树.html)。 -了解以上知识之后,就开始利用其特性,做一些二叉搜索树上的题目,[求最小绝对差](https://mp.weixin.qq.com/s/Hwzml6698uP3qQCC1ctUQQ),[求众数集合](https://mp.weixin.qq.com/s/KSAr6OVQIMC-uZ8MEAnGHg)。 +了解以上知识之后,就开始利用其特性,做一些二叉搜索树上的题目,[求最小绝对差](https://programmercarl.com/0530.二叉搜索树的最小绝对差.html),[求众数集合](https://programmercarl.com/0501.二叉搜索树中的众数.html)。 -接下来,开始求二叉树与二叉搜索树的公共祖先问题,单篇篇幅原因,先单独介绍[普通二叉树如何求最近公共祖先](https://mp.weixin.qq.com/s/n6Rk3nc_X3TSkhXHrVmBTQ)。 +接下来,开始求二叉树与二叉搜索树的公共祖先问题,单篇篇幅原因,先单独介绍[普通二叉树如何求最近公共祖先](https://programmercarl.com/0236.二叉树的最近公共祖先.html)。 现在已经讲过了几种二叉树了,二叉树,二叉平衡树,完全二叉树,二叉搜索树,后面还会有平衡二叉搜索树。 那么一些同学难免会有混乱了,我针对如下三个问题,帮大家在捋顺一遍: @@ -116,3 +116,4 @@ 大家如果每天坚持跟下来,会发现又是充实的一周![机智] +
diff --git a/problems/周总结/20201030回溯周末总结.md b/problems/周总结/20201030回溯周末总结.md index cbb0eb8a..3a432bee 100644 --- a/problems/周总结/20201030回溯周末总结.md +++ b/problems/周总结/20201030回溯周末总结.md @@ -13,7 +13,7 @@ 本周我们正式开始了回溯算法系列,那么首先当然是概述。 -在[关于回溯算法,你该了解这些!](https://mp.weixin.qq.com/s/gjSgJbNbd1eAA5WkA-HeWw)中介绍了什么是回溯,回溯法的效率,回溯法解决的问题以及回溯法模板。 +在[关于回溯算法,你该了解这些!](https://programmercarl.com/回溯算法理论基础.html)中介绍了什么是回溯,回溯法的效率,回溯法解决的问题以及回溯法模板。 **回溯是递归的副产品,只要有递归就会有回溯**。 @@ -31,14 +31,14 @@ 回溯法确实不好理解,所以需要把回溯法抽象为一个图形来理解就容易多了,每一道回溯法的题目都可以抽象为树形结构。 -针对很多同学都写不好回溯,我在[关于回溯算法,你该了解这些!](https://mp.weixin.qq.com/s/gjSgJbNbd1eAA5WkA-HeWw)用回溯三部曲,分析了回溯算法,并给出了回溯法的模板。 +针对很多同学都写不好回溯,我在[关于回溯算法,你该了解这些!](https://programmercarl.com/回溯算法理论基础.html)用回溯三部曲,分析了回溯算法,并给出了回溯法的模板。 这个模板会伴随整个回溯法系列! ## 周二 -在[回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)中,我们开始用回溯法解决第一道题目,组合问题。 +在[回溯算法:求组合问题!](https://programmercarl.com/0077.组合.html)中,我们开始用回溯法解决第一道题目,组合问题。 我在文中开始的时候给大家列举k层for循环例子,进而得出都是同样是暴利解法,为什么要用回溯法。 @@ -48,19 +48,19 @@ ## 周三 -针对[回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)还可以做剪枝的操作。 +针对[回溯算法:求组合问题!](https://programmercarl.com/0077.组合.html)还可以做剪枝的操作。 -在[回溯算法:组合问题再剪剪枝](https://mp.weixin.qq.com/s/Ri7spcJMUmph4c6XjPWXQA)中把回溯法代码做了剪枝优化,在文中我依然把问题抽象为一个树形结构,大家可以一目了然剪的究竟是哪里。 +在[回溯算法:组合问题再剪剪枝](https://programmercarl.com/0077.组合优化.html)中把回溯法代码做了剪枝优化,在文中我依然把问题抽象为一个树形结构,大家可以一目了然剪的究竟是哪里。 **剪枝精髓是:for循环在寻找起点的时候要有一个范围,如果这个起点到集合终止之间的元素已经不够 题目要求的k个元素了,就没有必要搜索了**。 ## 周四 -在[回溯算法:求组合总和!](https://mp.weixin.qq.com/s/HX7WW6ixbFZJASkRnCTC3w)中,相当于 [回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)加了一个元素总和的限制。 +在[回溯算法:求组合总和!](https://programmercarl.com/0216.组合总和III.html)中,相当于 [回溯算法:求组合问题!](https://programmercarl.com/0077.组合.html)加了一个元素总和的限制。 整体思路还是一样的,本题的剪枝会好想一些,即:**已选元素总和如果已经大于n(题中要求的和)了,那么往后遍历就没有意义了,直接剪掉**。 -在本题中,依然还可以有一个剪枝,就是[回溯算法:组合问题再剪剪枝](https://mp.weixin.qq.com/s/Ri7spcJMUmph4c6XjPWXQA)中提到的,对for循环选择的起始范围的剪枝。 +在本题中,依然还可以有一个剪枝,就是[回溯算法:组合问题再剪剪枝](https://programmercarl.com/0077.组合优化.html)中提到的,对for循环选择的起始范围的剪枝。 所以,剪枝的代码,可以把for循环,加上 `i <= 9 - (k - path.size()) + 1` 的限制! @@ -68,11 +68,11 @@ ## 周五 -在[回溯算法:电话号码的字母组合](https://mp.weixin.qq.com/s/e2ua2cmkE_vpYjM3j6HY0A)中,开始用多个集合来求组合,还是熟悉的模板题目,但是有一些细节。 +在[回溯算法:电话号码的字母组合](https://programmercarl.com/0017.电话号码的字母组合.html)中,开始用多个集合来求组合,还是熟悉的模板题目,但是有一些细节。 -例如这里for循环,可不像是在 [回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)和[回溯算法:求组合总和!](https://mp.weixin.qq.com/s/HX7WW6ixbFZJASkRnCTC3w)中从startIndex开始遍历的。 +例如这里for循环,可不像是在 [回溯算法:求组合问题!](https://programmercarl.com/0077.组合.html)和[回溯算法:求组合总和!](https://programmercarl.com/0216.组合总和III.html)中从startIndex开始遍历的。 -**因为本题每一个数字代表的是不同集合,也就是求不同集合之间的组合,而[回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)和[回溯算法:求组合总和!](https://mp.weixin.qq.com/s/HX7WW6ixbFZJASkRnCTC3w)都是是求同一个集合中的组合!** +**因为本题每一个数字代表的是不同集合,也就是求不同集合之间的组合,而[回溯算法:求组合问题!](https://programmercarl.com/0077.组合.html)和[回溯算法:求组合总和!](https://programmercarl.com/0216.组合总和III.html)都是是求同一个集合中的组合!** 如果大家在现场面试的时候,一定要注意各种输入异常的情况,例如本题输入1 * #按键。 @@ -82,9 +82,9 @@ 因为之前链表系列没有写总结,虽然链表系列已经是两个月前的事情,但还是有必要补一下。 -所以给出[链表:总结篇!](https://mp.weixin.qq.com/s/vK0JjSTHfpAbs8evz5hH8A),这里对之前链表理论基础和经典题目进行了总结。 +所以给出[链表:总结篇!](https://programmercarl.com/链表总结篇.html),这里对之前链表理论基础和经典题目进行了总结。 -同时对[链表:环找到了,那入口呢?](https://mp.weixin.qq.com/s/_QVP3IkRZWx9zIpQRgajzA)中求环入口的问题又进行了补充证明,可以说把环形链表的方方面面都讲的很通透了,大家如果没有做过环形链表的题目一定要去做一做。 +同时对[链表:环找到了,那入口呢?](https://programmercarl.com/0142.环形链表II.html)中求环入口的问题又进行了补充证明,可以说把环形链表的方方面面都讲的很通透了,大家如果没有做过环形链表的题目一定要去做一做。 ## 总结 @@ -112,4 +112,4 @@ * B站:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -![](../pics/公众号.png) +
diff --git a/problems/周总结/20201107回溯周末总结.md b/problems/周总结/20201107回溯周末总结.md index 8f2e762d..71bf62de 100644 --- a/problems/周总结/20201107回溯周末总结.md +++ b/problems/周总结/20201107回溯周末总结.md @@ -6,17 +6,17 @@ ## 周一 -在[回溯算法:求组合总和(二)](https://mp.weixin.qq.com/s/FLg8G6EjVcxBjwCbzpACPw)中讲解的组合总和问题,和以前的组合问题还都不一样。 +在[回溯算法:求组合总和(二)](https://programmercarl.com/0039.组合总和.html)中讲解的组合总和问题,和以前的组合问题还都不一样。 -本题和[回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ),[回溯算法:求组合总和!](https://mp.weixin.qq.com/s/HX7WW6ixbFZJASkRnCTC3w)和区别是:本题没有数量要求,可以无限重复,但是有总和的限制,所以间接的也是有个数的限制。 +本题和[回溯算法:求组合问题!](https://programmercarl.com/0077.组合.html),[回溯算法:求组合总和!](https://programmercarl.com/0216.组合总和III.html)和区别是:本题没有数量要求,可以无限重复,但是有总和的限制,所以间接的也是有个数的限制。 不少录友都是看到可以重复选择,就义无反顾的把startIndex去掉了。 **本题还需要startIndex来控制for循环的起始位置,对于组合问题,什么时候需要startIndex呢?** -我举过例子,如果是一个集合来求组合的话,就需要startIndex,例如:[回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ),[回溯算法:求组合总和!](https://mp.weixin.qq.com/s/HX7WW6ixbFZJASkRnCTC3w)。 +我举过例子,如果是一个集合来求组合的话,就需要startIndex,例如:[回溯算法:求组合问题!](https://programmercarl.com/0077.组合.html),[回溯算法:求组合总和!](https://programmercarl.com/0216.组合总和III.html)。 -如果是多个集合取组合,各个集合之间相互不影响,那么就不用startIndex,例如:[回溯算法:电话号码的字母组合](https://mp.weixin.qq.com/s/e2ua2cmkE_vpYjM3j6HY0A) +如果是多个集合取组合,各个集合之间相互不影响,那么就不用startIndex,例如:[回溯算法:电话号码的字母组合](https://programmercarl.com/0017.电话号码的字母组合.html) **注意以上我只是说求组合的情况,如果是排列问题,又是另一套分析的套路,后面我再讲解排列的时候就重点介绍**。 @@ -30,13 +30,13 @@ for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; **在求和问题中,排序之后加剪枝是常见的套路!** -在[回溯算法:求组合总和(二)](https://mp.weixin.qq.com/s/FLg8G6EjVcxBjwCbzpACPw)第一个树形结构没有画出startIndex的作用,**这里这里纠正一下,准确的树形结构如图所示:** +在[回溯算法:求组合总和(二)](https://programmercarl.com/0039.组合总和.html)第一个树形结构没有画出startIndex的作用,**这里这里纠正一下,准确的树形结构如图所示:** ![39.组合总和](https://img-blog.csdnimg.cn/20201123202227835.png) ## 周二 -在[回溯算法:求组合总和(三)](https://mp.weixin.qq.com/s/_1zPYk70NvHsdY8UWVGXmQ)中依旧讲解组合总和问题,本题集合元素会有重复,但要求解集不能包含重复的组合。 +在[回溯算法:求组合总和(三)](https://programmercarl.com/0040.组合总和II.html)中依旧讲解组合总和问题,本题集合元素会有重复,但要求解集不能包含重复的组合。 **所以难就难在去重问题上了**。 @@ -60,7 +60,7 @@ for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; ## 周三 -在[回溯算法:分割回文串](https://mp.weixin.qq.com/s/Pb1epUTbU8fHIht-g_MS5Q)中,我们开始讲解切割问题,虽然最后代码看起来好像是一道模板题,但是从分析到学会套用这个模板,是比较难的。 +在[回溯算法:分割回文串](https://programmercarl.com/0131.分割回文串.html)中,我们开始讲解切割问题,虽然最后代码看起来好像是一道模板题,但是从分析到学会套用这个模板,是比较难的。 我列出如下几个难点: @@ -85,9 +85,9 @@ for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; ## 周四 -如果没有做过[回溯算法:分割回文串](https://mp.weixin.qq.com/s/Pb1epUTbU8fHIht-g_MS5Q)的话,[回溯算法:复原IP地址](https://mp.weixin.qq.com/s/v--VmA8tp9vs4bXCqHhBuA)这道题目应该是比较难的。 +如果没有做过[回溯算法:分割回文串](https://programmercarl.com/0131.分割回文串.html)的话,[回溯算法:复原IP地址](https://programmercarl.com/0093.复原IP地址.html)这道题目应该是比较难的。 -复原IP照[回溯算法:分割回文串](https://mp.weixin.qq.com/s/Pb1epUTbU8fHIht-g_MS5Q)就多了一些限制,例如只能分四段,而且还是更改字符串,插入逗点。 +复原IP照[回溯算法:分割回文串](https://programmercarl.com/0131.分割回文串.html)就多了一些限制,例如只能分四段,而且还是更改字符串,插入逗点。 树形图如下: @@ -109,7 +109,7 @@ if (s.size() > 12) return result; // 剪枝 ## 周五 -在[回溯算法:求子集问题!](https://mp.weixin.qq.com/s/NNRzX-vJ_pjK4qxohd_LtA)中讲解了子集问题,**在树形结构中子集问题是要收集所有节点的结果,而组合问题是收集叶子节点的结果**。 +在[回溯算法:求子集问题!](https://programmercarl.com/0078.子集.html)中讲解了子集问题,**在树形结构中子集问题是要收集所有节点的结果,而组合问题是收集叶子节点的结果**。 如图: @@ -135,13 +135,13 @@ if (startIndex >= nums.size()) { // 终止条件可以不加 ## 周六 -早起的哈希表系列没有总结,所以[哈希表:总结篇!(每逢总结必经典)](https://mp.weixin.qq.com/s/1s91yXtarL-PkX07BfnwLg)如约而至。 +早起的哈希表系列没有总结,所以[哈希表:总结篇!(每逢总结必经典)](https://programmercarl.com/哈希表总结.html)如约而至。 可能之前大家做过很多哈希表的题目,但是没有串成线,总结篇来帮你串成线,捋顺哈希表的整个脉络。 大家对什么时候各种set与map比较疑惑,想深入了解红黑树,哈希之类的。 -**如果真的只是想清楚什么时候使用各种set与map,不用看那么多,把[关于哈希表,你该了解这些!](https://mp.weixin.qq.com/s/g8N6WmoQmsCUw3_BaWxHZA)看了就够了**。 +**如果真的只是想清楚什么时候使用各种set与map,不用看那么多,把[关于哈希表,你该了解这些!](https://programmercarl.com/哈希表理论基础.html)看了就够了**。 ## 总结 @@ -167,3 +167,4 @@ leetcode上的计时应该是以4ms为单位,有的多提交几次,多个4ms +
diff --git a/problems/周总结/20201112回溯周末总结.md b/problems/周总结/20201112回溯周末总结.md index 886b8923..3d019cf2 100644 --- a/problems/周总结/20201112回溯周末总结.md +++ b/problems/周总结/20201112回溯周末总结.md @@ -4,9 +4,9 @@ ## 周一 -在[回溯算法:求子集问题(二)](https://mp.weixin.qq.com/s/WJ4JNDRJgsW3eUN72Hh3uQ)中,开始针对子集问题进行去重。 +在[回溯算法:求子集问题(二)](https://programmercarl.com/0090.子集II.html)中,开始针对子集问题进行去重。 -本题就是[回溯算法:求子集问题!](https://mp.weixin.qq.com/s/NNRzX-vJ_pjK4qxohd_LtA)的基础上加上了去重,去重我们在[回溯算法:求组合总和(三)](https://mp.weixin.qq.com/s/_1zPYk70NvHsdY8UWVGXmQ)也讲过了。 +本题就是[回溯算法:求子集问题!](https://programmercarl.com/0078.子集.html)的基础上加上了去重,去重我们在[回溯算法:求组合总和(三)](https://programmercarl.com/0040.组合总和II.html)也讲过了。 所以本题对大家应该并不难。 @@ -16,18 +16,18 @@ ## 周二 -在[回溯算法:递增子序列](https://mp.weixin.qq.com/s/ePxOtX1ATRYJb2Jq7urzHQ)中,处处都能看到子集的身影,但处处是陷阱,值得好好琢磨琢磨! +在[回溯算法:递增子序列](https://programmercarl.com/0491.递增子序列.html)中,处处都能看到子集的身影,但处处是陷阱,值得好好琢磨琢磨! 树形结构如下: ![491. 递增子序列1](https://img-blog.csdnimg.cn/20201112170832333.png) -[回溯算法:递增子序列](https://mp.weixin.qq.com/s/ePxOtX1ATRYJb2Jq7urzHQ)留言区大家有很多疑问,主要还是和[回溯算法:求子集问题(二)](https://mp.weixin.qq.com/s/WJ4JNDRJgsW3eUN72Hh3uQ)混合在了一起。 +[回溯算法:递增子序列](https://programmercarl.com/0491.递增子序列.html)留言区大家有很多疑问,主要还是和[回溯算法:求子集问题(二)](https://programmercarl.com/0090.子集II.html)混合在了一起。 详细在[本周小结!(回溯算法系列三)续集](https://mp.weixin.qq.com/s/kSMGHc_YpsqL2j-jb_E_Ag)中给出了介绍! ## 周三 -我们已经分析了组合问题,分割问题,子集问题,那么[回溯算法:排列问题!](https://mp.weixin.qq.com/s/SCOjeMX1t41wcvJq49GhMw) 又不一样了。 +我们已经分析了组合问题,分割问题,子集问题,那么[回溯算法:排列问题!](https://programmercarl.com/0046.全排列.html) 又不一样了。 排列是有序的,也就是说[1,2] 和[2,1] 是两个集合,这和之前分析的子集以及组合所不同的地方。 @@ -43,7 +43,7 @@ ## 周四 -排列问题也要去重了,在[回溯算法:排列问题(二)](https://mp.weixin.qq.com/s/9L8h3WqRP_h8LLWNT34YlA)中又一次强调了“树层去重”和“树枝去重”。 +排列问题也要去重了,在[回溯算法:排列问题(二)](https://programmercarl.com/0047.全排列II.html)中又一次强调了“树层去重”和“树枝去重”。 树形结构如下: @@ -87,11 +87,12 @@ ## 总结 -本周我们对[子集问题进行了去重](https://mp.weixin.qq.com/s/WJ4JNDRJgsW3eUN72Hh3uQ),然后介绍了和子集问题非常像的[递增子序列](https://mp.weixin.qq.com/s/ePxOtX1ATRYJb2Jq7urzHQ),如果还保持惯性思维,这道题就可以掉坑里。 +本周我们对[子集问题进行了去重](https://programmercarl.com/0090.子集II.html),然后介绍了和子集问题非常像的[递增子序列](https://programmercarl.com/0491.递增子序列.html),如果还保持惯性思维,这道题就可以掉坑里。 -接着介绍了[排列问题!](https://mp.weixin.qq.com/s/SCOjeMX1t41wcvJq49GhMw),以及对[排列问题如何进行去重](https://mp.weixin.qq.com/s/9L8h3WqRP_h8LLWNT34YlA)。 +接着介绍了[排列问题!](https://programmercarl.com/0046.全排列.html),以及对[排列问题如何进行去重](https://programmercarl.com/0047.全排列II.html)。 最后我补充了子集问题,排列问题和组合问题的性能分析,给大家提供了回溯算法复杂度的分析思路。 +
diff --git a/problems/周总结/20201126贪心周末总结.md b/problems/周总结/20201126贪心周末总结.md index 215e8f01..5671cdbd 100644 --- a/problems/周总结/20201126贪心周末总结.md +++ b/problems/周总结/20201126贪心周末总结.md @@ -3,7 +3,7 @@ ## 周一 -本周正式开始了贪心算法,在[关于贪心算法,你该了解这些!](https://mp.weixin.qq.com/s/O935TaoHE9Eexwe_vSbRAg)中,我们介绍了什么是贪心以及贪心的套路。 +本周正式开始了贪心算法,在[关于贪心算法,你该了解这些!](https://programmercarl.com/贪心算法理论基础.html)中,我们介绍了什么是贪心以及贪心的套路。 **贪心的本质是选择每一阶段的局部最优,从而达到全局最优。** @@ -25,7 +25,7 @@ ## 周二 -在[贪心算法:分发饼干](https://mp.weixin.qq.com/s/YSuLIAYyRGlyxbp9BNC1uw)中讲解了贪心算法的第一道题目。 +在[贪心算法:分发饼干](https://programmercarl.com/0455.分发饼干.html)中讲解了贪心算法的第一道题目。 这道题目很明显能看出来是用贪心,也是入门好题。 @@ -39,7 +39,7 @@ 所有还是小饼干优先先喂饱小胃口更好一些,也比较直观。 -一些录友不清楚[贪心算法:分发饼干](https://mp.weixin.qq.com/s/YSuLIAYyRGlyxbp9BNC1uw)中时间复杂度是怎么来的? +一些录友不清楚[贪心算法:分发饼干](https://programmercarl.com/0455.分发饼干.html)中时间复杂度是怎么来的? 就是快排O(nlogn),遍历O(n),加一起就是还是O(nlogn)。 @@ -47,7 +47,7 @@ 接下来就要上一点难度了,要不然大家会误以为贪心算法就是常识判断一下就行了。 -在[贪心算法:摆动序列](https://mp.weixin.qq.com/s/Xytl05kX8LZZ1iWWqjMoHA)中,需要计算最长摇摆序列。 +在[贪心算法:摆动序列](https://programmercarl.com/0376.摆动序列.html)中,需要计算最长摇摆序列。 其实就是让序列有尽可能多的局部峰值。 @@ -62,13 +62,13 @@ ## 周四 -在[贪心算法:最大子序和](https://mp.weixin.qq.com/s/DrjIQy6ouKbpletQr0g1Fg)中,详细讲解了用贪心的方式来求最大子序列和,其实这道题目是一道动态规划的题目。 +在[贪心算法:最大子序和](https://programmercarl.com/0053.最大子序和.html)中,详细讲解了用贪心的方式来求最大子序列和,其实这道题目是一道动态规划的题目。 **贪心的思路为局部最优:当前“连续和”为负数的时候立刻放弃,从下一个元素重新计算“连续和”,因为负数加上下一个元素 “连续和”只会越来越小。从而推出全局最优:选取最大“连续和”** 代码很简单,但是思路却比较难。还需要反复琢磨。 -针对[贪心算法:最大子序和](https://mp.weixin.qq.com/s/DrjIQy6ouKbpletQr0g1Fg)文章中给出的贪心代码如下; +针对[贪心算法:最大子序和](https://programmercarl.com/0053.最大子序和.html)文章中给出的贪心代码如下; ``` class Solution { public: @@ -95,20 +95,21 @@ public: ## 总结 -本周我们讲解了[贪心算法的理论基础](https://mp.weixin.qq.com/s/O935TaoHE9Eexwe_vSbRAg),了解了贪心本质:局部最优推出全局最优。 +本周我们讲解了[贪心算法的理论基础](https://programmercarl.com/贪心算法理论基础.html),了解了贪心本质:局部最优推出全局最优。 -然后讲解了第一道题目[分发饼干](https://mp.weixin.qq.com/s/YSuLIAYyRGlyxbp9BNC1uw),还是比较基础的,可能会给大家一种贪心算法比较简单的错觉,因为贪心有时候接近于常识。 +然后讲解了第一道题目[分发饼干](https://programmercarl.com/0455.分发饼干.html),还是比较基础的,可能会给大家一种贪心算法比较简单的错觉,因为贪心有时候接近于常识。 其实我还准备一些简单的贪心题目,甚至网上很多都质疑这些题目是不是贪心算法。这些题目我没有立刻发出来,因为真的会让大家感觉贪心过于简单,而忽略了贪心的本质:局部最优和全局最优两个关键点。 **所以我在贪心系列难度会有所交替,难的题目在于拓展思路,简单的题目在于分析清楚其贪心的本质,后续我还会发一些简单的题目来做贪心的分析。** -在[摆动序列](https://mp.weixin.qq.com/s/Xytl05kX8LZZ1iWWqjMoHA)中大家就初步感受到贪心没那么简单了。 +在[摆动序列](https://programmercarl.com/0376.摆动序列.html)中大家就初步感受到贪心没那么简单了。 -本周最后是[最大子序和](https://mp.weixin.qq.com/s/DrjIQy6ouKbpletQr0g1Fg),这道题目要用贪心的方式做出来,就比较有难度,都知道负数加上正数之后会变小,但是这道题目依然会让很多人搞混淆,其关键在于:**不能让“连续和”为负数的时候加上下一个元素,而不是 不让“连续和”加上一个负数**。这块真的需要仔细体会! +本周最后是[最大子序和](https://programmercarl.com/0053.最大子序和.html),这道题目要用贪心的方式做出来,就比较有难度,都知道负数加上正数之后会变小,但是这道题目依然会让很多人搞混淆,其关键在于:**不能让“连续和”为负数的时候加上下一个元素,而不是 不让“连续和”加上一个负数**。这块真的需要仔细体会! +
diff --git a/problems/周总结/20201203贪心周末总结.md b/problems/周总结/20201203贪心周末总结.md index 43e877dd..23b0b7cf 100644 --- a/problems/周总结/20201203贪心周末总结.md +++ b/problems/周总结/20201203贪心周末总结.md @@ -6,7 +6,7 @@ 一说到股票问题,一般都会想到动态规划,其实有时候贪心更有效! -在[贪心算法:买卖股票的最佳时机II](https://mp.weixin.qq.com/s/VsTFA6U96l18Wntjcg3fcg)中,讲到只能多次买卖一支股票,如何获取最大利润。 +在[贪心算法:买卖股票的最佳时机II](https://programmercarl.com/0122.买卖股票的最佳时机II.html)中,讲到只能多次买卖一支股票,如何获取最大利润。 **这道题目理解利润拆分是关键点!** 不要整块的去看,而是把整体利润拆为每天的利润,就很容易想到贪心了。 @@ -20,7 +20,7 @@ ## 周二 -在[贪心算法:跳跃游戏](https://mp.weixin.qq.com/s/606_N9j8ACKCODoCbV1lSA)中是给你一个数组看能否跳到终点。 +在[贪心算法:跳跃游戏](https://programmercarl.com/0055.跳跃游戏.html)中是给你一个数组看能否跳到终点。 本题贪心的关键是:**不用拘泥于每次究竟跳几步,而是看覆盖范围,覆盖范围内一定是可以跳过来的,不用管是怎么跳的**。 @@ -37,7 +37,7 @@ ## 周三 -这道题目:[贪心算法:跳跃游戏II](https://mp.weixin.qq.com/s/kJBcsJ46DKCSjT19pxrNYg)可就有点难了。 +这道题目:[贪心算法:跳跃游戏II](https://programmercarl.com/0045.跳跃游戏II.html)可就有点难了。 本题解题关键在于:**以最小的步数增加最大的覆盖范围,直到覆盖范围覆盖了终点**。 @@ -49,7 +49,7 @@ 注意:**图中的移动下标是到当前这步覆盖的最远距离(下标2的位置),此时没有到终点,只能增加第二步来扩大覆盖范围**。 -在[贪心算法:跳跃游戏II](https://mp.weixin.qq.com/s/kJBcsJ46DKCSjT19pxrNYg)中我给出了两个版本的代码。 +在[贪心算法:跳跃游戏II](https://programmercarl.com/0045.跳跃游戏II.html)中我给出了两个版本的代码。 其实本质都是超过当前覆盖范围,步数就加一,但版本一需要考虑当前覆盖最远距离下标是不是数组终点的情况。 @@ -67,7 +67,7 @@ ## 周四 -这道题目:[贪心算法:K次取反后最大化的数组和](https://mp.weixin.qq.com/s/dMTzBBVllRm_Z0aaWvYazA)就比较简单了,哈哈,用简单题来讲一讲贪心的思想。 +这道题目:[贪心算法:K次取反后最大化的数组和](https://programmercarl.com/1005.K次取反后最大化的数组和.html)就比较简单了,哈哈,用简单题来讲一讲贪心的思想。 **这里其实用了两次贪心!** @@ -77,8 +77,7 @@ 第二次贪心:局部最优:只找数值最小的正整数进行反转,当前数值可以达到最大(例如正整数数组{5, 3, 1},反转1 得到-1 比 反转5得到的-5 大多了),全局最优:整个 数组和 达到最大。 - -[贪心算法:K次取反后最大化的数组和](https://mp.weixin.qq.com/s/dMTzBBVllRm_Z0aaWvYazA)中的代码,最后while处理K的时候,其实直接判断奇偶数就可以了,文中给出的方式太粗暴了,哈哈,Carl大意了。 +[贪心算法:K次取反后最大化的数组和](https://programmercarl.com/1005.K次取反后最大化的数组和.html)中的代码,最后while处理K的时候,其实直接判断奇偶数就可以了,文中给出的方式太粗暴了,哈哈,Carl大意了。 例外一位录友留言给出一个很好的建议,因为文中是使用快排,仔细看题,**题目中限定了数据范围是正负一百,所以可以使用桶排序**,这样时间复杂度就可以优化为O(n)了。但可能代码要复杂一些了。 @@ -96,3 +95,4 @@ +
diff --git a/problems/周总结/20201210复杂度分析周末总结.md b/problems/周总结/20201210复杂度分析周末总结.md index 1833c1ad..02bfbd83 100644 --- a/problems/周总结/20201210复杂度分析周末总结.md +++ b/problems/周总结/20201210复杂度分析周末总结.md @@ -15,7 +15,7 @@ # 周一 -在[程序员的简历应该这么写!!(附简历模板)](https://mp.weixin.qq.com/s/nCTUzuRTBo1_R_xagVszsA)中以我自己的总结经验为例讲一讲大家应该如何写简历。 +在[程序员的简历应该这么写!!(附简历模板)](https://programmercarl.com/前序/程序员简历.html)中以我自己的总结经验为例讲一讲大家应该如何写简历。 主要有如下几点: @@ -49,7 +49,7 @@ # 周二 -在[关于时间复杂度,你不知道的都在这里!](https://mp.weixin.qq.com/s/LWBfehW1gMuEnXtQjJo-sw)中详细讲解了时间复杂度,很多被大家忽略的内容,在文中都做了详细的解释。 +在[关于时间复杂度,你不知道的都在这里!](https://programmercarl.com/前序/关于时间复杂度,你不知道的都在这里!.html)中详细讲解了时间复杂度,很多被大家忽略的内容,在文中都做了详细的解释。 文中涉及如下问题: @@ -61,7 +61,7 @@ 这些问题大家可能懵懵懂懂的了解一些,但一细问又答不上来。 -相信看完本篇[关于时间复杂度,你不知道的都在这里!](https://mp.weixin.qq.com/s/LWBfehW1gMuEnXtQjJo-sw),以上问题大家就理解的清晰多了。 +相信看完本篇[关于时间复杂度,你不知道的都在这里!](https://programmercarl.com/前序/关于时间复杂度,你不知道的都在这里!.html),以上问题大家就理解的清晰多了。 文中最后还运用以上知识通过一道简单的题目具体分析了一下其时间复杂度,给出两种方法究竟谁最优。 @@ -70,7 +70,7 @@ # 周三 -在[O(n)的算法居然超时了,此时的n究竟是多大?](https://mp.weixin.qq.com/s/73ryNsuPFvBQkt6BbhNzLA)中介绍了大家在leetcode上提交代码经常遇到的一个问题-超时! +在[O(n)的算法居然超时了,此时的n究竟是多大?](https://programmercarl.com/前序/On的算法居然超时了,此时的n究竟是多大?.html)中介绍了大家在leetcode上提交代码经常遇到的一个问题-超时! 估计很多录友知道算法超时了,但没有注意过 O(n)的算法,如果1s内出结果,这个n究竟是多大? @@ -85,7 +85,7 @@ # 周四 -在[通过一道面试题目,讲一讲递归算法的时间复杂度!](https://mp.weixin.qq.com/s/I6ZXFbw09NR31F5CJR_geQ)中,讲一讲如果计算递归算法的时间复杂度。 +在[通过一道面试题目,讲一讲递归算法的时间复杂度!](https://programmercarl.com/前序/通过一道面试题目,讲一讲递归算法的时间复杂度!.html)中,讲一讲如果计算递归算法的时间复杂度。 递归的时间复杂度等于**递归的次数 * 每次递归中的操作次数**。 @@ -119,3 +119,4 @@ 就酱,「代码随想录」是技术公众号里的一抹清流,值得推荐给身边的朋友同学们! +
diff --git a/problems/周总结/20201217贪心周末总结.md b/problems/周总结/20201217贪心周末总结.md index 4a634da5..4d12f92a 100644 --- a/problems/周总结/20201217贪心周末总结.md +++ b/problems/周总结/20201217贪心周末总结.md @@ -6,7 +6,7 @@ ## 周一 -在[贪心算法:加油站](https://mp.weixin.qq.com/s/aDbiNuEZIhy6YKgQXvKELw)中给出每一个加油站的汽油和开到这个加油站的消耗,问汽车能不能开一圈。 +在[贪心算法:加油站](https://programmercarl.com/0134.加油站.html)中给出每一个加油站的汽油和开到这个加油站的消耗,问汽车能不能开一圈。 这道题目咋眼一看,感觉是一道模拟题,模拟一下汽车从每一个节点出发看看能不能开一圈,时间复杂度是O(n^2)。 @@ -28,7 +28,7 @@ ## 周二 -在[贪心算法:分发糖果](https://mp.weixin.qq.com/s/8MwlgFfvaNYmjGwjuMlETQ)中我们第一次接触了需要考虑两个维度的情况。 +在[贪心算法:分发糖果](https://programmercarl.com/0135.分发糖果.html)中我们第一次接触了需要考虑两个维度的情况。 例如这道题,是先考虑左边呢,还是考虑右边呢? @@ -54,7 +54,7 @@ ## 周三 -在[贪心算法:柠檬水找零](https://mp.weixin.qq.com/s/0kT4P-hzY7H6Ae0kjQqnZg)中我们模拟了买柠檬水找零的过程。 +在[贪心算法:柠檬水找零](https://programmercarl.com/0860.柠檬水找零.html)中我们模拟了买柠檬水找零的过程。 这道题目刚一看,可能会有点懵,这要怎么找零才能保证完整全部账单的找零呢? @@ -72,11 +72,11 @@ ## 周四 -在[贪心算法:根据身高重建队列](https://mp.weixin.qq.com/s/-2TgZVdOwS-DvtbjjDEbfw)中,我们再一次遇到了需要考虑两个维度的情况。 +在[贪心算法:根据身高重建队列](https://programmercarl.com/0406.根据身高重建队列.html)中,我们再一次遇到了需要考虑两个维度的情况。 -之前我们已经做过一道类似的了就是[贪心算法:分发糖果](https://mp.weixin.qq.com/s/8MwlgFfvaNYmjGwjuMlETQ),但本题比分发糖果难不少! +之前我们已经做过一道类似的了就是[贪心算法:分发糖果](https://programmercarl.com/0135.分发糖果.html),但本题比分发糖果难不少! -[贪心算法:根据身高重建队列](https://mp.weixin.qq.com/s/-2TgZVdOwS-DvtbjjDEbfw)中依然是要确定一边,然后在考虑另一边,两边一起考虑一定会蒙圈。 +[贪心算法:根据身高重建队列](https://programmercarl.com/0406.根据身高重建队列.html)中依然是要确定一边,然后在考虑另一边,两边一起考虑一定会蒙圈。 那么本题先确定k还是先确定h呢,也就是究竟先按h排序呢,还先按照k排序呢? @@ -92,8 +92,9 @@ 「代码随想录」里已经讲了十一道贪心题目了,大家可以发现在每一道题目的讲解中,我都是把什么是局部最优,和什么是全局最优说清楚。 -虽然有时候感觉贪心就是常识,但如果真正是常识性的题目,其实是模拟题,就不是贪心算法了!例如[贪心算法:加油站](https://mp.weixin.qq.com/s/aDbiNuEZIhy6YKgQXvKELw)中的贪心方法一,其实我就认为不是贪心算法,而是直接从全局最优的角度上来模拟,因为方法里没有体现局部最优的过程。 +虽然有时候感觉贪心就是常识,但如果真正是常识性的题目,其实是模拟题,就不是贪心算法了!例如[贪心算法:加油站](https://programmercarl.com/0134.加油站.html)中的贪心方法一,其实我就认为不是贪心算法,而是直接从全局最优的角度上来模拟,因为方法里没有体现局部最优的过程。 而且大家也会发现,贪心并没有想象中的那么简单,贪心往往妙的出其不意,触不及防!哈哈 +
diff --git a/problems/周总结/20201224贪心周末总结.md b/problems/周总结/20201224贪心周末总结.md index cdc62168..097ae9ed 100644 --- a/problems/周总结/20201224贪心周末总结.md +++ b/problems/周总结/20201224贪心周末总结.md @@ -4,7 +4,7 @@ ## 周一 -在[贪心算法:用最少数量的箭引爆气球](https://mp.weixin.qq.com/s/HxVAJ6INMfNKiGwI88-RFw)中,我们开始讲解了重叠区间问题,用最少的弓箭射爆所有气球,其本质就是找到最大的重叠区间。 +在[贪心算法:用最少数量的箭引爆气球](https://programmercarl.com/0452.用最少数量的箭引爆气球.html)中,我们开始讲解了重叠区间问题,用最少的弓箭射爆所有气球,其本质就是找到最大的重叠区间。 按照左边界经行排序后,如果气球重叠了,重叠气球中右边边界的最小值 之前的区间一定需要一个弓箭 @@ -16,7 +16,7 @@ ## 周二 -在[贪心算法:无重叠区间](https://mp.weixin.qq.com/s/oFOEoW-13Bm4mik-aqAOmw)中要去掉最少的区间,来让所有区间没有重叠。 +在[贪心算法:无重叠区间](https://programmercarl.com/0435.无重叠区间.html)中要去掉最少的区间,来让所有区间没有重叠。 我来按照右边界排序,从左向右记录非交叉区间的个数。最后用区间总数减去非交叉区间的个数就是需要移除的区间个数了。 @@ -24,14 +24,14 @@ ![435.无重叠区间](https://img-blog.csdnimg.cn/20201221201553618.png) -细心的同学就发现了,此题和 [贪心算法:用最少数量的箭引爆气球](https://mp.weixin.qq.com/s/HxVAJ6INMfNKiGwI88-RFw)非常像。 +细心的同学就发现了,此题和 [贪心算法:用最少数量的箭引爆气球](https://programmercarl.com/0452.用最少数量的箭引爆气球.html)非常像。 弓箭的数量就相当于是非交叉区间的数量,只要把弓箭那道题目代码里射爆气球的判断条件加个等号(认为[0,1][1,2]不是相邻区间),然后用总区间数减去弓箭数量 就是要移除的区间数量了。 -把[贪心算法:用最少数量的箭引爆气球](https://mp.weixin.qq.com/s/HxVAJ6INMfNKiGwI88-RFw)代码稍做修改,别可以AC本题。 +把[贪心算法:用最少数量的箭引爆气球](https://programmercarl.com/0452.用最少数量的箭引爆气球.html)代码稍做修改,别可以AC本题。 修改后的C++代码如下: -```C++ +```CPP class Solution { public: // 按照区间左边界从大到小排序 @@ -58,7 +58,7 @@ public: ## 周三 -[贪心算法:划分字母区间](https://mp.weixin.qq.com/s/pdX4JwV1AOpc_m90EcO2Hw)中我们要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中。 +[贪心算法:划分字母区间](https://programmercarl.com/0763.划分字母区间.html)中我们要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中。 这道题目leetcode上标的是贪心,其实我不认识是贪心,因为没感受到局部最优和全局最优的关系。 @@ -76,7 +76,7 @@ public: ## 周四 -[贪心算法:合并区间](https://mp.weixin.qq.com/s/royhzEM5tOkUFwUGrNStpw)中要合并所有重叠的区间。 +[贪心算法:合并区间](https://programmercarl.com/0056.合并区间.html)中要合并所有重叠的区间。 相信如果录友们前几天区间问题的题目认真练习了,今天题目就应该算简单一些了。 @@ -95,10 +95,11 @@ public: 其实很多区间的合并操作看起来都是常识,其实贪心算法有时候就是常识,哈哈,但也别小看了贪心算法。 -在[贪心算法:合并区间](https://mp.weixin.qq.com/s/royhzEM5tOkUFwUGrNStpw)中就说过,对于贪心算法,很多同学都是:「如果能凭常识直接做出来,就会感觉不到自己用了贪心, 一旦第一直觉想不出来, 可能就一直想不出来了」。 +在[贪心算法:合并区间](https://programmercarl.com/0056.合并区间.html)中就说过,对于贪心算法,很多同学都是:「如果能凭常识直接做出来,就会感觉不到自己用了贪心, 一旦第一直觉想不出来, 可能就一直想不出来了」。 所以还是要多看多做多练习! **「代码随想录」里总结的都是经典题目,大家跟着练就节省了不少选择题目的时间了**。 +
diff --git a/problems/周总结/20210107动规周末总结.md b/problems/周总结/20210107动规周末总结.md index 24700941..b4baa4ad 100644 --- a/problems/周总结/20210107动规周末总结.md +++ b/problems/周总结/20210107动规周末总结.md @@ -3,7 +3,7 @@ ## 周一 -在[关于动态规划,你该了解这些!](https://mp.weixin.qq.com/s/ocZwfPlCWrJtVGACqFNAag)中我们讲解了动态规划的基础知识。 +在[关于动态规划,你该了解这些!](https://programmercarl.com/动态规划理论基础.html)中我们讲解了动态规划的基础知识。 首先讲一下动规和贪心的区别,其实大家不用太强调理论上的区别,做做题,就感受出来了。 @@ -33,13 +33,13 @@ ## 周二 -这道题目[动态规划:斐波那契数](https://mp.weixin.qq.com/s/ko0zLJplF7n_4TysnPOa_w)是当之无愧的动规入门题。 +这道题目[动态规划:斐波那契数](https://programmercarl.com/0509.斐波那契数.html)是当之无愧的动规入门题。 简单题,我们就是用来了解方法论的,用动规五部曲走一遍,题目其实已经把递推公式,和dp数组如何初始化都给我们了。 ## 周三 -[动态规划:爬楼梯](https://mp.weixin.qq.com/s/Ohop0jApSII9xxOMiFhGIw) 这道题目其实就是斐波那契数列。 +[动态规划:爬楼梯](https://programmercarl.com/0070.爬楼梯.html) 这道题目其实就是斐波那契数列。 但正常思考过程应该是推导完递推公式之后,发现这是斐波那契,而不是上来就知道这是斐波那契。 @@ -51,7 +51,7 @@ dp[0]其实就是一个无意义的存在,不用去初始化dp[0]。 一个严谨的思考过程,应该是初始化dp[1] = 1,dp[2] = 2,然后i从3开始遍历,代码如下: -```C++ +```CPP dp[1] = 1; dp[2] = 2; for (int i = 3; i <= n; i++) { // 注意i是从3开始的 @@ -67,7 +67,7 @@ for (int i = 3; i <= n; i++) { // 注意i是从3开始的 这里我先给出我的实现代码: -```C++ +```CPP class Solution { public: int climbStairs(int n) { @@ -98,11 +98,11 @@ public: 这道绝佳的面试题我没有用过,如果录友们有面试别人的需求,就把这个套路拿去吧,哈哈哈。 -我在[通过一道面试题目,讲一讲递归算法的时间复杂度!](https://mp.weixin.qq.com/s/I6ZXFbw09NR31F5CJR_geQ)中,以我自己面试别人的真实经历,通过求x的n次方 这么简单的题目,就可以考察候选人对算法性能以及递归的理解深度,录友们可以看看,绝对有收获! +我在[通过一道面试题目,讲一讲递归算法的时间复杂度!](https://programmercarl.com/前序/通过一道面试题目,讲一讲递归算法的时间复杂度!.html)中,以我自己面试别人的真实经历,通过求x的n次方 这么简单的题目,就可以考察候选人对算法性能以及递归的理解深度,录友们可以看看,绝对有收获! ## 周四 -这道题目[动态规划:使用最小花费爬楼梯](https://mp.weixin.qq.com/s/djZB9gkyLFAKcQcSvKDorA)就是在爬台阶的基础上加了一个花费, +这道题目[动态规划:使用最小花费爬楼梯](https://programmercarl.com/0746.使用最小花费爬楼梯.html)就是在爬台阶的基础上加了一个花费, 这道题描述也确实有点魔幻。 @@ -122,7 +122,7 @@ public: 所以代码也可以这么写: -```C++ +```CPP class Solution { public: int minCostClimbingStairs(vector& cost) { @@ -149,3 +149,4 @@ public: +
diff --git a/problems/周总结/20210114动规周末总结.md b/problems/周总结/20210114动规周末总结.md index acce0fb2..039f3596 100644 --- a/problems/周总结/20210114动规周末总结.md +++ b/problems/周总结/20210114动规周末总结.md @@ -1,7 +1,7 @@ ## 周一 -[动态规划:不同路径](https://mp.weixin.qq.com/s/MGgGIt4QCpFMROE9X9he_A)中求从出发点到终点有几种路径,只能向下或者向右移动一步。 +[动态规划:不同路径](https://programmercarl.com/0062.不同路径.html)中求从出发点到终点有几种路径,只能向下或者向右移动一步。 我们提供了三种方法,但重点讲解的还是动规,也是需要重点掌握的。 @@ -35,7 +35,7 @@ for (int i = 1; i < m; i++) { ## 周二 -[动态规划:不同路径还不够,要有障碍!](https://mp.weixin.qq.com/s/lhqF0O4le9-wvalptOVOww)相对于[动态规划:不同路径](https://mp.weixin.qq.com/s/MGgGIt4QCpFMROE9X9he_A)添加了障碍。 +[动态规划:不同路径还不够,要有障碍!](https://programmercarl.com/0063.不同路径II.html)相对于[动态规划:不同路径](https://programmercarl.com/0062.不同路径.html)添加了障碍。 dp[i][j]定义依然是:表示从(0 ,0)出发,到(i, j) 有dp[i][j]条不同的路径。 @@ -78,7 +78,7 @@ for (int i = 1; i < m; i++) { ## 周三 -[动态规划:整数拆分,你要怎么拆?](https://mp.weixin.qq.com/s/cVbyHrsWH_Rfzlj-ESr01A)给出一个整数,问有多少种拆分的方法。 +[动态规划:整数拆分,你要怎么拆?](https://programmercarl.com/0343.整数拆分.html)给出一个整数,问有多少种拆分的方法。 这道题目就有点难度了,题目中dp我也给出了两种方法,但通过两种方法的比较可以看出,对dp数组定义的理解,以及dp数组初始化的重要性。 @@ -121,7 +121,7 @@ for (int i = 3; i <= n ; i++) { **或者也可以理解j是拆分i的第一个整数**。 -[动态规划:整数拆分,你要怎么拆?](https://mp.weixin.qq.com/s/cVbyHrsWH_Rfzlj-ESr01A)总结里,我也给出了递推公式dp[i] = max(dp[i], dp[i - j] * dp[j])这种写法。 +[动态规划:整数拆分,你要怎么拆?](https://programmercarl.com/0343.整数拆分.html)总结里,我也给出了递推公式dp[i] = max(dp[i], dp[i - j] * dp[j])这种写法。 对于这种写法,一位录友总结的很好,意思就是:如果递推公式是dp[i-j] * dp[j],这样就相当于强制把一个数至少拆分成四份。 @@ -129,7 +129,7 @@ dp[i-j]至少是两个数的乘积,dp[j]又至少是两个数的乘积,但 ## 周四 -[动态规划:不同的二叉搜索树](https://mp.weixin.qq.com/s/8VE8pDrGxTf8NEVYBDwONw)给出n个不同的节点求能组成多少个不同二叉搜索树。 +[动态规划:不同的二叉搜索树](https://programmercarl.com/0096.不同的二叉搜索树.html)给出n个不同的节点求能组成多少个不同二叉搜索树。 这道题目还是比较难的,想到用动态规划的方法就很不容易了! @@ -145,7 +145,7 @@ n为5时候的dp数组状态如图: ## 总结 -本周题目已经开始点难度了,特别是[动态规划:不同的二叉搜索树](https://mp.weixin.qq.com/s/8VE8pDrGxTf8NEVYBDwONw)这道题目,明显感觉阅读量很低,可能是因为确实有点难吧。 +本周题目已经开始点难度了,特别是[动态规划:不同的二叉搜索树](https://programmercarl.com/0096.不同的二叉搜索树.html)这道题目,明显感觉阅读量很低,可能是因为确实有点难吧。 我现在也陷入了纠结,题目一简单,就会有录友和我反馈说题目太简单了,题目一难,阅读量就特别低。 @@ -157,3 +157,4 @@ n为5时候的dp数组状态如图: 预告,我们下周正式开始讲解背包问题,经典的不能再经典,也是比较难的一类动态规划的题目了,录友们上车抓稳咯。 +
diff --git a/problems/周总结/20210121动规周末总结.md b/problems/周总结/20210121动规周末总结.md index dc0e7a46..e9427142 100644 --- a/problems/周总结/20210121动规周末总结.md +++ b/problems/周总结/20210121动规周末总结.md @@ -7,7 +7,7 @@ ## 周一 -[动态规划:关于01背包问题,你该了解这些!](https://mp.weixin.qq.com/s/FwIiPPmR18_AJO5eiidT6w)中,我们开始介绍了背包问题。 +[动态规划:关于01背包问题,你该了解这些!](https://programmercarl.com/背包理论基础01背包-1.html)中,我们开始介绍了背包问题。 首先对于背包的所有问题中,01背包是最最基础的,其他背包也是在01背包的基础上稍作变化。 @@ -29,7 +29,7 @@ dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]); 3. dp数组如何初始化 -```C++ +```CPP // 初始化 dp vector> dp(weight.size() + 1, vector(bagWeight + 1, 0)); for (int j = bagWeight; j >= weight[0]; j--) { @@ -43,7 +43,7 @@ for (int j = bagWeight; j >= weight[0]; j--) { 但是先遍历物品更好理解。代码如下: -```C++ +```CPP // weight数组的大小 就是物品个数 for(int i = 1; i < weight.size(); i++) { // 遍历物品 for(int j = 0; j <= bagWeight; j++) { // 遍历背包容量 @@ -75,7 +75,7 @@ for(int i = 1; i < weight.size(); i++) { // 遍历物品 ## 周二 -[动态规划:关于01背包问题,你该了解这些!(滚动数组)](https://mp.weixin.qq.com/s/M4uHxNVKRKm5HPjkNZBnFA)中把01背包的一维dp数组(滚动数组)实现详细讲解了一遍。 +[动态规划:关于01背包问题,你该了解这些!(滚动数组)](https://programmercarl.com/背包理论基础01背包-2.html)中把01背包的一维dp数组(滚动数组)实现详细讲解了一遍。 分析一下和二维dp数组有什么区别,在初始化和遍历顺序上又有什么差异? @@ -107,7 +107,7 @@ dp[j] = max(dp[j], dp[j - weight[i]] + value[i]); 代码如下: -```C++ +```CPP for(int i = 0; i < weight.size(); i++) { // 遍历物品 for(int j = bagWeight; j >= weight[i]; j--) { // 遍历背包容量 dp[j] = max(dp[j], dp[j - weight[i]] + value[i]); @@ -125,7 +125,7 @@ for(int i = 0; i < weight.size(); i++) { // 遍历物品 ## 周三 -[动态规划:416. 分割等和子集](https://mp.weixin.qq.com/s/sYw3QtPPQ5HMZCJcT4EaLQ)中我们开始用01背包来解决问题。 +[动态规划:416. 分割等和子集](https://programmercarl.com/0416.分割等和子集.html)中我们开始用01背包来解决问题。 只有确定了如下四点,才能把01背包问题套到本题上来。 @@ -138,11 +138,11 @@ for(int i = 0; i < weight.size(); i++) { // 遍历物品 ## 周四 -[动态规划:1049. 最后一块石头的重量 II](https://mp.weixin.qq.com/s/WbwAo3jaUaNJjvhHgq0BGg)这道题目其实和[动态规划:416. 分割等和子集](https://mp.weixin.qq.com/s/sYw3QtPPQ5HMZCJcT4EaLQ)是非常像的。 +[动态规划:1049. 最后一块石头的重量 II](https://programmercarl.com/1049.最后一块石头的重量II.html)这道题目其实和[动态规划:416. 分割等和子集](https://programmercarl.com/0416.分割等和子集.html)是非常像的。 本题其实就是尽量让石头分成重量相同的两堆,相撞之后剩下的石头最小,这样就化解成01背包问题了。 -[动态规划:416. 分割等和子集](https://mp.weixin.qq.com/s/sYw3QtPPQ5HMZCJcT4EaLQ)相当于是求背包是否正好装满,而本题是求背包最多能装多少。 +[动态规划:416. 分割等和子集](https://programmercarl.com/0416.分割等和子集.html)相当于是求背包是否正好装满,而本题是求背包最多能装多少。 这两道题目是对dp[target]的处理方式不同。这也考验的对dp[i]定义的理解。 @@ -158,3 +158,4 @@ for(int i = 0; i < weight.size(); i++) { // 遍历物品 就像是我们讲解01背包的时候,花了那么大力气才把每一个细节都讲清楚,这里其实是基础,后面的背包问题怎么变,基础比较牢固自然会有自己的一套思考过程。 +
diff --git a/problems/周总结/20210128动规周末总结.md b/problems/周总结/20210128动规周末总结.md index bd597e41..06193a70 100644 --- a/problems/周总结/20210128动规周末总结.md +++ b/problems/周总结/20210128动规周末总结.md @@ -2,7 +2,7 @@ ## 周一 -[动态规划:目标和!](https://mp.weixin.qq.com/s/2pWmaohX75gwxvBENS-NCw)要求在数列之间加入+ 或者 -,使其和为S。 +[动态规划:目标和!](https://programmercarl.com/0494.目标和.html)要求在数列之间加入+ 或者 -,使其和为S。 所有数的总和为sum,假设加法的总和为x,那么可以推出x = (S + sum) / 2。 @@ -39,7 +39,7 @@ dp数组状态变化如下: ## 周二 -这道题目[动态规划:一和零!](https://mp.weixin.qq.com/s/x-u3Dsp76DlYqtCe0xEKJw)算有点难度。 +这道题目[动态规划:一和零!](https://programmercarl.com/0474.一和零.html)算有点难度。 **不少同学都以为是多重背包,其实这是一道标准的01背包**。 @@ -78,7 +78,7 @@ dp[i][j] = max(dp[i][j], dp[i - zeroNum][j - oneNum] + 1); 此时01背包我们就讲完了,正式开始完全背包。 -在[动态规划:关于完全背包,你该了解这些!](https://mp.weixin.qq.com/s/akwyxlJ4TLvKcw26KB9uJw)中我们讲解了完全背包的理论基础。 +在[动态规划:关于完全背包,你该了解这些!](https://programmercarl.com/背包问题理论基础完全背包.html)中我们讲解了完全背包的理论基础。 其实完全背包和01背包区别就是完全背包的物品是无限数量。 @@ -86,7 +86,7 @@ dp[i][j] = max(dp[i][j], dp[i - zeroNum][j - oneNum] + 1); 完全背包的物品是可以添加多次的,所以遍历背包容量要从小到大去遍历,即: -```C++ +```CPP // 先遍历物品,再遍历背包 for(int i = 0; i < weight.size(); i++) { // 遍历物品 for(int j = weight[i]; j < bagWeight ; j++) { // 遍历背包容量 @@ -100,7 +100,7 @@ for(int i = 0; i < weight.size(); i++) { // 遍历物品 **那么为什么要先遍历物品,在遍历背包呢?** (灵魂拷问) -其实对于纯完全背包,先遍历物品,再遍历背包 与 先遍历背包,再遍历物品都是可以的。我在文中[动态规划:关于完全背包,你该了解这些!](https://mp.weixin.qq.com/s/akwyxlJ4TLvKcw26KB9uJw)也给出了详细的解释。 +其实对于纯完全背包,先遍历物品,再遍历背包 与 先遍历背包,再遍历物品都是可以的。我在文中[动态规划:关于完全背包,你该了解这些!](https://programmercarl.com/背包问题理论基础完全背包.html)也给出了详细的解释。 这个细节是很多同学忽略掉的点,其实也不算细节了,**相信不少同学在写背包的时候,两层for循环的先后循序搞不清楚,靠感觉来的**。 @@ -110,7 +110,7 @@ for(int i = 0; i < weight.size(); i++) { // 遍历物品 ## 周四 -在[动态规划:给你一些零钱,你要怎么凑?](https://mp.weixin.qq.com/s/PlowDsI4WMBOzf3q80AksQ)中就是给你一堆零钱(零钱个数无限),为凑成amount的组合数有几种。 +在[动态规划:给你一些零钱,你要怎么凑?](https://programmercarl.com/0518.零钱兑换II.html)中就是给你一堆零钱(零钱个数无限),为凑成amount的组合数有几种。 **注意这里组合数和排列数的区别!** @@ -134,8 +134,9 @@ for(int i = 0; i < weight.size(); i++) { // 遍历物品 其实这是一种错觉,或者说对动规理解的不够深入! -我在动规专题开篇介绍[关于动态规划,你该了解这些!](https://mp.weixin.qq.com/s/ocZwfPlCWrJtVGACqFNAag)中就强调了 **递推公式仅仅是 动规五部曲里的一小部分, dp数组的定义、初始化、遍历顺序,哪一点没有搞透的话,即使知道递推公式,遇到稍稍难一点的动规题目立刻会感觉写不出来了**。 +我在动规专题开篇介绍[关于动态规划,你该了解这些!](https://programmercarl.com/动态规划理论基础.html)中就强调了 **递推公式仅仅是 动规五部曲里的一小部分, dp数组的定义、初始化、遍历顺序,哪一点没有搞透的话,即使知道递推公式,遇到稍稍难一点的动规题目立刻会感觉写不出来了**。 此时相信大家对动规五部曲也有更深的理解了,同样也验证了Carl之前讲过的:**简单题是用来学习方法论的,而遇到难题才体现出方法论的重要性!** +
diff --git a/problems/周总结/20210204动规周末总结.md b/problems/周总结/20210204动规周末总结.md index db14f7f3..31009298 100644 --- a/problems/周总结/20210204动规周末总结.md +++ b/problems/周总结/20210204动规周末总结.md @@ -2,7 +2,7 @@ ## 周一 -[动态规划:377. 组合总和 Ⅳ](https://mp.weixin.qq.com/s/Iixw0nahJWQgbqVNk8k6gA)中给定一个由正整数组成且不存在重复数字的数组,找出和为给定目标正整数的组合的个数(顺序不同的序列被视作不同的组合)。 +[动态规划:377. 组合总和 Ⅳ](https://programmercarl.com/0377.组合总和Ⅳ.html)中给定一个由正整数组成且不存在重复数字的数组,找出和为给定目标正整数的组合的个数(顺序不同的序列被视作不同的组合)。 题目面试虽然是组合,但又强调顺序不同的序列被视作不同的组合,其实这道题目求的是排列数! @@ -10,7 +10,7 @@ 这个和前上周讲的组合问题又不一样,关键就体现在遍历顺序上! -在[动态规划:518.零钱兑换II](https://mp.weixin.qq.com/s/PlowDsI4WMBOzf3q80AksQ) 中就已经讲过了。 +在[动态规划:518.零钱兑换II](https://programmercarl.com/0518.零钱兑换II.html) 中就已经讲过了。 **如果求组合数就是外层for循环遍历物品,内层for遍历背包**。 @@ -20,7 +20,7 @@ 所以本题遍历顺序最终遍历顺序:**target(背包)放在外循环,将nums(物品)放在内循环,内循环从前到后遍历**。 -```C++ +```CPP class Solution { public: int combinationSum4(vector& nums, int target) { @@ -40,7 +40,7 @@ public: ## 周二 -爬楼梯之前我们已经做过了,就是斐波那契数列,很好解,但[动态规划:70. 爬楼梯进阶版(完全背包)](https://mp.weixin.qq.com/s/e_wacnELo-2PG76EjrUakA)中我们进阶了一下。 +爬楼梯之前我们已经做过了,就是斐波那契数列,很好解,但[动态规划:70. 爬楼梯进阶版(完全背包)](https://programmercarl.com/0070.爬楼梯完全背包版本.html)中我们进阶了一下。 改为:每次可以爬 1 、 2、.....、m 个台阶。问有多少种不同的方法可以爬到楼顶呢? @@ -53,10 +53,10 @@ public: **此时大家应该发现这就是一个完全背包问题了!** -和昨天的题目[动态规划:377. 组合总和 Ⅳ](https://mp.weixin.qq.com/s/Iixw0nahJWQgbqVNk8k6gA)基本就是一道题了,遍历顺序也是一样一样的! +和昨天的题目[动态规划:377. 组合总和 Ⅳ](https://programmercarl.com/0377.组合总和Ⅳ.html)基本就是一道题了,遍历顺序也是一样一样的! 代码如下: -```C++ +```CPP class Solution { public: int climbStairs(int n) { @@ -77,7 +77,7 @@ public: ## 周三 -[动态规划:322.零钱兑换](https://mp.weixin.qq.com/s/dyk-xNilHzNtVdPPLObSeQ)给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数(每种硬币的数量是无限的)。 +[动态规划:322.零钱兑换](https://programmercarl.com/0322.零钱兑换.html)给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数(每种硬币的数量是无限的)。 这里我们都知道这是完全背包。 @@ -93,7 +93,7 @@ public: 外层for循环遍历物品,内层for遍历背包: -```C++ +```CPP // 版本一 class Solution { public: @@ -115,7 +115,7 @@ public: 外层for遍历背包,内层for循环遍历物品: -```C++ +```CPP // 版本二 class Solution { public: @@ -137,10 +137,10 @@ public: ## 周四 -[动态规划:279.完全平方数](https://mp.weixin.qq.com/s/VfJT78p7UGpDZsapKF_QJQ)给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, ...)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少(平方数可以重复使用)。 +[动态规划:279.完全平方数](https://programmercarl.com/0279.完全平方数.html)给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, ...)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少(平方数可以重复使用)。 -如果按顺序把前面的文章都看了,这道题目就是简单题了。 dp[i]的定义,递推公式,初始化,遍历顺序,都是和[动态规划:322. 零钱兑换](https://mp.weixin.qq.com/s/dyk-xNilHzNtVdPPLObSeQ) 一样一样的。 +如果按顺序把前面的文章都看了,这道题目就是简单题了。 dp[i]的定义,递推公式,初始化,遍历顺序,都是和[动态规划:322. 零钱兑换](https://programmercarl.com/0322.零钱兑换.html) 一样一样的。 要是没有前面的基础上来做这道题,那这道题目就有点难度了。 @@ -148,7 +148,7 @@ public: 先遍历背包,在遍历物品: -```C++ +```CPP // 版本一 class Solution { public: @@ -167,7 +167,7 @@ public: 先遍历物品,在遍历背包: -```C++ +```CPP // 版本二 class Solution { public: @@ -193,10 +193,11 @@ public: 我这里做一下总结: -求组合数:[动态规划:518.零钱兑换II](https://mp.weixin.qq.com/s/PlowDsI4WMBOzf3q80AksQ) -求排列数:[动态规划:377. 组合总和 Ⅳ](https://mp.weixin.qq.com/s/Iixw0nahJWQgbqVNk8k6gA)、[动态规划:70. 爬楼梯进阶版(完全背包)](https://mp.weixin.qq.com/s/e_wacnELo-2PG76EjrUakA) -求最小数:[动态规划:322. 零钱兑换](https://mp.weixin.qq.com/s/dyk-xNilHzNtVdPPLObSeQ)、[动态规划:279.完全平方数](https://mp.weixin.qq.com/s/VfJT78p7UGpDZsapKF_QJQ) +求组合数:[动态规划:518.零钱兑换II](https://programmercarl.com/0518.零钱兑换II.html) +求排列数:[动态规划:377. 组合总和 Ⅳ](https://programmercarl.com/0377.组合总和Ⅳ.html)、[动态规划:70. 爬楼梯进阶版(完全背包)](https://programmercarl.com/0070.爬楼梯完全背包版本.html) +求最小数:[动态规划:322. 零钱兑换](https://programmercarl.com/0322.零钱兑换.html)、[动态规划:279.完全平方数](https://programmercarl.com/0279.完全平方数.html) 此时我们就已经把完全背包的遍历顺序研究的透透的了! +
diff --git a/problems/周总结/20210225动规周末总结.md b/problems/周总结/20210225动规周末总结.md index 739d0469..ae8b4800 100644 --- a/problems/周总结/20210225动规周末总结.md +++ b/problems/周总结/20210225动规周末总结.md @@ -3,7 +3,7 @@ ## 周一 -[动态规划:开始打家劫舍!](https://mp.weixin.qq.com/s/UZ31WdLEEFmBegdgLkJ8Dw)中就是给一个数组相邻之间不能连着偷,如果偷才能得到最大金钱。 +[动态规划:开始打家劫舍!](https://programmercarl.com/0198.打家劫舍.html)中就是给一个数组相邻之间不能连着偷,如果偷才能得到最大金钱。 1. 确定dp数组含义 @@ -35,7 +35,7 @@ dp[1] = max(nums[0], nums[1]); ## 周二 -[动态规划:继续打家劫舍!](https://mp.weixin.qq.com/s/kKPx4HpH3RArbRcxAVHbeQ)就是数组成环了,然后相邻的不能连着偷。 +[动态规划:继续打家劫舍!](https://programmercarl.com/0213.打家劫舍II.html)就是数组成环了,然后相邻的不能连着偷。 这里主要考虑清楚三种情况: @@ -61,17 +61,17 @@ dp[1] = max(nums[0], nums[1]); 所以我在本文重点强调了情况一二三是“考虑”的范围,而具体房间偷与不偷交给递推公式去抉择。 -剩下的就和[动态规划:开始打家劫舍!](https://mp.weixin.qq.com/s/UZ31WdLEEFmBegdgLkJ8Dw)是一个逻辑了。 +剩下的就和[动态规划:开始打家劫舍!](https://programmercarl.com/0198.打家劫舍.html)是一个逻辑了。 ## 周三 -[动态规划:还要打家劫舍!](https://mp.weixin.qq.com/s/BOJ1lHsxbQxUZffXlgglEQ)这次是在一颗二叉树上打家劫舍了,条件还是一样的,相临的不能偷。 +[动态规划:还要打家劫舍!](https://programmercarl.com/0337.打家劫舍III.html)这次是在一颗二叉树上打家劫舍了,条件还是一样的,相临的不能偷。 这道题目是树形DP的入门题目,其实树形DP其实就是在树上进行递推公式的推导,没有什么神秘的。 这道题目我给出了暴力的解法: -```C++ +```CPP class Solution { public: int rob(TreeNode* root) { @@ -94,7 +94,7 @@ public: 代码如下: -```C++ +```CPP class Solution { public: unordered_map umap; // 记录计算过的结果 @@ -120,7 +120,7 @@ public: 1. 确定递归函数的参数和返回值 -```C++ +```CPP vector robTree(TreeNode* cur) { ``` @@ -142,7 +142,7 @@ if (cur == NULL) return vector{0, 0}; 采用后序遍历,代码如下: -```C++ +```CPP // 下标0:不偷,下标1:偷 vector left = robTree(cur->left); // 左 vector right = robTree(cur->right); // 右 @@ -160,7 +160,7 @@ vector right = robTree(cur->right); // 右 代码如下: -```C++ +```CPP vector left = robTree(cur->left); // 左 vector right = robTree(cur->right); // 右 @@ -184,14 +184,14 @@ return {val2, val1}; 因为平时我们习惯了在一维数组或者二维数组上推导公式,一下子换成了树,就需要对树的遍历方式足够了解! -大家还记不记得我在讲解贪心专题的时候,讲到这道题目:[贪心算法:我要监控二叉树!](https://mp.weixin.qq.com/s/kCxlLLjWKaE6nifHC3UL2Q),这也是贪心算法在树上的应用。**那我也可以把这个算法起一个名字,叫做树形贪心**,哈哈哈 +大家还记不记得我在讲解贪心专题的时候,讲到这道题目:[贪心算法:我要监控二叉树!](https://programmercarl.com/0968.监控二叉树.html),这也是贪心算法在树上的应用。**那我也可以把这个算法起一个名字,叫做树形贪心**,哈哈哈 “树形贪心”词汇从此诞生,来自「代码随想录」 ## 周四 -[动态规划:买卖股票的最佳时机](https://mp.weixin.qq.com/s/keWo5qYJY4zmHn3amfXdfQ) 一段时间,只能买买一次,问最大收益。 +[动态规划:买卖股票的最佳时机](https://programmercarl.com/0121.买卖股票的最佳时机.html) 一段时间,只能买买一次,问最大收益。 这里我给出了三中解法: @@ -218,7 +218,7 @@ public: 因为股票就买卖一次,那么贪心的想法很自然就是取最左最小值,取最右最大值,那么得到的差值就是最大利润。 -```C++ +```CPP class Solution { public: int maxProfit(vector& prices) { @@ -237,7 +237,7 @@ public: 动规解法,版本一,代码如下: -```C++ +```CPP // 版本一 class Solution { public: @@ -262,7 +262,7 @@ public: 那么我们只需要记录 当前天的dp状态和前一天的dp状态就可以了,可以使用滚动数组来节省空间,代码如下: -```C++ +```CPP // 版本二 class Solution { public: @@ -300,3 +300,4 @@ public: **代码随想录温馨提醒:投资有风险,入市需谨慎!** +
diff --git a/problems/周总结/20210304动规周末总结.md b/problems/周总结/20210304动规周末总结.md index 977b41e0..dad9884d 100644 --- a/problems/周总结/20210304动规周末总结.md +++ b/problems/周总结/20210304动规周末总结.md @@ -3,9 +3,9 @@ ## 周一 -[动态规划:买卖股票的最佳时机II](https://mp.weixin.qq.com/s/d4TRWFuhaY83HPa6t5ZL-w)中股票可以买买多了次! +[动态规划:买卖股票的最佳时机II](https://programmercarl.com/0122.买卖股票的最佳时机II(动态规划).html)中股票可以买买多了次! -这也是和[121. 买卖股票的最佳时机](https://mp.weixin.qq.com/s/keWo5qYJY4zmHn3amfXdfQ)的唯一区别(注意只有一只股票,所以再次购买前要出售掉之前的股票) +这也是和[121. 买卖股票的最佳时机](https://programmercarl.com/0121.买卖股票的最佳时机.html)的唯一区别(注意只有一只股票,所以再次购买前要出售掉之前的股票) 重点在于递推公式公式的不同。 @@ -22,7 +22,7 @@ dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] - prices[i]); dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] + prices[i]); ``` -大家可以发现本题和[121. 买卖股票的最佳时机](https://mp.weixin.qq.com/s/keWo5qYJY4zmHn3amfXdfQ)的代码几乎一样,唯一的区别在: +大家可以发现本题和[121. 买卖股票的最佳时机](https://programmercarl.com/0121.买卖股票的最佳时机.html)的代码几乎一样,唯一的区别在: ``` dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] - prices[i]); @@ -32,7 +32,7 @@ dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] - prices[i]); ## 周二 -[动态规划:买卖股票的最佳时机III](https://mp.weixin.qq.com/s/Sbs157mlVDtAR0gbLpdKzg)中最多只能完成两笔交易。 +[动态规划:买卖股票的最佳时机III](https://programmercarl.com/0123.买卖股票的最佳时机III.html)中最多只能完成两笔交易。 **这意味着可以买卖一次,可以买卖两次,也可以不买卖**。 @@ -85,9 +85,9 @@ dp[0][4] = 0; ## 周三 -[动态规划:买卖股票的最佳时机IV](https://mp.weixin.qq.com/s/jtxZJWAo2y5sUsW647Z5cw)最多可以完成 k 笔交易。 +[动态规划:买卖股票的最佳时机IV](https://programmercarl.com/0188.买卖股票的最佳时机IV.html)最多可以完成 k 笔交易。 -相对于上一道[动态规划:123.买卖股票的最佳时机III](https://mp.weixin.qq.com/s/Sbs157mlVDtAR0gbLpdKzg),本题需要通过前两次的交易,来类比前k次的交易 +相对于上一道[动态规划:123.买卖股票的最佳时机III](https://programmercarl.com/0123.买卖股票的最佳时机III.html),本题需要通过前两次的交易,来类比前k次的交易 1. 确定dp数组以及下标的含义 @@ -110,14 +110,14 @@ j的状态表示为: 还要强调一下:dp[i][1],**表示的是第i天,买入股票的状态,并不是说一定要第i天买入股票,这是很多同学容易陷入的误区**。 -```C++ +```CPP for (int j = 0; j < 2 * k - 1; j += 2) { dp[i][j + 1] = max(dp[i - 1][j + 1], dp[i - 1][j] - prices[i]); dp[i][j + 2] = max(dp[i - 1][j + 2], dp[i - 1][j + 1] + prices[i]); } ``` -**本题和[动态规划:123.买卖股票的最佳时机III](https://mp.weixin.qq.com/s/Sbs157mlVDtAR0gbLpdKzg)最大的区别就是这里要类比j为奇数是买,偶数是卖剩的状态**。 +**本题和[动态规划:123.买卖股票的最佳时机III](https://programmercarl.com/0123.买卖股票的最佳时机III.html)最大的区别就是这里要类比j为奇数是买,偶数是卖剩的状态**。 3. dp数组如何初始化 @@ -125,7 +125,7 @@ for (int j = 0; j < 2 * k - 1; j += 2) { 代码如下: -```C++ +```CPP for (int j = 1; j < 2 * k; j += 2) { dp[0][j] = -prices[0]; } @@ -147,9 +147,9 @@ for (int j = 1; j < 2 * k; j += 2) { ## 周四 -[动态规划:最佳买卖股票时机含冷冻期](https://mp.weixin.qq.com/s/IgC0iWWCDpYL9ZbTHGHgfw)尽可能地完成更多的交易(多次买卖一支股票),但有冷冻期,冷冻期为1天 +[动态规划:最佳买卖股票时机含冷冻期](https://programmercarl.com/0309.最佳买卖股票时机含冷冻期.html)尽可能地完成更多的交易(多次买卖一支股票),但有冷冻期,冷冻期为1天 -相对于[动态规划:122.买卖股票的最佳时机II](https://mp.weixin.qq.com/s/d4TRWFuhaY83HPa6t5ZL-w),本题加上了一个冷冻期 +相对于[动态规划:122.买卖股票的最佳时机II](https://programmercarl.com/0122.买卖股票的最佳时机II(动态规划).html),本题加上了一个冷冻期 **本题则需要第三个状态:不持有股票(冷冻期)的最多现金**。 @@ -202,3 +202,4 @@ vector> dp(n, vector(3, 0)); 下周还会有一篇股票系列的文章,**股票系列后面我也会单独写一篇总结,来高度概括一下,这样大家会对股票问题就有一个整体性的理解了**。 +
diff --git a/problems/周总结/二叉树阶段总结系列一.md b/problems/周总结/二叉树阶段总结系列一.md index dc73672d..595f367d 100644 --- a/problems/周总结/二叉树阶段总结系列一.md +++ b/problems/周总结/二叉树阶段总结系列一.md @@ -4,21 +4,21 @@ **注意这个周末总结和系列总结还是不一样的(二叉树还远没有结束),这个总结是针对留言疑问以及刷题群里讨论内容的归纳。** -1. [关于二叉树,你该了解这些!](https://mp.weixin.qq.com/s/q_eKfL8vmSbSFcptZ3aeRA) -2. [二叉树:一入递归深似海,从此offer是路人](https://mp.weixin.qq.com/s/Ww60X5mIKWdMQV4cN3ejOA) -3. [二叉树:听说递归能做的,栈也能做!](https://mp.weixin.qq.com/s/OH7aCVJ5-Gi32PkNCoZk4A) -4. [二叉树:前中后序迭代方式的写法就不能统一一下么?](https://mp.weixin.qq.com/s/ATQMPCpBlaAgrqdLDMVPZA) -5. [二叉树:层序遍历登场!](https://mp.weixin.qq.com/s/4-bDKi7SdwfBGRm9FYduiA) -6. [二叉树:你真的会翻转二叉树么?](https://mp.weixin.qq.com/s/jG0MgYR9DoUMYcRRF7magw) +1. [关于二叉树,你该了解这些!](https://programmercarl.com/二叉树理论基础.html) +2. [二叉树:一入递归深似海,从此offer是路人](https://programmercarl.com/二叉树的递归遍历.html) +3. [二叉树:听说递归能做的,栈也能做!](https://programmercarl.com/二叉树的迭代遍历.html) +4. [二叉树:前中后序迭代方式的写法就不能统一一下么?](https://programmercarl.com/二叉树的统一迭代法.html) +5. [二叉树:层序遍历登场!](https://programmercarl.com/0102.二叉树的层序遍历.html) +6. [二叉树:你真的会翻转二叉树么?](https://programmercarl.com/0226.翻转二叉树.html) -## [关于二叉树,你该了解这些!](https://mp.weixin.qq.com/s/q_eKfL8vmSbSFcptZ3aeRA) +## [关于二叉树,你该了解这些!](https://programmercarl.com/二叉树理论基础.html) 有同学会把红黑树和二叉平衡搜索树弄分开了,其实红黑树就是一种二叉平衡搜索树,这两个树不是独立的,所以C++中map、multimap、set、multiset的底层实现机制是二叉平衡搜索树,再具体一点是红黑树。 对于二叉树节点的定义,C++代码如下: -```C++ +```CPP struct TreeNode { int val; TreeNode *left; @@ -40,7 +40,7 @@ TreeNode* a = new TreeNode(9); 没有构造函数的话就要这么写: -```C++ +```CPP TreeNode* a = new TreeNode(); a->val = 9; a->left = NULL; @@ -51,24 +51,24 @@ a->right = NULL; morris遍历是二叉树遍历算法的超强进阶算法,morris遍历可以将非递归遍历中的空间复杂度降为O(1),感兴趣大家就去查一查学习学习,比较小众,面试几乎不会考。我其实也没有研究过,就不做过多介绍了。 -## [二叉树的递归遍历](https://mp.weixin.qq.com/s/Ww60X5mIKWdMQV4cN3ejOA) +## [二叉树的递归遍历](https://programmercarl.com/二叉树的递归遍历.html) -在[二叉树:一入递归深似海,从此offer是路人](https://mp.weixin.qq.com/s/Ww60X5mIKWdMQV4cN3ejOA)中讲到了递归三要素,以及前中后序的递归写法。 +在[二叉树:一入递归深似海,从此offer是路人](https://programmercarl.com/二叉树的递归遍历.html)中讲到了递归三要素,以及前中后序的递归写法。 -文章中我给出了leetcode上三道二叉树的前中后序题目,但是看完[二叉树:一入递归深似海,从此offer是路人](https://mp.weixin.qq.com/s/Ww60X5mIKWdMQV4cN3ejOA),依然可以解决n叉树的前后序遍历,在leetcode上分别是 +文章中我给出了leetcode上三道二叉树的前中后序题目,但是看完[二叉树:一入递归深似海,从此offer是路人](https://programmercarl.com/二叉树的递归遍历.html),依然可以解决n叉树的前后序遍历,在leetcode上分别是 * 589. N叉树的前序遍历 * 590. N叉树的后序遍历 大家可以再去把这两道题目做了。 -## [二叉树的非递归遍历](https://mp.weixin.qq.com/s/OH7aCVJ5-Gi32PkNCoZk4A) +## [二叉树的非递归遍历](https://programmercarl.com/二叉树的迭代遍历.html) 细心的同学发现文中前后序遍历空节点是入栈的,其实空节点入不入栈都差不多,但感觉空节点不入栈确实清晰一些,符合文中动画的演示。 前序遍历空节点不入栈的代码:(注意注释部分,和文章中的区别) -```C++ +```CPP class Solution { public: vector preorderTraversal(TreeNode* root) { @@ -91,7 +91,7 @@ public: 后序遍历空节点不入栈的代码:(注意注释部分,和文章中的区别) -```C++ +```CPP class Solution { public: vector postorderTraversal(TreeNode* root) { @@ -125,7 +125,7 @@ public: ## 周四 -在[二叉树:前中后序迭代方式的写法就不能统一一下么?](https://mp.weixin.qq.com/s/WKg0Ty1_3SZkztpHubZPRg)中我们使用空节点作为标记,给出了统一的前中后序迭代法。 +在[二叉树:前中后序迭代方式的写法就不能统一一下么?](https://programmercarl.com/二叉树的统一迭代法.html)中我们使用空节点作为标记,给出了统一的前中后序迭代法。 此时又多了一种前中后序的迭代写法,那么有同学问了:前中后序迭代法是不是一定要统一来写,这样才算是规范。 @@ -135,7 +135,7 @@ public: ## 周五 -在[二叉树:层序遍历登场!](https://mp.weixin.qq.com/s/Gb3BjakIKGNpup2jYtTzog)中我们介绍了二叉树的另一种遍历方式(图论中广度优先搜索在二叉树上的应用)即:层序遍历。 +在[二叉树:层序遍历登场!](https://programmercarl.com/0102.二叉树的层序遍历.html)中我们介绍了二叉树的另一种遍历方式(图论中广度优先搜索在二叉树上的应用)即:层序遍历。 看完这篇文章,去leetcode上怒刷五题,文章中 编号107题目的样例图放错了(原谅我匆忙之间总是手抖),但不影响大家理解。 @@ -145,14 +145,14 @@ public: ## 周六 -在[二叉树:你真的会翻转二叉树么?](https://mp.weixin.qq.com/s/6gY1MiXrnm-khAAJiIb5Bg)中我们把翻转二叉树这么一道简单又经典的问题,充分的剖析了一波,相信就算做过这道题目的同学,看完本篇之后依然有所收获! +在[二叉树:你真的会翻转二叉树么?](https://programmercarl.com/0226.翻转二叉树.html)中我们把翻转二叉树这么一道简单又经典的问题,充分的剖析了一波,相信就算做过这道题目的同学,看完本篇之后依然有所收获! **文中我指的是递归的中序遍历是不行的,因为使用递归的中序遍历,某些节点的左右孩子会翻转两次。** 如果非要使用递归中序的方式写,也可以,如下代码就可以避免节点左右孩子翻转两次的情况: -```C++ +```CPP class Solution { public: TreeNode* invertTree(TreeNode* root) { @@ -171,7 +171,7 @@ public: 代码如下: -```C++ +```CPP class Solution { public: TreeNode* invertTree(TreeNode* root) { @@ -206,3 +206,4 @@ public: **本周我们都是讲解了二叉树,从理论基础到遍历方式,从递归到迭代,从深度遍历到广度遍历,最后再用了一个翻转二叉树的题目把我们之前讲过的遍历方式都串了起来。** +
diff --git a/problems/哈希表总结.md b/problems/哈希表总结.md index c3fbde2b..58e386bc 100644 --- a/problems/哈希表总结.md +++ b/problems/哈希表总结.md @@ -12,7 +12,7 @@ # 哈希表理论基础 -在[关于哈希表,你该了解这些!](https://mp.weixin.qq.com/s/RSUANESA_tkhKhYe3ZR8Jg)中,我们介绍了哈希表的基础理论知识,不同于枯燥的讲解,这里介绍了都是对刷题有帮助的理论知识点。 +在[关于哈希表,你该了解这些!](https://programmercarl.com/哈希表理论基础.html)中,我们介绍了哈希表的基础理论知识,不同于枯燥的讲解,这里介绍了都是对刷题有帮助的理论知识点。 **一般来说哈希表都是用来快速判断一个元素是否出现集合里**。 @@ -28,7 +28,7 @@ * set(集合) * map(映射) -在C++语言中,set 和 map 都分别提供了三种数据结构,每种数据结构的底层实现和用途都有所不同,在[关于哈希表,你该了解这些!](https://mp.weixin.qq.com/s/RSUANESA_tkhKhYe3ZR8Jg)中我给出了详细分析,这一知识点很重要! +在C++语言中,set 和 map 都分别提供了三种数据结构,每种数据结构的底层实现和用途都有所不同,在[关于哈希表,你该了解这些!](https://programmercarl.com/哈希表理论基础.html)中我给出了详细分析,这一知识点很重要! 例如什么时候用std::set,什么时候用std::multiset,什么时候用std::unordered_set,都是很有考究的。 @@ -40,13 +40,13 @@ 一些应用场景就是为数组量身定做的。 -在[242.有效的字母异位词](https://mp.weixin.qq.com/s/ffS8jaVFNUWyfn_8T31IdA)中,我们提到了数组就是简单的哈希表,但是数组的大小是受限的! +在[242.有效的字母异位词](https://programmercarl.com/0242.有效的字母异位词.html)中,我们提到了数组就是简单的哈希表,但是数组的大小是受限的! 这道题目包含小写字母,那么使用数组来做哈希最合适不过。 -在[383.赎金信](https://mp.weixin.qq.com/s/qAXqv--UERmiJNNpuphOUQ)中同样要求只有小写字母,那么就给我们浓浓的暗示,用数组! +在[383.赎金信](https://programmercarl.com/0383.赎金信.html)中同样要求只有小写字母,那么就给我们浓浓的暗示,用数组! -本题和[242.有效的字母异位词](https://mp.weixin.qq.com/s/ffS8jaVFNUWyfn_8T31IdA)很像,[242.有效的字母异位词](https://mp.weixin.qq.com/s/ffS8jaVFNUWyfn_8T31IdA)是求 字符串a 和 字符串b 是否可以相互组成,在[383.赎金信](https://mp.weixin.qq.com/s/qAXqv--UERmiJNNpuphOUQ)中是求字符串a能否组成字符串b,而不用管字符串b 能不能组成字符串a。 +本题和[242.有效的字母异位词](https://programmercarl.com/0242.有效的字母异位词.html)很像,[242.有效的字母异位词](https://programmercarl.com/0242.有效的字母异位词.html)是求 字符串a 和 字符串b 是否可以相互组成,在[383.赎金信](https://programmercarl.com/0383.赎金信.html)中是求字符串a能否组成字符串b,而不用管字符串b 能不能组成字符串a。 一些同学可能想,用数组干啥,都用map不就完事了。 @@ -55,7 +55,7 @@ ## set作为哈希表 -在[349. 两个数组的交集](https://mp.weixin.qq.com/s/aMSA5zrp3jJcLjuSB0Es2Q)中我们给出了什么时候用数组就不行了,需要用set。 +在[349. 两个数组的交集](https://programmercarl.com/0349.两个数组的交集.html)中我们给出了什么时候用数组就不行了,需要用set。 这道题目没有限制数值的大小,就无法使用数组来做哈希表了。 @@ -66,7 +66,7 @@ 所以此时一样的做映射的话,就可以使用set了。 -关于set,C++ 给提供了如下三种可用的数据结构:(详情请看[关于哈希表,你该了解这些!](https://mp.weixin.qq.com/s/RSUANESA_tkhKhYe3ZR8Jg)) +关于set,C++ 给提供了如下三种可用的数据结构:(详情请看[关于哈希表,你该了解这些!](https://programmercarl.com/哈希表理论基础.html)) * std::set * std::multiset @@ -74,21 +74,21 @@ std::set和std::multiset底层实现都是红黑树,std::unordered_set的底层实现是哈希, 使用unordered_set 读写效率是最高的,本题并不需要对数据进行排序,而且还不要让数据重复,所以选择unordered_set。 -在[202.快乐数](https://mp.weixin.qq.com/s/n5q0ujxxrjQS3xuh3dgqBQ)中,我们再次使用了unordered_set来判断一个数是否重复出现过。 +在[202.快乐数](https://programmercarl.com/0202.快乐数.html)中,我们再次使用了unordered_set来判断一个数是否重复出现过。 ## map作为哈希表 -在[1.两数之和](https://mp.weixin.qq.com/s/vaMsLnH-f7_9nEK4Cuu3KQ)中map正式登场。 +在[1.两数之和](https://programmercarl.com/0001.两数之和.html)中map正式登场。 来说一说:使用数组和set来做哈希法的局限。 * 数组的大小是受限制的,而且如果元素很少,而哈希值太大会造成内存空间的浪费。 -* set是一个集合,里面放的元素只能是一个key,而两数之和这道题目,不仅要判断y是否存在而且还要记录y的下表位置,因为要返回x 和 y的下表。所以set 也不能用。 +* set是一个集合,里面放的元素只能是一个key,而两数之和这道题目,不仅要判断y是否存在而且还要记录y的下标位置,因为要返回x 和 y的下标。所以set 也不能用。 map是一种``的结构,本题可以用key保存数值,用value在保存数值所在的下表。所以使用map最为合适。 -C++提供如下三种map::(详情请看[关于哈希表,你该了解这些!](https://mp.weixin.qq.com/s/RSUANESA_tkhKhYe3ZR8Jg)) +C++提供如下三种map::(详情请看[关于哈希表,你该了解这些!](https://programmercarl.com/哈希表理论基础.html)) * std::map * std::multimap @@ -96,19 +96,19 @@ C++提供如下三种map::(详情请看[关于哈希表,你该了解这 std::unordered_map 底层实现为哈希,std::map 和std::multimap 的底层实现是红黑树。 -同理,std::map 和std::multimap 的key也是有序的(这个问题也经常作为面试题,考察对语言容器底层的理解),[1.两数之和](https://mp.weixin.qq.com/s/vaMsLnH-f7_9nEK4Cuu3KQ)中并不需要key有序,选择std::unordered_map 效率更高! +同理,std::map 和std::multimap 的key也是有序的(这个问题也经常作为面试题,考察对语言容器底层的理解),[1.两数之和](https://programmercarl.com/0001.两数之和.html)中并不需要key有序,选择std::unordered_map 效率更高! -在[454.四数相加](https://mp.weixin.qq.com/s/12g_w6RzHuEpFts1pT6BWw)中我们提到了其实需要哈希的地方都能找到map的身影。 +在[454.四数相加](https://programmercarl.com/0454.四数相加II.html)中我们提到了其实需要哈希的地方都能找到map的身影。 -本题咋眼一看好像和[18. 四数之和](https://mp.weixin.qq.com/s/SBU3THi1Kv6Sar7htqCB2Q),[15.三数之和](https://mp.weixin.qq.com/s/QfTNEByq1YlNSXRKEumwHg)差不多,其实差很多! +本题咋眼一看好像和[18. 四数之和](https://programmercarl.com/0018.四数之和.html),[15.三数之和](https://programmercarl.com/0015.三数之和.html)差不多,其实差很多! -**关键差别是本题为四个独立的数组,只要找到A[i] + B[j] + C[k] + D[l] = 0就可以,不用考虑重复问题,而[18. 四数之和](https://mp.weixin.qq.com/s/SBU3THi1Kv6Sar7htqCB2Q),[15.三数之和](https://mp.weixin.qq.com/s/QfTNEByq1YlNSXRKEumwHg)是一个数组(集合)里找到和为0的组合,可就难很多了!** +**关键差别是本题为四个独立的数组,只要找到A[i] + B[j] + C[k] + D[l] = 0就可以,不用考虑重复问题,而[18. 四数之和](https://programmercarl.com/0018.四数之和.html),[15.三数之和](https://programmercarl.com/0015.三数之和.html)是一个数组(集合)里找到和为0的组合,可就难很多了!** 用哈希法解决了两数之和,很多同学会感觉用哈希法也可以解决三数之和,四数之和。 其实是可以解决,但是非常麻烦,需要去重导致代码效率很低。 -在[15.三数之和](https://mp.weixin.qq.com/s/QfTNEByq1YlNSXRKEumwHg)中我给出了哈希法和双指针两个解法,大家就可以体会到,使用哈希法还是比较麻烦的。 +在[15.三数之和](https://programmercarl.com/0015.三数之和.html)中我给出了哈希法和双指针两个解法,大家就可以体会到,使用哈希法还是比较麻烦的。 所以18. 四数之和,15.三数之和都推荐使用双指针法! @@ -131,4 +131,4 @@ 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) -
+
diff --git a/problems/哈希表理论基础.md b/problems/哈希表理论基础.md index f78dc241..2d3b03bd 100644 --- a/problems/哈希表理论基础.md +++ b/problems/哈希表理论基础.md @@ -133,4 +133,4 @@ 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) -
+
diff --git a/problems/回溯总结.md b/problems/回溯总结.md index 793df516..5a9725dd 100644 --- a/problems/回溯总结.md +++ b/problems/回溯总结.md @@ -14,7 +14,7 @@ 关于回溯算法理论基础,我录了一期B站视频[带你学透回溯算法(理论篇)](https://www.bilibili.com/video/BV1cy4y167mM)如果对回溯算法还不了解的话,可以看一下。 -在[关于回溯算法,你该了解这些!](https://mp.weixin.qq.com/s/gjSgJbNbd1eAA5WkA-HeWw)中我们详细的介绍了回溯算法的理论知识,不同于教科书般的讲解,这里介绍的回溯法的效率,解决的问题以及模板都是在刷题的过程中非常实用! +在[关于回溯算法,你该了解这些!](https://programmercarl.com/回溯算法理论基础.html)中我们详细的介绍了回溯算法的理论知识,不同于教科书般的讲解,这里介绍的回溯法的效率,解决的问题以及模板都是在刷题的过程中非常实用! **回溯是递归的副产品,只要有递归就会有回溯**,所以回溯法也经常和二叉树遍历,深度优先搜索混在一起,因为这两种方式都是用了递归。 @@ -32,7 +32,7 @@ 回溯法确实不好理解,所以需要把回溯法抽象为一个图形来理解就容易多了,**在后面的每一道回溯法的题目我都将遍历过程抽象为树形结构方便大家的理解**。 -在[关于回溯算法,你该了解这些!](https://mp.weixin.qq.com/s/gjSgJbNbd1eAA5WkA-HeWw)还用了回溯三部曲来分析回溯算法,并给出了回溯法的模板: +在[关于回溯算法,你该了解这些!](https://programmercarl.com/回溯算法理论基础.html)还用了回溯三部曲来分析回溯算法,并给出了回溯法的模板: ``` void backtracking(参数) { @@ -55,7 +55,7 @@ void backtracking(参数) { ## 组合问题 -在[回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)中,我们开始用回溯法解决第一道题目:组合问题。 +在[回溯算法:求组合问题!](https://programmercarl.com/0077.组合.html)中,我们开始用回溯法解决第一道题目:组合问题。 我在文中开始的时候给大家列举k层for循环例子,进而得出都是同样是暴利解法,为什么要用回溯法! @@ -71,13 +71,13 @@ void backtracking(参数) { **所以,录友们刚开始学回溯法,起跑姿势就很标准了!** -优化回溯算法只有剪枝一种方法,在[回溯算法:组合问题再剪剪枝](https://mp.weixin.qq.com/s/Ri7spcJMUmph4c6XjPWXQA)中把回溯法代码做了剪枝优化,树形结构如图: +优化回溯算法只有剪枝一种方法,在[回溯算法:组合问题再剪剪枝](https://programmercarl.com/0077.组合优化.html)中把回溯法代码做了剪枝优化,树形结构如图: ![77.组合4](https://img-blog.csdnimg.cn/20201118153133458.png) 大家可以一目了然剪的究竟是哪里。 -**[回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)剪枝精髓是:for循环在寻找起点的时候要有一个范围,如果这个起点到集合终止之间的元素已经不够题目要求的k个元素了,就没有必要搜索了**。 +**[回溯算法:求组合问题!](https://programmercarl.com/0077.组合.html)剪枝精髓是:for循环在寻找起点的时候要有一个范围,如果这个起点到集合终止之间的元素已经不够题目要求的k个元素了,就没有必要搜索了**。 **在for循环上做剪枝操作是回溯法剪枝的常见套路!** 后面的题目还会经常用到。 @@ -86,7 +86,7 @@ void backtracking(参数) { ### 组合总和(一) -在[回溯算法:求组合总和!](https://mp.weixin.qq.com/s/HX7WW6ixbFZJASkRnCTC3w)中,相当于 [回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)加了一个元素总和的限制。 +在[回溯算法:求组合总和!](https://programmercarl.com/0216.组合总和III.html)中,相当于 [回溯算法:求组合问题!](https://programmercarl.com/0077.组合.html)加了一个元素总和的限制。 树形结构如图: ![216.组合总和III](https://img-blog.csdnimg.cn/20201118201921245.png) @@ -95,21 +95,21 @@ void backtracking(参数) { ![216.组合总和III1](https://img-blog.csdnimg.cn/20201118202038240.png) -在本题中,依然还可以有一个剪枝,就是[回溯算法:组合问题再剪剪枝](https://mp.weixin.qq.com/s/Ri7spcJMUmph4c6XjPWXQA)中提到的,对for循环选择的起始范围的剪枝。 +在本题中,依然还可以有一个剪枝,就是[回溯算法:组合问题再剪剪枝](https://programmercarl.com/0077.组合优化.html)中提到的,对for循环选择的起始范围的剪枝。 所以剪枝的代码可以在for循环加上 `i <= 9 - (k - path.size()) + 1` 的限制! ### 组合总和(二) -在[回溯算法:求组合总和(二)](https://mp.weixin.qq.com/s/FLg8G6EjVcxBjwCbzpACPw)中讲解的组合总和问题,和[回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ),[回溯算法:求组合总和!](https://mp.weixin.qq.com/s/HX7WW6ixbFZJASkRnCTC3w)和区别是:本题没有数量要求,可以无限重复,但是有总和的限制,所以间接的也是有个数的限制。 +在[回溯算法:求组合总和(二)](https://programmercarl.com/0039.组合总和.html)中讲解的组合总和问题,和[回溯算法:求组合问题!](https://programmercarl.com/0077.组合.html),[回溯算法:求组合总和!](https://programmercarl.com/0216.组合总和III.html)和区别是:本题没有数量要求,可以无限重复,但是有总和的限制,所以间接的也是有个数的限制。 不少同学都是看到可以重复选择,就义无反顾的把startIndex去掉了。 **本题还需要startIndex来控制for循环的起始位置,对于组合问题,什么时候需要startIndex呢?** -我举过例子,如果是一个集合来求组合的话,就需要startIndex,例如:[回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ),[回溯算法:求组合总和!](https://mp.weixin.qq.com/s/HX7WW6ixbFZJASkRnCTC3w)。 +我举过例子,如果是一个集合来求组合的话,就需要startIndex,例如:[回溯算法:求组合问题!](https://programmercarl.com/0077.组合.html),[回溯算法:求组合总和!](https://programmercarl.com/0216.组合总和III.html)。 -如果是多个集合取组合,各个集合之间相互不影响,那么就不用startIndex,例如:[回溯算法:电话号码的字母组合](https://mp.weixin.qq.com/s/e2ua2cmkE_vpYjM3j6HY0A) +如果是多个集合取组合,各个集合之间相互不影响,那么就不用startIndex,例如:[回溯算法:电话号码的字母组合](https://programmercarl.com/0017.电话号码的字母组合.html) **注意以上我只是说求组合的情况,如果是排列问题,又是另一套分析的套路**。 @@ -130,7 +130,7 @@ for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; ### 组合总和(三) -在[回溯算法:求组合总和(三)](https://mp.weixin.qq.com/s/_1zPYk70NvHsdY8UWVGXmQ)中集合元素会有重复,但要求解集不能包含重复的组合。 +在[回溯算法:求组合总和(三)](https://programmercarl.com/0040.组合总和II.html)中集合元素会有重复,但要求解集不能包含重复的组合。 **所以难就难在去重问题上了**。 @@ -153,11 +153,11 @@ for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; ## 多个集合求组合 -在[回溯算法:电话号码的字母组合](https://mp.weixin.qq.com/s/e2ua2cmkE_vpYjM3j6HY0A)中,开始用多个集合来求组合,还是熟悉的模板题目,但是有一些细节。 +在[回溯算法:电话号码的字母组合](https://programmercarl.com/0017.电话号码的字母组合.html)中,开始用多个集合来求组合,还是熟悉的模板题目,但是有一些细节。 -例如这里for循环,可不像是在 [回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)和[回溯算法:求组合总和!](https://mp.weixin.qq.com/s/HX7WW6ixbFZJASkRnCTC3w)中从startIndex开始遍历的。 +例如这里for循环,可不像是在 [回溯算法:求组合问题!](https://programmercarl.com/0077.组合.html)和[回溯算法:求组合总和!](https://programmercarl.com/0216.组合总和III.html)中从startIndex开始遍历的。 -**因为本题每一个数字代表的是不同集合,也就是求不同集合之间的组合,而[回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)和[回溯算法:求组合总和!](https://mp.weixin.qq.com/s/HX7WW6ixbFZJASkRnCTC3w)都是是求同一个集合中的组合!** +**因为本题每一个数字代表的是不同集合,也就是求不同集合之间的组合,而[回溯算法:求组合问题!](https://programmercarl.com/0077.组合.html)和[回溯算法:求组合总和!](https://programmercarl.com/0216.组合总和III.html)都是是求同一个集合中的组合!** 树形结构如下: @@ -169,7 +169,7 @@ for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; # 切割问题 -在[回溯算法:分割回文串](https://mp.weixin.qq.com/s/Pb1epUTbU8fHIht-g_MS5Q)中,我们开始讲解切割问题,虽然最后代码看起来好像是一道模板题,但是从分析到学会套用这个模板,是比较难的。 +在[回溯算法:分割回文串](https://programmercarl.com/0131.分割回文串.html)中,我们开始讲解切割问题,虽然最后代码看起来好像是一道模板题,但是从分析到学会套用这个模板,是比较难的。 我列出如下几个难点: @@ -196,7 +196,7 @@ for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; ## 子集问题(一) -在[回溯算法:求子集问题!](https://mp.weixin.qq.com/s/NNRzX-vJ_pjK4qxohd_LtA)中讲解了子集问题,**在树形结构中子集问题是要收集所有节点的结果,而组合问题是收集叶子节点的结果**。 +在[回溯算法:求子集问题!](https://programmercarl.com/0078.子集.html)中讲解了子集问题,**在树形结构中子集问题是要收集所有节点的结果,而组合问题是收集叶子节点的结果**。 如图: @@ -221,9 +221,9 @@ if (startIndex >= nums.size()) { // 终止条件可以不加 ## 子集问题(二) -在[回溯算法:求子集问题(二)](https://mp.weixin.qq.com/s/WJ4JNDRJgsW3eUN72Hh3uQ)中,开始针对子集问题进行去重。 +在[回溯算法:求子集问题(二)](https://programmercarl.com/0090.子集II.html)中,开始针对子集问题进行去重。 -本题就是[回溯算法:求子集问题!](https://mp.weixin.qq.com/s/NNRzX-vJ_pjK4qxohd_LtA)的基础上加上了去重,去重我们在[回溯算法:求组合总和(三)](https://mp.weixin.qq.com/s/_1zPYk70NvHsdY8UWVGXmQ)也讲过了,一样的套路。 +本题就是[回溯算法:求子集问题!](https://programmercarl.com/0078.子集.html)的基础上加上了去重,去重我们在[回溯算法:求组合总和(三)](https://programmercarl.com/0040.组合总和II.html)也讲过了,一样的套路。 树形结构如下: @@ -231,15 +231,15 @@ if (startIndex >= nums.size()) { // 终止条件可以不加 ## 递增子序列 -在[回溯算法:递增子序列](https://mp.weixin.qq.com/s/ePxOtX1ATRYJb2Jq7urzHQ)中,处处都能看到子集的身影,但处处是陷阱,值得好好琢磨琢磨! +在[回溯算法:递增子序列](https://programmercarl.com/0491.递增子序列.html)中,处处都能看到子集的身影,但处处是陷阱,值得好好琢磨琢磨! 树形结构如下: ![491. 递增子序列1](https://img-blog.csdnimg.cn/20201112170832333.png) -很多同学都会把这道题目和[回溯算法:求子集问题(二)](https://mp.weixin.qq.com/s/WJ4JNDRJgsW3eUN72Hh3uQ)混在一起。 +很多同学都会把这道题目和[回溯算法:求子集问题(二)](https://programmercarl.com/0090.子集II.html)混在一起。 -**[回溯算法:求子集问题(二)](https://mp.weixin.qq.com/s/WJ4JNDRJgsW3eUN72Hh3uQ)也可以使用set针对同一父节点本层去重,但子集问题一定要排序,为什么呢?** +**[回溯算法:求子集问题(二)](https://programmercarl.com/0090.子集II.html)也可以使用set针对同一父节点本层去重,但子集问题一定要排序,为什么呢?** 我用没有排序的集合{2,1,2,2}来举个例子画一个图,如下: @@ -251,7 +251,7 @@ if (startIndex >= nums.size()) { // 终止条件可以不加 ## 排列问题(一) -[回溯算法:排列问题!](https://mp.weixin.qq.com/s/SCOjeMX1t41wcvJq49GhMw) 又不一样了。 +[回溯算法:排列问题!](https://programmercarl.com/0046.全排列.html) 又不一样了。 排列是有序的,也就是说[1,2] 和[2,1] 是两个集合,这和之前分析的子集以及组合所不同的地方。 @@ -268,7 +268,7 @@ if (startIndex >= nums.size()) { // 终止条件可以不加 ## 排列问题(二) -排列问题也要去重了,在[回溯算法:排列问题(二)](https://mp.weixin.qq.com/s/9L8h3WqRP_h8LLWNT34YlA)中又一次强调了“树层去重”和“树枝去重”。 +排列问题也要去重了,在[回溯算法:排列问题(二)](https://programmercarl.com/0047.全排列II.html)中又一次强调了“树层去重”和“树枝去重”。 树形结构如下: @@ -294,17 +294,17 @@ if (startIndex >= nums.size()) { // 终止条件可以不加 以上我都是统一使用used数组来去重的,其实使用set也可以用来去重! -在[本周小结!(回溯算法系列三)续集](https://mp.weixin.qq.com/s/kSMGHc_YpsqL2j-jb_E_Ag)中给出了子集、组合、排列问题使用set来去重的解法以及具体代码,并纠正一些同学的常见错误写法。 +在[本周小结!(回溯算法系列三)续集](https://programmercarl.com/回溯算法去重问题的另一种写法.html)中给出了子集、组合、排列问题使用set来去重的解法以及具体代码,并纠正一些同学的常见错误写法。 同时详细分析了 使用used数组去重 和 使用set去重 两种写法的性能差异: **使用set去重的版本相对于used数组的版本效率都要低很多**,大家在leetcode上提交,能明显发现。 -原因在[回溯算法:递增子序列](https://mp.weixin.qq.com/s/ePxOtX1ATRYJb2Jq7urzHQ)中也分析过,主要是因为程序运行的时候对unordered_set 频繁的insert,unordered_set需要做哈希映射(也就是把key通过hash function映射为唯一的哈希值)相对费时间,而且insert的时候其底层的符号表也要做相应的扩充,也是费时的。 +原因在[回溯算法:递增子序列](https://programmercarl.com/0491.递增子序列.html)中也分析过,主要是因为程序运行的时候对unordered_set 频繁的insert,unordered_set需要做哈希映射(也就是把key通过hash function映射为唯一的哈希值)相对费时间,而且insert的时候其底层的符号表也要做相应的扩充,也是费时的。 **而使用used数组在时间复杂度上几乎没有额外负担!** -**使用set去重,不仅时间复杂度高了,空间复杂度也高了**,在[本周小结!(回溯算法系列三)](https://mp.weixin.qq.com/s/tLkt9PSo42X60w8i94ViiA)中分析过,组合,子集,排列问题的空间复杂度都是O(n),但如果使用set去重,空间复杂度就变成了O(n^2),因为每一层递归都有一个set集合,系统栈空间是n,每一个空间都有set集合。 +**使用set去重,不仅时间复杂度高了,空间复杂度也高了**,在[本周小结!(回溯算法系列三)](https://programmercarl.com/周总结/20201112回溯周末总结.html)中分析过,组合,子集,排列问题的空间复杂度都是O(n),但如果使用set去重,空间复杂度就变成了O(n^2),因为每一层递归都有一个set集合,系统栈空间是n,每一个空间都有set集合。 那有同学可能疑惑 用used数组也是占用O(n)的空间啊? @@ -314,7 +314,7 @@ used数组可是全局变量,每层与每层之间公用一个used数组,所 之前说过,有递归的地方就有回溯,深度优先搜索也是用递归来实现的,所以往往伴随着回溯。 -在[回溯算法:重新安排行程](https://mp.weixin.qq.com/s/3kmbS4qDsa6bkyxR92XCTA)其实也算是图论里深搜的题目,但是我用回溯法的套路来讲解这道题目,算是给大家拓展一下思路,原来回溯法还可以这么玩! +在[回溯算法:重新安排行程](https://programmercarl.com/0332.重新安排行程.html)其实也算是图论里深搜的题目,但是我用回溯法的套路来讲解这道题目,算是给大家拓展一下思路,原来回溯法还可以这么玩! 以输入:[["JFK", "KUL"], ["JFK", "NRT"], ["NRT", "JFK"]为例,抽象为树形结构如下: @@ -331,7 +331,7 @@ used数组可是全局变量,每层与每层之间公用一个used数组,所 ## N皇后问题 -在[回溯算法:N皇后问题](https://mp.weixin.qq.com/s/lU_QwCMj6g60nh8m98GAWg)中终于迎来了传说中的N皇后。 +在[回溯算法:N皇后问题](https://programmercarl.com/0051.N皇后.html)中终于迎来了传说中的N皇后。 下面我用一个3 * 3 的棋牌,将搜索过程抽象为一颗树,如图: @@ -345,19 +345,19 @@ used数组可是全局变量,每层与每层之间公用一个used数组,所 **这里我明确给出了棋盘的宽度就是for循环的长度,递归的深度就是棋盘的高度,这样就可以套进回溯法的模板里了**。 -相信看完本篇[回溯算法:N皇后问题](https://mp.weixin.qq.com/s/lU_QwCMj6g60nh8m98GAWg)也没那么难了,传说已经不是传说了,哈哈。 +相信看完本篇[回溯算法:N皇后问题](https://programmercarl.com/0051.N皇后.html)也没那么难了,传说已经不是传说了,哈哈。 ## 解数独问题 -在[回溯算法:解数独](https://mp.weixin.qq.com/s/eWE9TapVwm77yW9Q81xSZQ)中要征服回溯法的最后一道山峰。 +在[回溯算法:解数独](https://programmercarl.com/0037.解数独.html)中要征服回溯法的最后一道山峰。 解数独应该是棋盘很难的题目了,比N皇后还要复杂一些,但只要理解 “二维递归”这个过程,其实发现就没那么难了。 -大家已经跟着「代码随想录」刷过了如下回溯法题目,例如:[77.组合(组合问题)](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ),[131.分割回文串(分割问题)](https://mp.weixin.qq.com/s/Pb1epUTbU8fHIht-g_MS5Q),[78.子集(子集问题)](https://mp.weixin.qq.com/s/NNRzX-vJ_pjK4qxohd_LtA),[46.全排列(排列问题)](https://mp.weixin.qq.com/s/SCOjeMX1t41wcvJq49GhMw),以及[51.N皇后(N皇后问题)](https://mp.weixin.qq.com/s/lU_QwCMj6g60nh8m98GAWg),其实这些题目都是一维递归。 +大家已经跟着「代码随想录」刷过了如下回溯法题目,例如:[77.组合(组合问题)](https://programmercarl.com/0077.组合.html),[131.分割回文串(分割问题)](https://programmercarl.com/0131.分割回文串.html),[78.子集(子集问题)](https://programmercarl.com/0078.子集.html),[46.全排列(排列问题)](https://programmercarl.com/0046.全排列.html),以及[51.N皇后(N皇后问题)](https://programmercarl.com/0051.N皇后.html),其实这些题目都是一维递归。 -其中[N皇后问题](https://mp.weixin.qq.com/s/lU_QwCMj6g60nh8m98GAWg)是因为每一行每一列只放一个皇后,只需要一层for循环遍历一行,递归来遍历列,然后一行一列确定皇后的唯一位置。 +其中[N皇后问题](https://programmercarl.com/0051.N皇后.html)是因为每一行每一列只放一个皇后,只需要一层for循环遍历一行,递归来遍历列,然后一行一列确定皇后的唯一位置。 本题就不一样了,**本题中棋盘的每一个位置都要放一个数字,并检查数字是否合法,解数独的树形结构要比N皇后更宽更深**。 @@ -367,7 +367,7 @@ used数组可是全局变量,每层与每层之间公用一个used数组,所 解数独可以说是非常难的题目了,如果还一直停留在一维递归的逻辑中,这道题目可以让大家瞬间崩溃。 -**所以我在[回溯算法:解数独](https://mp.weixin.qq.com/s/eWE9TapVwm77yW9Q81xSZQ)中开篇就提到了二维递归,这也是我自创词汇**,希望可以帮助大家理解解数独的搜索过程。 +**所以我在[回溯算法:解数独](https://programmercarl.com/0037.解数独.html)中开篇就提到了二维递归,这也是我自创词汇**,希望可以帮助大家理解解数独的搜索过程。 一波分析之后,在看代码会发现其实也不难,唯一难点就是理解**二维递归**的思维逻辑。 @@ -382,7 +382,7 @@ used数组可是全局变量,每层与每层之间公用一个used数组,所 以下在计算空间复杂度的时候我都把系统栈(不是数据结构里的栈)所占空间算进去。 子集问题分析: -* 时间复杂度:O(n * 2^n),因为每一个元素的状态无外乎取与不取,所以时间复杂度为O(2^n),构造每一组子集都需要填进数组,又有需要O(n),最终时间复杂度:O(n * 2^n) +* 时间复杂度:O(2^n),因为每一个元素的状态无外乎取与不取,所以时间复杂度为O(2^n) * 空间复杂度:O(n),递归深度为n,所以系统栈所用空间为O(n),每一层递归所用的空间都是常数级别,注意代码里的result和path都是全局变量,就算是放在参数里,传的也是引用,并不会新申请内存空间,最终空间复杂度为O(n) 排列问题分析: @@ -390,7 +390,7 @@ used数组可是全局变量,每层与每层之间公用一个used数组,所 * 空间复杂度:O(n),和子集问题同理。 组合问题分析: -* 时间复杂度:O(n * 2^n),组合问题其实就是一种子集的问题,所以组合问题最坏的情况,也不会超过子集问题的时间复杂度。 +* 时间复杂度:O(2^n),组合问题其实就是一种子集的问题,所以组合问题最坏的情况,也不会超过子集问题的时间复杂度。 * 空间复杂度:O(n),和子集问题同理。 N皇后问题分析: @@ -454,4 +454,4 @@ Go: * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/回溯算法去重问题的另一种写法.md b/problems/回溯算法去重问题的另一种写法.md index 19a4ae3b..d267d23c 100644 --- a/problems/回溯算法去重问题的另一种写法.md +++ b/problems/回溯算法去重问题的另一种写法.md @@ -8,13 +8,13 @@ # 回溯算法去重问题的另一种写法 -> 在 [本周小结!(回溯算法系列三)](https://mp.weixin.qq.com/s/tLkt9PSo42X60w8i94ViiA) 中一位录友对 整颗树的本层和同一节点的本层有疑问,也让我重新思考了一下,发现这里确实有问题,所以专门写一篇来纠正,感谢录友们的积极交流哈! +> 在 [本周小结!(回溯算法系列三)](https://programmercarl.com/周总结/20201112回溯周末总结.html) 中一位录友对 整颗树的本层和同一节点的本层有疑问,也让我重新思考了一下,发现这里确实有问题,所以专门写一篇来纠正,感谢录友们的积极交流哈! 接下来我再把这块再讲一下。 -在[回溯算法:求子集问题(二)](https://mp.weixin.qq.com/s/WJ4JNDRJgsW3eUN72Hh3uQ)中的去重和 [回溯算法:递增子序列](https://mp.weixin.qq.com/s/ePxOtX1ATRYJb2Jq7urzHQ)中的去重 都是 同一父节点下本层的去重。 +在[回溯算法:求子集问题(二)](https://programmercarl.com/0090.子集II.html)中的去重和 [回溯算法:递增子序列](https://programmercarl.com/0491.递增子序列.html)中的去重 都是 同一父节点下本层的去重。 -[回溯算法:求子集问题(二)](https://mp.weixin.qq.com/s/WJ4JNDRJgsW3eUN72Hh3uQ)也可以使用set针对同一父节点本层去重,但子集问题一定要排序,为什么呢? +[回溯算法:求子集问题(二)](https://programmercarl.com/0090.子集II.html)也可以使用set针对同一父节点本层去重,但子集问题一定要排序,为什么呢? 我用没有排序的集合{2,1,2,2}来举例子画一个图,如图: @@ -22,15 +22,15 @@ 图中,大家就很明显的看到,子集重复了。 -那么下面我针对[回溯算法:求子集问题(二)](https://mp.weixin.qq.com/s/WJ4JNDRJgsW3eUN72Hh3uQ) 给出使用set来对本层去重的代码实现。 +那么下面我针对[回溯算法:求子集问题(二)](https://programmercarl.com/0090.子集II.html) 给出使用set来对本层去重的代码实现。 ## 90.子集II -used数组去重版本: [回溯算法:求子集问题(二)](https://mp.weixin.qq.com/s/WJ4JNDRJgsW3eUN72Hh3uQ) +used数组去重版本: [回溯算法:求子集问题(二)](https://programmercarl.com/0090.子集II.html) 使用set去重的版本如下: -```C++ +```CPP class Solution { private: vector> result; @@ -71,7 +71,7 @@ public: 例如: -```C++ +```CPP class Solution { private: vector> result; @@ -110,7 +110,7 @@ private: 代码如下: -```C++ +```CPP class Solution { private: vector> result; @@ -138,11 +138,11 @@ uset已经是全局变量,本层的uset记录了一个元素,然后进入下 ## 40. 组合总和 II -使用used数组去重版本:[回溯算法:求组合总和(三)](https://mp.weixin.qq.com/s/_1zPYk70NvHsdY8UWVGXmQ) +使用used数组去重版本:[回溯算法:求组合总和(三)](https://programmercarl.com/0040.组合总和II.html) 使用set去重的版本如下: -```C++ +```CPP class Solution { private: vector> result; @@ -179,11 +179,11 @@ public: ## 47. 全排列 II -使用used数组去重版本:[回溯算法:排列问题(二)](https://mp.weixin.qq.com/s/9L8h3WqRP_h8LLWNT34YlA) +使用used数组去重版本:[回溯算法:排列问题(二)](https://programmercarl.com/0047.全排列II.html) 使用set去重的版本如下: -```C++ +```CPP class Solution { private: vector> result; @@ -224,11 +224,11 @@ public: 需要注意的是:**使用set去重的版本相对于used数组的版本效率都要低很多**,大家在leetcode上提交,能明显发现。 -原因在[回溯算法:递增子序列](https://mp.weixin.qq.com/s/ePxOtX1ATRYJb2Jq7urzHQ)中也分析过,主要是因为程序运行的时候对unordered_set 频繁的insert,unordered_set需要做哈希映射(也就是把key通过hash function映射为唯一的哈希值)相对费时间,而且insert的时候其底层的符号表也要做相应的扩充,也是费时的。 +原因在[回溯算法:递增子序列](https://programmercarl.com/0491.递增子序列.html)中也分析过,主要是因为程序运行的时候对unordered_set 频繁的insert,unordered_set需要做哈希映射(也就是把key通过hash function映射为唯一的哈希值)相对费时间,而且insert的时候其底层的符号表也要做相应的扩充,也是费时的。 **而使用used数组在时间复杂度上几乎没有额外负担!** -**使用set去重,不仅时间复杂度高了,空间复杂度也高了**,在[本周小结!(回溯算法系列三)](https://mp.weixin.qq.com/s/tLkt9PSo42X60w8i94ViiA)中分析过,组合,子集,排列问题的空间复杂度都是O(n),但如果使用set去重,空间复杂度就变成了O(n^2),因为每一层递归都有一个set集合,系统栈空间是n,每一个空间都有set集合。 +**使用set去重,不仅时间复杂度高了,空间复杂度也高了**,在[本周小结!(回溯算法系列三)](https://programmercarl.com/周总结/20201112回溯周末总结.html)中分析过,组合,子集,排列问题的空间复杂度都是O(n),但如果使用set去重,空间复杂度就变成了O(n^2),因为每一层递归都有一个set集合,系统栈空间是n,每一个空间都有set集合。 那有同学可能疑惑 用used数组也是占用O(n)的空间啊? @@ -236,7 +236,7 @@ used数组可是全局变量,每层与每层之间公用一个used数组,所 ## 总结 -本篇本打算是对[本周小结!(回溯算法系列三)](https://mp.weixin.qq.com/s/tLkt9PSo42X60w8i94ViiA)的一个点做一下纠正,没想到又写出来这么多! +本篇本打算是对[本周小结!(回溯算法系列三)](https://programmercarl.com/周总结/20201112回溯周末总结.html)的一个点做一下纠正,没想到又写出来这么多! **这个点都源于一位录友的疑问,然后我思考总结了一下,就写着这一篇,所以还是得多交流啊!** @@ -263,4 +263,4 @@ Go: * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/回溯算法理论基础.md b/problems/回溯算法理论基础.md index 35e9db0f..29981c66 100644 --- a/problems/回溯算法理论基础.md +++ b/problems/回溯算法理论基础.md @@ -13,7 +13,7 @@ 回溯法也可以叫做回溯搜索法,它是一种搜索的方式。 -在二叉树系列中,我们已经不止一次,提到了回溯,例如[二叉树:以为使用了递归,其实还隐藏着回溯](https://mp.weixin.qq.com/s/ivLkHzWdhjQQD1rQWe6zWA)。 +在二叉树系列中,我们已经不止一次,提到了回溯,例如[二叉树:以为使用了递归,其实还隐藏着回溯](https://programmercarl.com/二叉树中递归带着回溯.html)。 回溯是递归的副产品,只要有递归就会有回溯。 @@ -67,7 +67,7 @@ 这里给出Carl总结的回溯算法模板。 -在讲[二叉树的递归](https://mp.weixin.qq.com/s/PwVIfxDlT3kRgMASWAMGhA)中我们说了递归三部曲,这里我再给大家列出回溯三部曲。 +在讲[二叉树的递归](https://programmercarl.com/二叉树的递归遍历.html)中我们说了递归三部曲,这里我再给大家列出回溯三部曲。 * 回溯函数模板返回值以及参数 @@ -87,7 +87,7 @@ void backtracking(参数) * 回溯函数终止条件 -既然是树形结构,那么我们在讲解[二叉树的递归](https://mp.weixin.qq.com/s/PwVIfxDlT3kRgMASWAMGhA)的时候,就知道遍历树形结构一定要有终止条件。 +既然是树形结构,那么我们在讲解[二叉树的递归](https://programmercarl.com/二叉树的递归遍历.html)的时候,就知道遍历树形结构一定要有终止条件。 所以回溯也有要终止条件。 @@ -181,4 +181,4 @@ Go: * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/字符串总结.md b/problems/字符串总结.md index 71be6422..57ac9a31 100644 --- a/problems/字符串总结.md +++ b/problems/字符串总结.md @@ -46,7 +46,7 @@ for (int i = 0; i < a.size(); i++) { # 要不要使用库函数 -在文章[344.反转字符串](https://mp.weixin.qq.com/s/_rNm66OJVl92gBDIbGpA3w)中强调了**打基础的时候,不要太迷恋于库函数。** +在文章[344.反转字符串](https://programmercarl.com/0344.反转字符串.html)中强调了**打基础的时候,不要太迷恋于库函数。** 甚至一些同学习惯于调用substr,split,reverse之类的库函数,却不知道其实现原理,也不知道其时间复杂度,这样实现出来的代码,如果在面试现场,面试官问:“分析其时间复杂度”的话,一定会一脸懵逼! @@ -57,15 +57,15 @@ for (int i = 0; i < a.size(); i++) { # 双指针法 -在[344.反转字符串](https://mp.weixin.qq.com/s/_rNm66OJVl92gBDIbGpA3w) ,我们使用双指针法实现了反转字符串的操作,**双指针法在数组,链表和字符串中很常用。** +在[344.反转字符串](https://programmercarl.com/0344.反转字符串.html) ,我们使用双指针法实现了反转字符串的操作,**双指针法在数组,链表和字符串中很常用。** -接着在[字符串:替换空格](https://mp.weixin.qq.com/s/69HNjR4apcRSAo_KyknPjA),同样还是使用双指针法在时间复杂度O(n)的情况下完成替换空格。 +接着在[字符串:替换空格](https://programmercarl.com/剑指Offer05.替换空格.html),同样还是使用双指针法在时间复杂度O(n)的情况下完成替换空格。 **其实很多数组填充类的问题,都可以先预先给数组扩容带填充后的大小,然后在从后向前进行操作。** -那么针对数组删除操作的问题,其实在[27. 移除元素](https://mp.weixin.qq.com/s/RMkulE4NIb6XsSX83ra-Ww)中就已经提到了使用双指针法进行移除操作。 +那么针对数组删除操作的问题,其实在[27. 移除元素](https://programmercarl.com/0027.移除元素.html)中就已经提到了使用双指针法进行移除操作。 -同样的道理在[151.翻转字符串里的单词](https://mp.weixin.qq.com/s/4j6vPFHkFAXnQhmSkq2X9g)中我们使用O(n)的时间复杂度,完成了删除冗余空格。 +同样的道理在[151.翻转字符串里的单词](https://programmercarl.com/0151.翻转字符串里的单词.html)中我们使用O(n)的时间复杂度,完成了删除冗余空格。 一些同学会使用for循环里调用库函数erase来移除元素,这其实是O(n^2)的操作,因为erase就是O(n)的操作,所以这也是典型的不知道库函数的时间复杂度,上来就用的案例了。 @@ -73,7 +73,7 @@ for (int i = 0; i < a.size(); i++) { 在反转上还可以在加一些玩法,其实考察的是对代码的掌控能力。 -[541. 反转字符串II](https://mp.weixin.qq.com/s/pzXt6PQ029y7bJ9YZB2mVQ)中,一些同学可能为了处理逻辑:每隔2k个字符的前k的字符,写了一堆逻辑代码或者再搞一个计数器,来统计2k,再统计前k个字符。 +[541. 反转字符串II](https://programmercarl.com/0541.反转字符串II.html)中,一些同学可能为了处理逻辑:每隔2k个字符的前k的字符,写了一堆逻辑代码或者再搞一个计数器,来统计2k,再统计前k个字符。 其实**当需要固定规律一段一段去处理字符串的时候,要想想在在for循环的表达式上做做文章**。 @@ -81,26 +81,26 @@ for (int i = 0; i < a.size(); i++) { 因为要找的也就是每2 * k 区间的起点,这样写程序会高效很多。 -在[151.翻转字符串里的单词](https://mp.weixin.qq.com/s/4j6vPFHkFAXnQhmSkq2X9g)中要求翻转字符串里的单词,这道题目可以说是综合考察了字符串的多种操作。是考察字符串的好题。 +在[151.翻转字符串里的单词](https://programmercarl.com/0151.翻转字符串里的单词.html)中要求翻转字符串里的单词,这道题目可以说是综合考察了字符串的多种操作。是考察字符串的好题。 这道题目通过 **先整体反转再局部反转**,实现了反转字符串里的单词。 后来发现反转字符串还有一个牛逼的用处,就是达到左旋的效果。 -在[字符串:反转个字符串还有这个用处?](https://mp.weixin.qq.com/s/Px_L-RfT2b_jXKcNmccPsw)中,我们通过**先局部反转再整体反转**达到了左旋的效果。 +在[字符串:反转个字符串还有这个用处?](https://programmercarl.com/剑指Offer58-II.左旋转字符串.html)中,我们通过**先局部反转再整体反转**达到了左旋的效果。 # KMP KMP的主要思想是**当出现字符串不匹配时,可以知道一部分之前已经匹配的文本内容,可以利用这些信息避免从头再去做匹配了。** -KMP的精髓所在就是前缀表,在[KMP精讲](https://mp.weixin.qq.com/s/MoRBHbS4hQXn7LcPdmHmIg)中提到了,什么是KMP,什么是前缀表,以及为什么要用前缀表。 +KMP的精髓所在就是前缀表,在[KMP精讲](https://programmercarl.com/0028.实现strStr.html)中提到了,什么是KMP,什么是前缀表,以及为什么要用前缀表。 前缀表:起始位置到下表i之前(包括i)的子串中,有多大长度的相同前缀后缀。 那么使用KMP可以解决两类经典问题: -1. 匹配问题:[28. 实现 strStr()](https://mp.weixin.qq.com/s/MoRBHbS4hQXn7LcPdmHmIg) -2. 重复子串问题:[459.重复的子字符串](https://mp.weixin.qq.com/s/32Pve4j8IWvdgxYEZdTeFg) +1. 匹配问题:[28. 实现 strStr()](https://programmercarl.com/0028.实现strStr.html) +2. 重复子串问题:[459.重复的子字符串](https://programmercarl.com/0459.重复的子字符串.html) 再一次强调了什么是前缀,什么是后缀,什么又是最长相等前后缀。 @@ -108,7 +108,7 @@ KMP的精髓所在就是前缀表,在[KMP精讲](https://mp.weixin.qq.com/s/Mo 后缀:指不包含第一个字符的所有以最后一个字符结尾的连续子串。 -然后**针对前缀表到底要不要减一,这其实是不同KMP实现的方式**,我们在[KMP精讲](https://mp.weixin.qq.com/s/MoRBHbS4hQXn7LcPdmHmIg)中针对之前两个问题,分别给出了两个不同版本的的KMP实现。 +然后**针对前缀表到底要不要减一,这其实是不同KMP实现的方式**,我们在[KMP精讲](https://programmercarl.com/0028.实现strStr.html)中针对之前两个问题,分别给出了两个不同版本的的KMP实现。 其中主要**理解j=next[x]这一步最为关键!** @@ -130,4 +130,4 @@ KMP算法是字符串查找最重要的算法,但彻底理解KMP并不容易 * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/数组总结篇.md b/problems/数组总结篇.md index 2c679493..7fafb94b 100644 --- a/problems/数组总结篇.md +++ b/problems/数组总结篇.md @@ -63,7 +63,7 @@ ## 二分法 -[数组:每次遇到二分法,都是一看就会,一写就废](https://mp.weixin.qq.com/s/fCf5QbPDtE6SSlZ1yh_q8Q) +[数组:每次遇到二分法,都是一看就会,一写就废](https://programmercarl.com/0704.二分查找.html) 这道题目呢,考察的数据的基本操作,思路很简单,但是在通过率在简单题里并不高,不要轻敌。 @@ -79,7 +79,7 @@ ## 双指针法 -* [数组:就移除个元素很难么?](https://mp.weixin.qq.com/s/wj0T-Xs88_FHJFwayElQlA) +* [数组:就移除个元素很难么?](https://programmercarl.com/0027.移除元素.html) 双指针法(快慢指针法):**通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。** @@ -95,7 +95,7 @@ ## 滑动窗口 -* [数组:滑动窗口拯救了你](https://mp.weixin.qq.com/s/UrZynlqi4QpyLlLhBPglyg) +* [数组:滑动窗口拯救了你](https://programmercarl.com/0209.长度最小的子数组.html) 本题介绍了数组操作中的另一个重要思想:滑动窗口。 @@ -111,7 +111,7 @@ ## 模拟行为 -* [数组:这个循环可以转懵很多人!](https://mp.weixin.qq.com/s/KTPhaeqxbMK9CxHUUgFDmg) +* [数组:这个循环可以转懵很多人!](https://programmercarl.com/0059.螺旋矩阵II.html) 模拟类的题目在数组中很常见,不涉及到什么算法,就是单纯的模拟,十分考察大家对代码的掌控能力。 @@ -149,4 +149,4 @@ Go: * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/数组理论基础.md b/problems/数组理论基础.md index b2837375..6d7b9f9a 100644 --- a/problems/数组理论基础.md +++ b/problems/数组理论基础.md @@ -53,7 +53,7 @@ 我们来做一个实验,C++测试代码如下: -```C++ +```CPP void test_arr() { int array[2][3] = { {0, 1, 2}, @@ -78,7 +78,7 @@ int main() { 注意地址为16进制,可以看出二维数组地址是连续一条线的。 -一些录友可能看不懂内存地址,我就简单介绍一下, 0x7ffee4065820 与 0x7ffee4065824 差了一个4,就是4个字节,因为这是一个int型的数组,所以两个相信数组元素地址差4个字节。 +一些录友可能看不懂内存地址,我就简单介绍一下, 0x7ffee4065820 与 0x7ffee4065824 差了一个4,就是4个字节,因为这是一个int型的数组,所以两个相邻数组元素地址差4个字节。 0x7ffee4065828 与 0x7ffee406582c 也是差了4个字节,在16进制里8 + 4 = c,c就是12。 @@ -124,4 +124,4 @@ public static void test_arr() { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/栈与队列总结.md b/problems/栈与队列总结.md index b3bb5c47..ffcd38a1 100644 --- a/problems/栈与队列总结.md +++ b/problems/栈与队列总结.md @@ -9,7 +9,7 @@ # 栈与队列的理论基础 -首先我们在[栈与队列:来看看栈和队列不为人知的一面](https://mp.weixin.qq.com/s/VZRjOccyE09aE-MgLbCMjQ)中讲解了栈和队列的理论基础。 +首先我们在[栈与队列:来看看栈和队列不为人知的一面](https://programmercarl.com/栈与队列理论基础.html)中讲解了栈和队列的理论基础。 里面提到了灵魂四问: @@ -33,9 +33,9 @@ 大家还是要多多重视起来! -了解了栈与队列基础之后,那么可以用[栈与队列:栈实现队列](https://mp.weixin.qq.com/s/P6tupDwRFi6Ay-L7DT4NVg) 和 [栈与队列:队列实现栈](https://mp.weixin.qq.com/s/yzn6ktUlL-vRG3-m5a8_Yw) 来练习一下栈与队列的基本操作。 +了解了栈与队列基础之后,那么可以用[栈与队列:栈实现队列](https://programmercarl.com/0232.用栈实现队列.html) 和 [栈与队列:队列实现栈](https://programmercarl.com/0225.用队列实现栈.html) 来练习一下栈与队列的基本操作。 -值得一提的是,用[栈与队列:用队列实现栈还有点别扭](https://mp.weixin.qq.com/s/yzn6ktUlL-vRG3-m5a8_Yw)中,其实只用一个队列就够了。 +值得一提的是,用[栈与队列:用队列实现栈还有点别扭](https://programmercarl.com/0225.用队列实现栈.html)中,其实只用一个队列就够了。 **一个队列在模拟栈弹出元素的时候只要将队列头部的元素(除了最后一个元素外) 重新添加到队列尾部,此时在去弹出元素就是栈的顺序了。** @@ -63,7 +63,7 @@ cd a/b/c/../../ ## 括号匹配问题 -在[栈与队列:系统中处处都是栈的应用](https://mp.weixin.qq.com/s/nLlmPMsDCIWSqAtr0jbrpQ)中我们讲解了括号匹配问题。 +在[栈与队列:系统中处处都是栈的应用](https://programmercarl.com/0020.有效的括号.html)中我们讲解了括号匹配问题。 **括号匹配是使用栈解决的经典问题。** @@ -79,23 +79,23 @@ cd a/b/c/../../ ## 字符串去重问题 -在[栈与队列:匹配问题都是栈的强项](https://mp.weixin.qq.com/s/eynAEbUbZoAWrk0ZlEugqg)中讲解了字符串去重问题。 +在[栈与队列:匹配问题都是栈的强项](https://programmercarl.com/1047.删除字符串中的所有相邻重复项.html)中讲解了字符串去重问题。 1047. 删除字符串中的所有相邻重复项 思路就是可以把字符串顺序放到一个栈中,然后如果相同的话 栈就弹出,这样最后栈里剩下的元素都是相邻不相同的元素了。 ## 逆波兰表达式问题 -在[栈与队列:有没有想过计算机是如何处理表达式的?](https://mp.weixin.qq.com/s/hneh2nnLT91rR8ms2fm_kw)中讲解了求逆波兰表达式。 +在[栈与队列:有没有想过计算机是如何处理表达式的?](https://programmercarl.com/0150.逆波兰表达式求值.html)中讲解了求逆波兰表达式。 -本题中每一个子表达式要得出一个结果,然后拿这个结果再进行运算,那么**这岂不就是一个相邻字符串消除的过程,和[栈与队列:匹配问题都是栈的强项](https://mp.weixin.qq.com/s/eynAEbUbZoAWrk0ZlEugqg)中的对对碰游戏是不是就非常像了。** +本题中每一个子表达式要得出一个结果,然后拿这个结果再进行运算,那么**这岂不就是一个相邻字符串消除的过程,和[栈与队列:匹配问题都是栈的强项](https://programmercarl.com/1047.删除字符串中的所有相邻重复项.html)中的对对碰游戏是不是就非常像了。** # 队列的经典题目 ## 滑动窗口最大值问题 -在[栈与队列:滑动窗口里求最大值引出一个重要数据结构](https://mp.weixin.qq.com/s/8c6l2bO74xyMjph09gQtpA)中讲解了一种数据结构:单调队列。 +在[栈与队列:滑动窗口里求最大值引出一个重要数据结构](https://programmercarl.com/0239.滑动窗口最大值.html)中讲解了一种数据结构:单调队列。 这道题目还是比较绕的,如果第一次遇到这种题目,需要反复琢磨琢磨 @@ -123,7 +123,7 @@ cd a/b/c/../../ ## 求前 K 个高频元素 -在[栈与队列:求前 K 个高频元素和队列有啥关系?](https://mp.weixin.qq.com/s/8hMwxoE_BQRbzCc7CA8rng)中讲解了求前 K 个高频元素。 +在[栈与队列:求前 K 个高频元素和队列有啥关系?](https://programmercarl.com/0347.前K个高频元素.html)中讲解了求前 K 个高频元素。 通过求前 K 个高频元素,引出另一种队列就是**优先级队列**。 @@ -181,4 +181,4 @@ Go: * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/栈与队列理论基础.md b/problems/栈与队列理论基础.md index db871a3c..c43ce0f5 100644 --- a/problems/栈与队列理论基础.md +++ b/problems/栈与队列理论基础.md @@ -94,4 +94,4 @@ std::queue> third; // 定义以list为底层容器的队列 * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/根据身高重建队列(vector原理讲解).md b/problems/根据身高重建队列(vector原理讲解).md index c548f6d8..dfc824fa 100644 --- a/problems/根据身高重建队列(vector原理讲解).md +++ b/problems/根据身高重建队列(vector原理讲解).md @@ -7,12 +7,12 @@

欢迎大家参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!

# 贪心算法:根据身高重建队列(续集) -在讲解[贪心算法:根据身高重建队列](https://mp.weixin.qq.com/s/-2TgZVdOwS-DvtbjjDEbfw)中,我们提到了使用vector(C++中的动态数组)来进行insert操作是费时的。 +在讲解[贪心算法:根据身高重建队列](https://programmercarl.com/0406.根据身高重建队列.html)中,我们提到了使用vector(C++中的动态数组)来进行insert操作是费时的。 但是在解释的过程中有不恰当的地方,所以来专门写一篇文章来详细说一说这个问题。 使用vector的代码如下: -```C++ +```CPP // 版本一,使用vector(动态数组) class Solution { public: @@ -38,7 +38,7 @@ public: 其直观上来看数组的insert操作是O(n)的,整体代码的时间复杂度是O(n^2)。 这么一分析好像和版本二链表实现的时间复杂度是一样的啊,为什么提交之后效率会差距这么大呢? -```C++ +```CPP // 版本二,使用list(链表) class Solution { public: @@ -99,15 +99,15 @@ for (int i = 0; i < vec.size(); i++) { **同时也注意此时capicity和size的变化,关键的地方我都标红了**。 -而在[贪心算法:根据身高重建队列](https://mp.weixin.qq.com/s/-2TgZVdOwS-DvtbjjDEbfw)中,我们使用vector来做insert的操作,此时大家可会发现,**虽然表面上复杂度是O(n^2),但是其底层都不知道额外做了多少次全量拷贝了,所以算上vector的底层拷贝,整体时间复杂度可以认为是O(n^2 + t * n)级别的,t是底层拷贝的次数**。 +而在[贪心算法:根据身高重建队列](https://programmercarl.com/0406.根据身高重建队列.html)中,我们使用vector来做insert的操作,此时大家可会发现,**虽然表面上复杂度是O(n^2),但是其底层都不知道额外做了多少次全量拷贝了,所以算上vector的底层拷贝,整体时间复杂度可以认为是O(n^2 + t * n)级别的,t是底层拷贝的次数**。 -那么是不是可以直接确定好vector的大小,不让它在动态扩容了,例如在[贪心算法:根据身高重建队列](https://mp.weixin.qq.com/s/-2TgZVdOwS-DvtbjjDEbfw)中已经给出了有people.size这么多的人,可以定义好一个固定大小的vector,这样我们就可以控制vector,不让它底层动态扩容。 +那么是不是可以直接确定好vector的大小,不让它在动态扩容了,例如在[贪心算法:根据身高重建队列](https://programmercarl.com/0406.根据身高重建队列.html)中已经给出了有people.size这么多的人,可以定义好一个固定大小的vector,这样我们就可以控制vector,不让它底层动态扩容。 这种方法需要自己模拟插入的操作,不仅没有直接调用insert接口那么方便,需要手动模拟插入操作,而且效率也不高! 手动模拟的过程其实不是很简单的,需要很多细节,我粗略写了一个版本,如下: -```C++ +```CPP // 版本三 // 使用vector,但不让它动态扩容 class Solution { @@ -147,7 +147,7 @@ public: 所以对于两种使用数组的方法一和方法三,也不好确定谁优,但一定都没有使用方法二链表的效率高! -一波分析之后,对于[贪心算法:根据身高重建队列](https://mp.weixin.qq.com/s/-2TgZVdOwS-DvtbjjDEbfw) ,大家就安心使用链表吧!别折腾了,哈哈,相当于我替大家折腾了一下。 +一波分析之后,对于[贪心算法:根据身高重建队列](https://programmercarl.com/0406.根据身高重建队列.html) ,大家就安心使用链表吧!别折腾了,哈哈,相当于我替大家折腾了一下。 ## 总结 @@ -180,4 +180,4 @@ Go: * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/知识星球精选/HR面注意事项.md b/problems/知识星球精选/HR面注意事项.md new file mode 100644 index 00000000..5e0e9d65 --- /dev/null +++ b/problems/知识星球精选/HR面注意事项.md @@ -0,0 +1,84 @@ + +# HR面注意事项 + +[知识星球](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)里已经有一些录友开始准备HR面。 + +![](https://code-thinking-1253855093.file.myqcloud.com/pics/20210807155107.png) + +甚至星球里已经有录友拿到百度提前批的offer + +![](https://code-thinking-1253855093.file.myqcloud.com/pics/20210808102821.png) + +看到一些录友面试这么顺利还是非常开心的,同时我也在知识星球里分享了HR面最容易遇到的 问题,已经应该如何回答。 + +相信公众号里不少录友也会遇到同样的问题,所以就给大家再分享一下。 + +HR面的话,如果不犯重大问题,一般不会刷人。 + +但有些同学也会犯重大问题,这样丢了offer,可就太可惜了。 + +**HR的职责是选择符合公司文化价值观的员工**,那么说到文化价值观,大家可能感觉 这虚无缥缈的,怎么能证明自己符合文化价值观呢。 + +其实HR主要从如下几个问题来考察,大家只要把这几个问题想清楚,就差不多了。 + + +## 为什么选择我们公司? + +这个大家一定要有所准备,不能被问到了之后一脸茫然,然后说 “就是想找个工作”,那基本就没戏了 + +要从**技术氛围,职业发展,公司潜力**等等方面来说自己为什么选择这家公司。 + +要表现自己如何如何看好这家公司,期待和这家公司一起成长。 + +## 有没有职业规划? + +一般应届生都没有明确的职业规划,不过当HR问起来的时候,不要说 自己想工作几年想做项目经理,工作几年想做产品经理,甚至想当leader带团队,这样会被HR认为 职业规划不清晰,尽量从技术的角度规划自己。 + +这个策略同样适用于社招。 + +虽然大部分程序员的终极目标都想做leader,或者做管理,(极少数想要写一辈子代码的大牛除外,不过国内环境对这种大牛并不友好) + +大家都想做战略做规划,那比写代码有意思,有成就感多了。 + +但不要说出来,一定要围绕技术这块来规划,根据你的岗位,**一年 技术达到什么程度,三年在某个技术领域有深入研究,五年成为技术专家之类的**等等。 + +这块其实我和HR朋友还真的讨论过,我说:就大厂,百分之九十五以上的程序员都不想写代码,以后指定是想转产品或者升leader做项目管理, 但你为什么还要问这么 无聊的问题呢。 + +HR朋友的回答是:你不说真相,我会认为你可能对技术有追求,但如果你说出真相,那么明确你对技术没有追求。 + +所以,即使你有其他想法,在职业规划HR面的时候,**也要仅仅围绕技术,树立自己要深耕技术的形象**。 + +## 是否接受加班? + +虽然大家都不喜欢加班,但是这个问题,我还是建议如果手头没有offer的话,大家尽量选择接受了吧 + +就说:自己可以介绍 XX程度的加班。 + +如果确实身体不适,那就直接拒绝,毕竟健康是第一位。 + +## 坚持最长的一件事情是什么? + +这个问题,大家最好之前就想好,有一些同学可能印象里自己没有坚持很长的事情,也没有好好想过这个问题,在HR面的时候被问到的时候,一脸茫然,不知道该说啥。 + +憋了半天说出一个不痛不痒的事情。这就是一个减分项了! + +问这个问题主要是考察大家的韧性,会不会做一个件事遇到困难就中途放弃了。 + +星球里的录友可以说自己坚持每日打卡总结,这也是可以的,毕竟这种需要自己克制才能做到的事情。 + +## 如果校招,直接会问:期望薪资XXX是否接受? + +这里大家如果感觉自己表现的很好 给面试官留下的很好的印象,**可以在这里争取 special offer** + +这都是可以的,但是要真的对自己信心十足。 + +## 如果社招,则会了解前一家目前公司薪水多少 ? + +**这里大家切记不要虚报工资,因为入职前是要查流水的,也就是要把上一件公司的银行流水截图报上来,这个是比较严肃的问题。** + + +好了,说了这么多,希望对大家有所帮助。 + +--------------- + +加入「代码随想录」知识星球,[点击这里](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) diff --git a/problems/知识星球精选/不一样的七夕.md b/problems/知识星球精选/不一样的七夕.md new file mode 100644 index 00000000..37e1c2da --- /dev/null +++ b/problems/知识星球精选/不一样的七夕.md @@ -0,0 +1,69 @@ + +# 特殊的七夕 + +昨天在[知识星球](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)发了一个状态: + +![](https://code-thinking-1253855093.file.myqcloud.com/pics/20210815084126.png) + +我还以为 过节嘛,录友们应该不会打卡了,但还依旧阻挡不辽录友打卡学习的步伐,来瞅瞅: + + +![](https://code-thinking-1253855093.file.myqcloud.com/pics/20210815100028.png) + +![](https://code-thinking-1253855093.file.myqcloud.com/pics/20210815100109.png) + +![](https://code-thinking-1253855093.file.myqcloud.com/pics/20210815100212.png) + +![](https://code-thinking-1253855093.file.myqcloud.com/pics/20210815095902.png) + + +当然了,我截图了一小部分,星球里每天的信息量都非常大。 + +如果说一个人坚持每天总结记笔记,**其实是很容易放弃的,今天不记,明天不急,后天不整理感觉也无所谓**。 + +这样时间就一点一点的被浪费掉了。 + +**因为我们学过的东西都会忘,不及时整理,时间就不能沉淀下来**,这就造成了一边学,一边忘,最后感觉自己好像也没学啥的感觉! + +所以每天记笔记,及时整理,是非常重要的。 + +这个习惯其实靠自己约束很容易放弃,但一群人一起坚持,就会不一样,大家相互监督,每天不总结记录点什么就会感觉少了点啥。 + +而且,大家每天的总结,我都会看,有问题 我都及时指出来,这样也防止自己学着学着学跑偏了。 + +昨天我也在[知识星球](https://mp.weixin.qq.com/s/X1XCH-KevURi3LnakJsCkA)回答了几位录友的问题,其中有两个问题 还是比较典型的,估计公众号里的录友也会遇到这样的疑惑。 + +所以也给大家说一说: + +## 准备的太晚了,想放弃秋招,直接准备春招 + +![](https://code-thinking-1253855093.file.myqcloud.com/pics/20210815091442.png) + +很多准备今年秋招的录友感觉自己还没准备好,想先找实习,或者 自己在学习学习,然后直接春招。 + +其实不到万不得已,我还是建议要冲刺秋招。 + +如果说,因为没准备好,提前批放弃还是可以的,但秋招不能也直接放弃了! + +秋招没找到好工作,一般11月份左右,一些大厂还会有补招,腾讯就经常补招,实在不行再准备春招,春招可能国企单位会多一些。 + +**而且面试也很看缘分,永远没有真正准备好的时候,知识一直都学不完**,所以 把秋招当做最后的机会,就算秋招没找到,也可以在冲春招,而不是 直接放弃秋招。 + +还有就心态方面来说,直接放弃秋招,等 今年 10月份,身边同学都找到工作了,天天吃喝玩乐,见面打招呼就问:你去哪了,你签哪了。那时候 自己心里压力会非常大,甚至会影响 春招找工作。 + + +## HR/面试官问你手上有没有其他offer,如何回答 + +![](https://code-thinking-1253855093.file.myqcloud.com/pics/20210815091819.png) + +这个问题,无论是校招还是社招,大家都会遇到。 + +如果大家手上有其他更好的offer,或者说同等水平公司的offer,可以说一说,这样凸显出自己的优势,即:你们不要我,有更好的公司要我, 这样给面试官或者HR点压力,可以争取到更高的薪酬。 + +如果没有更好的offer,可以说没有,然后解释:只认准贵公司,从技术氛围,职业发展,公司前景,来说贵司多么多么的好,我多么渴望和贵司一起成长之类的。**总之,就是捧起来,显得自己很专一**。 + +都是套路,哈哈哈哈。 + +**好了,说了这么多,希望对大家有所帮助**。 + + diff --git a/problems/知识星球精选/不少录友想放弃秋招.md b/problems/知识星球精选/不少录友想放弃秋招.md new file mode 100644 index 00000000..4807dd97 --- /dev/null +++ b/problems/知识星球精选/不少录友想放弃秋招.md @@ -0,0 +1,76 @@ +# 不少录友想放弃秋招了..... + +马上就要九月份了,互联网大厂的秋招的序幕早已拉开。 + +发现[知识星球](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)里有一部分录友想放弃秋招,直接准备明年的春招,估计关注公众号的录友也有不少有这种想法的。 + +![](https://code-thinking-1253855093.file.myqcloud.com/pics/20210813103515.png) + +一般有这种想法的录友都是 **之前没有准备好,或者是总感觉时间赶趟赶趟,然后突然间 发现时间不赶趟了。。。** + +也有一些感觉自己没有实习经历,简历上也没什么好写,想下半年去找一找实习,不去秋招,然后直接准备春招。 + +**对于这种情况,我的建议依然要冲刺秋招!** + +# 把秋招当做最后的机会 + +**等到春招的时候,可以选岗位已经很少了,各个大厂几乎都招满了**。 + +而且就算秋招没找到好工作,一般 11月份左右,一些大厂还会有补招,腾讯就经常补招。 + +补招的情况是就是腾讯发出了 offer,有的候选人 选择违约,不来了,那么腾讯就需要补招,把人数凑齐。 + +可能有录友想,谁居然连腾讯的offer都拒绝呢? + +其实挺多的,例如:有其他大厂的核心部门offer,父母给安排了 国企、央企 的核心岗位,或者有的选择 读博了之类的,导师毕业能给安排留校 或者去其他高校任教。 + +所以几乎每年,腾讯都要补招,其他大厂也会有补招,一般是11月份,所以就算秋招没拿到大厂offer,依然有机会! + +话再说回来,面试其实也很看缘分,**永远没有真正准备好的时候,知识一直都学不完**。 + +所以 **把秋招当做最后的机会,就算秋招没找到,也可以在冲春招,而不是 直接放弃秋招**。 + + +# 放弃秋招,对心态的影响 + +如果直接放弃秋招,等 今年 10月份,身边同学都找到工作了,那时候的场面就是歌舞升平,大家天天吃喝玩乐。 + +见面打会招呼就问:你去哪了,你签哪了? + +那时候如果自己还没有面试,还在准备面试,此时自己心里阴影面积有多大,甚至会影响春招找工作。 + +# 面试要趁早准备 + +每年这时候,都会有同学后悔,我怎么就没早点准备,就感觉时间不够用。 + +所以也给明年找工作的录友们(2023届)提一个醒,现在就要系统性的准备起来了,因为明年春季实习招聘 是一个很好的进大厂的机会,剩下的时间也不是很多了。 + +来看看[知识星球](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)里,一位准大三的录友准备的情况 + +![](https://code-thinking-1253855093.file.myqcloud.com/pics/星球大三.jpg) + +再来看看一位准大二的录友准备情况 + +![](https://code-thinking-1253855093.file.myqcloud.com/pics/星球大二.jpg) + +**我已经预感到 这两位 等到秋招的时候就是稳稳的offer收割机**。 + +[知识星球](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)还有很多已经开始提前准备,或者看了 星球发文状态就开始着手准备的录友了。 + + +所以 **所谓的大牛,都是 很早就规划自己要学的东西,很早就开始向过来人请教应该如何找工作,很早就知道自己应该学哪些技术,看哪些书, 这样等到找工作的时候,才是剑锋出鞘的时候**。 + +我们远远还没有到拼智商的程度。 + +这里 也是给公众号里的录友们提一个醒,估计还有不少录友依然在感觉时间还赶趟,但 未来的卷王已经在路上了 哈哈哈。 + +**不过话说回来,现在互联网求职确实卷!** + +但这是社会问题,我们改变不了。 + +**卷的核心是,好的东西少,但要想的人多!** + +**如果你也想要,就要提前准备,提前规划,提前努力!** + +也希望录友们都能找到一个自己心仪的工作,加油💪。 + diff --git a/problems/知识星球精选/专业技能可以这么写.md b/problems/知识星球精选/专业技能可以这么写.md new file mode 100644 index 00000000..51522b9f --- /dev/null +++ b/problems/知识星球精选/专业技能可以这么写.md @@ -0,0 +1,62 @@ +# 你简历里的「专业技能」写的够专业么? + + +其实我几乎每天都要看一些简历,有一些写的不错的,我都会在[知识星球](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)里分享一下。 +![](https://code-thinking-1253855093.cos.ap-guangzhou.myqcloud.com/pics/20210626172902.png) + +这次呢,我再专门说一说简历中的【专业技能】这一栏应该怎么写。 + +很多同学【专业技能】这块写的很少,其实不是掌握的少,而是没有表达出来。 + +例如有的同学这么写: + + +![](https://code-thinking-1253855093.cos.ap-guangzhou.myqcloud.com/pics/20210626173915.png) + +--------------------- + +![](https://code-thinking-1253855093.cos.ap-guangzhou.myqcloud.com/pics/20210626173940.png) + +-------------------- + +![](https://code-thinking-1253855093.cos.ap-guangzhou.myqcloud.com/pics/20210626174018.png) + +------------------- + +![](https://code-thinking-1253855093.cos.ap-guangzhou.myqcloud.com/pics/20210626174809.png) + + +这些【专业技能】都写的很少,其实是可以在丰富一些的。 + +我来给大家拓展一下、 + + +* 熟练C++,(列举C++的若干知识点),了解 Java,python,go (适当补充对这些语言的理解) +* 熟悉常见设计模式(例句一些设计模式) +* 熟悉linux操作系统vim开发环境,(列举网络编程相关知识,例如epoll,socket等等) +* 熟悉网络,(列举网络协议相关考点,tcp/ip,http, https, 三次,四次握手,流量控制等等) +* 数量掌握数据结构与算法(列举常用算法,最好搞透一个算法,说对该算法有独到见解) +* 数量使用Git,等版本控制 +* 以上为公共写法,下面可以在补充自己的其他领域的内容 + + +针对以上这个模板, 再来补充相关内容: + +1. 熟悉C/C++,熟练使用C的指针应用及内存管理,C++的封装继承多态,STL常用容器,C++11常用特性(智能指针等) ,了解 Python,Gtest等。 +2. 熟悉常用设计模式(单例模式,工厂模式等) +3. 熟悉Linux下vim开发环境,了解网络编程,IO多路复用,epoll等等。 +4. 熟悉OSI五层网络模型,熟悉TCP/IP,UDP,HTTP/HTTPS,DNS等网络协议,熟悉TCP三次握手,四次挥手,流量控制,拥塞控制等手段。 +5. 熟悉常用的数据结构(链表、栈、队列、二叉树等),熟练使用排序,贪心,动态规划等算法。 +6. 熟悉使用Git,vscode工具使用。 + +但需要注意的是,这里写的点,自己一定要熟练掌握,因为简历上写的,面试官一定会问。 + +这样有一个好处,就是 **缩小面试官的问题范围**, 只要简历上写的,你都准备好了,那么简历上的知识点面试官一定会问,这样你就掌握了主动权。 + +举一个例子,如果简历上直写:熟悉C++。其他都没介绍,那么面试官指定围绕C++漫天遍野的问起来了,你也猜不透面试官想问啥。 + +如果简历写熟悉C/C++,熟练使用C的指针应用及内存管理,C++的封装继承多态,STL常用容器,C++11常用特性(智能指针等)。那么面试官基本上只会问,内存管理,多态,STL和C++11的一些特性, **这样你就把面试官的问题都圈在可控范围内**,从而掌握主动权! + +这一点很重要,希望大家要有这个思路,去写自己的简历。 + + diff --git a/problems/知识星球精选/关于实习大家的疑问.md b/problems/知识星球精选/关于实习大家的疑问.md new file mode 100644 index 00000000..118d8bf1 --- /dev/null +++ b/problems/知识星球精选/关于实习大家的疑问.md @@ -0,0 +1,83 @@ + +# 关于实习,大家可能有点迷茫! + +我在[知识星球](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)里回答了很多关于实习相关的问题,其实很多录友可能都有这么样的疑问,主要关于实习的问题有如下四点: + +* 秋招什么时候开始准备 +* 要不要准备实习 +* 实习是不是重要? +* 什么时候找实习最有帮助 +* 如何选择实习的offer + +下面都分别来说一说: + +## 秋招什么时候开始准备 + +![实习二](https://img-blog.csdnimg.cn/20210502145513517.png) + +**准备工作指定是越早越好的**。 + +准备的越早,在8,9月份就越淡定,每年校招很多同学都会对于准备找工作总感觉赶趟赶趟,结果到了8月份开始慌得一笔了。 + +正常校招8月份就开始提前批(各大企业提前抢优秀毕业生)了,有的企业甚至7月份就开始。 + +基本到了7月份可能就没有整块的时间静下心来准备找工作,那时候已经铺天盖地的各种招聘信息,甚至一些同学已经拿到了offer了。 + +所以准备找工作的内容以7月为终结点比较稳妥,七月份之后以复习为主,有个整体框架,定时复习补充补充,多和同学交流面试经验。 + +## 要不要准备实习 + +有的同学是3,4月份准备面实习,然后7、8月份就去企业实习了,**实习有利有弊**。 + +如果可以去一线互联网公司实习,而且岗位也合适,那当然要去,如果去不了也别难过,因为实习生大部分都是打杂,干的活甚至都写不到简历上。 + +也有一小部分的实习生能够真正做到项目。 + +如果没有去实习,就把基础好好补充一下,**基础打好,毕竟对于应届生基础最为重要**, 编程语言、数据结构算法、计算机网络、操作系统、数据库这些都是基础,规划好时间把这些内容学好。 + +**对于应届生来说,项目经历是锦上添花,不是决定性的**。 + +有实习经历(前提是实习工作内容是真正的做项目,不是打杂),那么面试的时候面试官可能对项目经历感兴趣,问基础的内容就比较少, 如果没有实习经历,就把基础内容巩固一下,校招是一样的。 + +## 实习是不是非常重要? + +![实习一](https://img-blog.csdnimg.cn/20210502114600147.png) + +**大厂的实习经历对秋招还是很有帮助的**。 + + +但也不绝对,实习的话会耽误三个月左右,如果没有转正,而且一直在打杂的话,再去找秋招工作,**那时候一些基础的内容就都忘光了,反而秋招很被动**。 + +现在当然也可以按照准备找实习的状态来要求自己,给自己点压力,毕竟找实习准备的知识和秋招准备的知识差不多。 + +也可以提前经历一下面试,培养一下面试感觉,数据库方面知识你比较短缺,可以通过大量看这方面的面经迅速补充一下(秋招之前还可以系统看一看)。 + +如果拿到了大厂的实习offer,就去吧,实习的时候心里要有个秤,如果工作是打杂,就要尽快自己抽时间看基础准备秋招。 + +**另外需要注意的是,有些公司投递过简历面试没通过是有记录的,所以投递要当心,不要感觉投简历没有成本**,我知道的例如阿里,你每次投简历都有记录,如果实习面试都挂了,秋招的时候面试官也会看当时实习面试的记录(会考虑当时实习面试的结果)。 + +## 什么时候找实习最有帮助 + +![在这里插入图片描述](https://img-blog.csdnimg.cn/20210502151249354.png) + +6月份那时候基本不招实习生了,找的话也是日常实习(没有转正,实习时间是比较长的,要六个月),如果不是暑期实习就直接准备秋招吧。 + +**只有应届的暑期实习才有转正的机会,因为企业这样安排也是为了提前发现优秀毕业生!** + +例如:今年暑期实习,只招明年毕业的应届生。 + + +## 如何选择实习的offer + +![在这里插入图片描述](https://img-blog.csdnimg.cn/20210502152023574.png) + +如果目标应该是C++后端开发,那客户端实习offer可以选择别去了。 或者 选一个实习时间最短的offer先去着,例如两个月之类的,这样既能体现一下工作流程,也别耽误太多时间(毕竟客户端开发不是你的目标)。 + +**实习也不是必要的,一要看实习的岗位,是不是你想要的工作,二是实习的内容是不是打杂**,一些实习岗位其实是在浪费时间,如果转正不了的话,秋招就特别被动了,耽误了复习基础的时间。 + +还有就是**秋招的时候,一定要找小公司先练手,然后在面大公司**。 + + +以上基本覆盖了大家对实习的各种疑惑,不过现在已经到了5月份,实习面试基本结束了,如果没有拿到实习offer,大家安心准备秋招吧,依然可以冲刺大厂! + + diff --git a/problems/知识星球精选/关于提前批的一些建议.md b/problems/知识星球精选/关于提前批的一些建议.md new file mode 100644 index 00000000..fcf41a7e --- /dev/null +++ b/problems/知识星球精选/关于提前批的一些建议.md @@ -0,0 +1,68 @@ + +# 秋招和提前批都越来越提前了.... + +正在准备秋招的录友,可能都感受到了,现在的秋招越来越提前了.... + +以前提前批,都是 8月份,8月份中序左右,而不少大厂现在就已经提前批了。 + +不少录友在 公众号留言,和[知识星球](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)里,表示提前批来的还是有点快。 + +![](https://code-thinking-1253855093.cos.ap-guangzhou.myqcloud.com/pics/20210618162214.png) + +还没有把刷题攻略刷完的录友,要尽快刷完,至少先刷一遍,了解相关题型。 + +星球里,也有一些录友感受到了提前批的突如其来。 + +![](https://code-thinking-1253855093.cos.ap-guangzhou.myqcloud.com/pics/20210619190111.png) + +目前已经开始的提前批有 vivo, tp-link,京东,百度..... + +![](https://code-thinking-1253855093.cos.ap-guangzhou.myqcloud.com/pics/20210619190022.png) + + +有的录友已经明显紧张起来了。 + +![](https://code-thinking-1253855093.cos.ap-guangzhou.myqcloud.com/pics/20210619190354.png) + +其实这才刚刚开始,等 7月份 以后,又是铺天盖地的信息,大家更没心情静下心来看书了。 + + +# 关于提前批的一点小建议 + +**首先我们要知道为什么有提前批?** + +提前批不是大厂们呆着没事闲着多给我们一次面试机会,**而是提前抢优秀毕业生**,这一点大家一定要明确。 + + +了解了为什么有提前批,这样我们就有正确的心态面对它了。 + +如果没有准备好,或者自己定位因为不是 “优秀毕业生”,先不要碰提前提。 + +当然可以先面一些自己不想去的公司的提前批,用来练手。 + +至于对于自己心仪的公司,如果盲目参加提前批,首先会打乱自己的复习计划,和心态,然后就是提前批挂了后台都是有记录的。 + +只要是大厂的内部信息化做的比较完善,提前批挂了,是一定会记录的。 + +**那么提前批有没有影响呢?** + +很多招聘宣传的时候说,提前批挂了对秋招没影响,确实在一定程度上没影响,因为提前批挂了,依然可以投递秋招。 + +然后秋招面试的时候,面试官在不在意你的提前批成绩,就是另一回事了。 + +我之前内推了一些录友面试腾讯微信支付的部门,面试官和我很熟悉,我最近还和他吃了饭,聊一聊我内推的同学,他说看后台记录有些同学都投过好几次了,他看了之前面试结果的评价之后,就直接pass了。 + +所以大家可能要想好一个回答,就是面试官可能问:你的提前批为什么挂了? + +而且提前批挂了,都是有当时面试官评语的,如果7月份提前批面试,面试官评价:这位候选人基础不行。 + +秋招的时候,面试官也不会相信,一两个月能把基础补上来了。 即使你的基础其实没问题,只不过恰巧面试中的几个问题没答好而已。 + + +对于技术能力确实强的同学,我建议全力以赴准备提前批面试,因为提前批要求就比较高,很容易谈sp offer。而且现在就拿到了大厂offer,比找实习还香。 + +如果没准备好的同学,建议不要让提前批打乱阵脚,有计划的巩固基础,准备秋招。或者先拿自己不想去的公司的提前批练手。 + + +好了,说了这么多,希望对录友们有所帮助,加油💪 + diff --git a/problems/知识星球精选/写简历的一些问题.md b/problems/知识星球精选/写简历的一些问题.md new file mode 100644 index 00000000..513f86ba --- /dev/null +++ b/problems/知识星球精选/写简历的一些问题.md @@ -0,0 +1,95 @@ +# 程序员应该这么写简历! + +自运营[知识星球](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)以来,我已经给星球里的录友们看了 一百多份简历,并准对大家简历上的问题都给出了对应的详细建议。 + +社招,校招,实习的都有,其实大家的简历看多了,发现有很多共性的问题,这里就和大家分享一下。 + +我的简历模板也分享出来了,大家在「代码随想录」后台回复:简历模板,就可以获取! + +# 简历布局 + +不少录友的简历布局就很不合理, 有的把专业技能放在最下面了,有的甚至把教育经历放下面了,建议简历布局的顺序是这样: + +* 教育工作经历 +* 专业技能 +* 项目经验 +* 荣誉奖项 +* 个人简述 + +# 教育工作经历 + +一些录友可能本科学历不是很好,然后 简历上直接不写自己的本科学校。 + +其实教育经历是最基本的,你不写 面试官也一定会问,问出来 那么感觉更不好,所以关于教育经历,大家是一定要写的。 + +写本科以后教育经历的就行了,一些录友可能是 高中就读了一些特别牛逼的高中,然后把高中也写出来了,哈哈哈,高中经历真的就不用写了。 + +还有一些社招的录友,研究生和本科之间空了几年,这几年 一定要说清楚做了些什么,甚至是“编一下”,因为这个面试官也会问的。 + +# 专业技能 + +一些录友简历上没有「专业技能」这一栏,或者写的很短。 + +可能是不知道该写啥,甚至就不写了。 + +通常「专业技能」是在 「教育工作经历」之后的,我这里给出一个模板,大家按照这个格式来写「专业技能」就可以。 + +1. 熟练使用 C++,掌握Go,了解 Java、Python、PHP 等编程语言 +2. 熟练使用 linux 下 vim、Git 开发环境 +3. 了解 Linux 下网络编程、TCP/IP 协议 +4. 掌握基础数据结构和算法的基本原理 +5. 英语六级:XXX + + +一些录友会列举自己主修的课程,列了一堆,其实凑篇幅 我是理解的,就是感觉简历太单薄的,列课程来凑。 + +但大家凑篇幅 尽力在「专业技能」和「项目经验」上凑篇幅,如果把 自己主修可能都列出来,会让面试官感觉没有什么干货。(有的同学甚至靠留白才凑篇幅,这就更不要了) + +当然应届生如果有一些课程自己成绩确实很好,可以和「教育经历」写在一起,简单并行列举一下就可以了。 + +# 项目经验 + +很多录友写项目经验就是流水账,这是什么项目,自己完成了功能1,2,3,4。堆了很多字。 + +要知道面试官是不了解你的项目的,面试也只有 一个小时左右的时间,如果堆了很多文字 面试官也懒得去读。 + +面试官最在意的是什么呢? + +**项目中有哪些技术难点,以及 你是如何克服的**。 + +这是面试官最关心的,也是最能体现出候选人技术深度的问题。 + +所以大家在描述项目经验的时候,一定要时刻想着,这个项目的难点究竟是什么,要反复问自己这个问题。 + +可能有的同学说了,我这项目本来就没有难点啊,就是1,2,3,4功能,然后 遇到不会的,百度搜一下,差不多就这样了。 + +**项目没有难点,也要自己“造难点”**。 因为这个问题是面试官必问的! + +所以一定要准备好。 + +还有不少录友的项目经历都写了 web server,使用线程池 + 非阻塞 socket + epoll(ET 和 LT) + 事件处理 (Reactor 和模拟 Proactor) 等等。 + +这个项目可能是很多准备后台开发的同学 首选的 项目。 + +这种自己搞的小项目,**最好把你的代码上传的github上,然后在简历中贴出github地址**,面试官一定会看的。 + +如果看你的代码写的确实不错,那指定是加分项。比简历上写的天花乱坠都强! + +还有的同学项目经历特别多,写了5,6个项目,每个项目都是概述了一下自己做了XXX。 + +其实面试官,基本就会和你深入聊 2个的项目左右,列举这么多项目没有用的,关键这些项目一看也是技术含量不大。 + +**所以不用单纯堆项目个数。项目经历 两个足够,把两个项目搞深搞透** + + +# 校园经历 + +一些录友会把自己学校工作列出一大堆,例如各种学生会啊,创新部门啊之类的。甚至有的会把自己的减肥经历也列举出来。 + +如果面技术岗位,这一块其实不是面试官关心的,可以在 最后一栏「个人简述」,简单一两句概括一下自己的学生会经历就好,表明自己沟通能力没问题。 + +关于标明自己有毅力,有恒心,不怕吃苦等等,都简单一句概括。 + + +好了,关于简历的问题,我就先分享这些,估计应该击中了不少录友的痛点了。 + diff --git a/problems/知识星球精选/刷力扣用不用库函数.md b/problems/知识星球精选/刷力扣用不用库函数.md new file mode 100644 index 00000000..1a613b8d --- /dev/null +++ b/problems/知识星球精选/刷力扣用不用库函数.md @@ -0,0 +1,29 @@ +# 究竟什么时候用库函数,什么时候要自己实现 + +在[知识星球](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)里有录友问我,刷题究竟要不要用库函数? 刷题的时候总是禁不住库函数的诱惑,如果都不用库函数一些题目做起来还很麻烦。 + +估计不少录友都有这个困惑,我来说一说对于库函数的使用。 + +一些同学可能比较喜欢看力扣上直接调用库函数的评论和题解,**其实我感觉娱乐一下还是可以的,但千万别当真,别沉迷!** + +例如:[字符串:151. 翻转字符串里的单词](https://mp.weixin.qq.com/s/X3qpi2v5RSp08mO-W5Vicw)这道题目本身是综合考察同学们对字符串的处理能力,如果 split + reverse的话,那就失去了题目的意义了。 + +有的同学可能不屑于实现这么简单的功能,直接调库函数完事,把字符串分成一个个单词,一想就是那么一回事,多简单。 + +相信我,很多面试题都是一想很简单,实现起来一堆问题。 所以刷力扣本来就是为面试,也为了提高自己的代码能力,扎实一点没坏处。 + +**那么平时写算法题目就全都不用库函数了么?** + +当然也不是,这里我给大家提供一个标准。 + +**如果题目关键的部分直接用库函数就可以解决,建议不要使用库函数**。 + +**如果库函数仅仅是 解题过程中的一小部分,并且你已经很清楚这个库函数的内部实现原理的话,那么直接用库函数。** + +使用库函数最大的忌讳就是不知道这个库函数怎么实现的,也不知道其时间复杂度,上来就用,这样写出来的算法,时间复杂度自己都掌握不好的。 + +例如for循环里套一个字符串的insert,erase之类的操作,你说时间复杂度是多少呢,很明显是O(n^2)的时间复杂度了。 + +在刷题的时候本着我说的标准来使用库函数,详细对大家回有所帮助! + + diff --git a/problems/知识星球精选/刷题攻略要刷两遍.md b/problems/知识星球精选/刷题攻略要刷两遍.md new file mode 100644 index 00000000..53907ee8 --- /dev/null +++ b/problems/知识星球精选/刷题攻略要刷两遍.md @@ -0,0 +1,62 @@ +# 代码随想录上的题目最好刷两遍以上 + +今天秋招可能要提前很多,往年9月份开始秋招,今天可能9月份就已经结束了,所以 正在准备秋招的录友,还是要抓紧时间了。。 + +星球里已经有录友的给出了关于秋招提前的信息 + +![](https://code-thinking-1253855093.file.myqcloud.com/pics/20210801104138.png) + +那么在正式秋招之前,大家在准备算法,代码随想录上的题目 应该刷几篇呢? + +**至少刷两遍,只刷一遍是不够的**。 + +只刷一遍基本就会忘,而且关键方法论理解的也不到位,对递归三部曲,回溯三部曲,动规五部曲,只掌握了简单招式,而没有理解真正精髓。 + +拿到最简单的反转链表来说,只做一遍,下次面试出现基本还是做不出来。 + +这也是星球里 录友们的 多么痛的领悟! + +![](https://code-thinking-1253855093.file.myqcloud.com/pics/20210731183247.png) + +**等大家刷第二遍的时候,才能找到触类旁通的感觉!** + +第三遍基本就得心应手了。 + +在[「代码随想录」知识星球](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)中,我都是强调大家要至少刷两遍,有时间的话刷三遍, + +可以看看星球里录友们的打卡: + +![](https://code-thinking-1253855093.cos.ap-guangzhou.myqcloud.com/pics/20210701122522.png) + +![](https://code-thinking-1253855093.cos.ap-guangzhou.myqcloud.com/pics/20210701122422.png) + +![](https://code-thinking-1253855093.cos.ap-guangzhou.myqcloud.com/pics/20210701122313.png) + +有的录友已经开始三刷: + +![](https://code-thinking-1253855093.file.myqcloud.com/pics/20210727234031.png) + + +我为什么鼓励大家有时间的话,多刷几遍呢,首先这里的题目都是经典题目,而且在面试汇总也是频频出现, + +下面也是星球里的录友总结的面经: + +![](https://code-thinking-1253855093.cos.ap-guangzhou.myqcloud.com/pics/20210701121136.png) + +![](https://code-thinking-1253855093.cos.ap-guangzhou.myqcloud.com/pics/20210723125816.png) + + +## 那么已有的题目刷完了,可以刷些什么呢? + +我在Github上也做了一些题目的补充,在[上榜之后,都有哪些变化?](https://mp.weixin.qq.com/s/VJBV0qSBthjnbbmW-lctLA)说到了。 + +对于面试来说除了「代码随想录」上的题目,再了解一下:排序系列,简单图论(深搜,广搜,最小生成树,最短路径等),高级数据结构:并查集,字典树(了解一下),之后就差不多了。随便在leetcode找一些题目保持手感,题量至少300+,会稳一点。 + +关于深搜和广搜,其实深度优先搜索,我在二叉树的专题中讲解递归遍历,和回溯算法中 都讲了。 + +广度优先搜索,在二叉树树的层序遍历也讲了。 + +而图论中主要是在邻接表上进行的深搜和广搜。 + +面试中还是很少会考察图论,因为图论的代码量往往比较大,不适合在面试中考察,**面试中出现题目概率最大的是二叉树,回溯算法和动态规划!** + diff --git a/problems/知识星球精选/博士转行计算机.md b/problems/知识星球精选/博士转行计算机.md new file mode 100644 index 00000000..cb73804e --- /dev/null +++ b/problems/知识星球精选/博士转行计算机.md @@ -0,0 +1,48 @@ +# 本硕非计算机博士,如果找计算机相关工作 + +在[知识星球](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)里,有一位博士录友,本硕都不是计算机,博士转的计算机,问了这样一个问题 + +![](https://code-thinking-1253855093.file.myqcloud.com/pics/20210903213924.png) + +一下是我的回答,在这里分享给大家。 + +我的一些研究生同学,都在读博,有的毕业有的还没毕业,平时和他们聊,对博士就业也是有一定的了解,这里说一说我的建议。 + +对于博士,强烈建议能去高校就去高校,这样最大程度发挥出了博士的好处,赚国家科研经费的钱还是香的。 + +虽然现在对青年研究者并不友好,基本经济大头都被实验室boss拿走了。 + +但高校壁垒高,外界再优秀的人才,也进不去和你竞争,所以可以小范围的卷。出来的话,就是和整个社会AI领域甚至和研发的同学一起卷。 + +**在企业 是谁有能力谁就上, 在高校,至少你得有博士学位才能上! 这就是很高的门槛了**。 + + +而且能给博士提供岗位的企业少之又少,所以博士的就业面反而窄了。 + +可能有同学想,薪酬要的低一点还不行么,其实博士毕业对薪资还是有要求的,如果薪资和本科,硕士应届生一样的话,自己也接受不了。 + +所以高校能给博士的机会更多一些,不过现在高校也是 青年科研人员都是 五年合同制,如果没有产出,也要走人了,压力也很大。 + +及时这样,还是建议能去高校去高校,当然这需要有心善、能力强、有人脉的博导,那是最好的了,(**注意这里选择博导,心善是最重要的一个选项**) + +实在去不了高校,接下来我们在看企业。 + +博士找工作不建议走正式招聘流程,例如走官网投递之类的,面试都没戏。 + +**AI岗现在对coding能力,工程能力要求都很高,企业现在特别喜欢有科研能力和工程能力的博士**,但这种人才还是稀缺的,大多数博士可能不会做那么多的工程项目,更别说还是本硕是非计算机专业的博士。 + +所以博士找工作要靠门派,最好你的导师,实验室大boss 有哪些徒弟在外面企业,BAT华为之类的干的很好,能联系上,就是同一门派的师兄弟。 + +联系上,做一个内推,他们看一下你的博士论文和研究成果,如果行的话,面试基本就是走个流程,这样就很舒服了。 + +如果上来一波算法题,然后 操作系统,网络 数据库,这样考察下来,基本计算机专业的博士也招架不住,毕竟大多数博士是科研型的,一般来说工程能力比较弱,计算机基础哪些基本也没时间看。 + + +再说一说非科班博士如果有机会去面试,**一定要重点突出知识的迁移能力,和对学术的研究能力,这个是硕士本科生所不能具备的**。 + +企业还是比较喜欢有快速学习能力和知识迁移能力的人,因为技术是不断在变化了,只有学习能力足够强再能跟上。 + +所以**不能和本科硕士去硬拼专业技能的储备量**,特别是最新最热的技术(因为本来就是非科班嘛), 要体现出博士对技术对知识的理解,这是你的优势。 + +以上是我的回答,希望多大家有所启发。录友们周末愉快🌹 + diff --git a/problems/知识星球精选/备战2022届秋招.md b/problems/知识星球精选/备战2022届秋招.md new file mode 100644 index 00000000..383f5742 --- /dev/null +++ b/problems/知识星球精选/备战2022届秋招.md @@ -0,0 +1,62 @@ +# 要开始准备2022届的秋招了 + +在[知识星球](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)里准备秋招的录友还真不少,也会回答过不少关于秋招的问题。 + +![](https://img-blog.csdnimg.cn/20210507195443924.png) + +能感觉出来慌,不止这一位提问题的录友,很多同学都是这样,就是感觉一天天过的很快,也没干什么事情,然后心里就一直恐慌。 + +其实**慌主要是因为没有计划**,每天没有目的,然后一天一天的过,秋招越来越近,自然慌的很。 + +我在这篇里系统性的解答了实习相关的问题,[关于实习,大家可能有点迷茫!](https://mp.weixin.qq.com/s/xcxzi7c78kQGjvZ8hh7taA),也提到了 一般秋招8月份就要正式开始了,那时候各种提前批,面试小道消息,甚至身边一些同学已经拿到了offer,恐怕已经没有时间静下心好好学习了。 + +所以从现在看是满打满算,还有三个月的准备时间,如果利用好还是挺充足的,不要每天在把时间浪费在各种无聊的活动上。 + +这里我来列举一下,此时大家应该明确哪些事情: + +## 确定自己要面试的岗位 + +说道选择哪些岗位,一般的回答都是:选择自己感兴趣的呗,兴趣是最好的老师之类的balabala。 + +但我能亲身体会到,目前咱们的教育环境 也不可能说培养出什么 自己真正的兴趣,在大街上随便问一个人你的兴趣是什么? 基本回答都是:吃喝玩睡呗,还能有啥兴趣。 + +所以务实的建议是:现在去各大公司校招招聘官网 把所有的岗位都看一看,看看都有哪些要求,结合目前自己的经历和已经掌握的内容,看看哪些岗位可能最接近,然后再问问师兄师姐 这个岗位或者公司如何,最后后就把自己的目标岗位定下来了。 + +也有很多录友在星球里问我关于一些公司,岗位前景之类的问题,我都会给出相应的建议,这也是我工作过程中接触过了解过的。后面我也把部分内容整理一下,在公众号分享一下。 + +这样接下来的时间就是全身心去准备这个岗位所需要的技能。 + +**不少同学在秋招的时候,试试算法岗感觉也行,投投前端感觉也行,尝试后端也不是不可以,甚至再面面产品经理**。 + +**这样在秋招的时候就很被动了**,哪个岗位都没准备好,哪个岗位还都想试试,大概率是最后都没面上的。 + +所以现在就要把自己的目标岗位锁定了。 不同岗位之间 要求还是不一样的。 大家去看看招聘官网的要求就可以了。 + +## 制定学习计划 + +知道自己的目标岗位了,也知道岗位的要求了,剩下的就是制定详细的计划。 + +* 编程语言 +* 计算机基础(操作系统,网络,数据库、设计模式) +* linux相关(客户端岗位应该不需要) +* 项目 +* 准备自己的博客,github + +这几块按照岗位要求,8月份应该学会哪些内容,需要看哪些书等等。 + +然后以八月份以deadline开始倒推,每个月应该学哪些,每周应该学哪些,每天应该看那些。 + +把这些都明确了,心里就不慌了,也不会每天浪费大量宝贵的时间。 + +星球里的录友每天都在坚持打卡,总结自己今天学习的内容,这样就很好,把自己每天学习进度量化。 + +这样每天过的就很扎实。而且每天的打卡星球里录友和我都可以看到,我也会及时评论评论,大家也相互监督。 + +给大家看一位录友在星球里的总结: + +![](https://img-blog.csdnimg.cn/20210507204017952.png) + +大家平时的话,也要养成这种总结的习惯,只有及时总结把自己学过的内容积累下来,才能把时间沉淀下来,要不然就是一边学一边忘的节奏了。 + +**其实这种习惯,无论是秋招,还是准备跳槽,还是平时工作积累,都非常总要!** + diff --git a/problems/知识星球精选/大厂新人培养体系.md b/problems/知识星球精选/大厂新人培养体系.md new file mode 100644 index 00000000..165406f8 --- /dev/null +++ b/problems/知识星球精选/大厂新人培养体系.md @@ -0,0 +1,76 @@ +# 大厂的新人培养体系是什么样的 + +之前我一直在[知识星球](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)和大家讲,能进大厂一定要进大厂,大厂有比较好的培养体系。 + +也有录友在星球里问我,究竟培养体系应该是什么样的呢? 大厂都会这么培养新人么? + +![](https://code-thinking-1253855093.cos.ap-guangzhou.myqcloud.com/pics/20210717173307.png) + +其实大厂部门也是非常多,不同的领导对待新人的态度也是不一样的。 + +就拿腾讯来说,腾讯里面 上千个部门,基本就是上千个小公司,只不过外面披一个腾讯的壳子,每个小公司的leader风格截然不同。 + +赶上什么样的领导了,这就看命运了。 + +**只能说进大厂,大概率会有一个比较好的培养体系**。 + +那么好的培养体系是什么呢? + +要从两个方面来说: + +* 给你详细的学习路线(自我技术提升) +* 给你有产出的活(用来晋升) + +## 详细的学习路线 + +关于详细的学习路线,一般大厂入职之后配有导师的,导师给你安排的每一个功能,应该带你熟悉整个研发的流程。 + +一个功能的开发,需要经历如下几步: + +1. 看需求文档,确定需求 +2. 这个需求包含了哪些功能 +3. 有哪些难点 +4. 后台架构是什么样的(要有架构图) +5. 定协议(前后台一起商量),服务与服务之间的,后台与客户端之间的 +6. 设计数据结构+算法=程序 +7. 预估一下容量(各种资源例如带宽,存储,CPU等等) +8. 考虑一下部署(安全性,容灾,可伸缩性。。。。) +9. 设计评审 +(上面过程都是在分析) +10. 编码 +11. 自测 +12. 联调 +13. 交给测试 +14. 代码review +15. 合入 +16. 发布 + +可以看出来,写代码仅仅是 其中的一小步。导师应该带你走一遍完整的开发流程,然后告诉一些注意事项,**这样为自己程序员生涯打好基础**。 + +可能有的同学会感觉:我就开发一个小功能,哪用得着这么多步骤,一把梭哈,直接代码写完。 + +哈哈,这么想的同学一般是没有参与过大型且流程规范的项目开发。互联网千万级用户的项目,几十上百人一起开发是需要规范的,**所以上面我说的每一步都很重要!** + +## 有产出的活 + +初入职场的同学,可能都非常在意能不能学到东西,也就是自我技术提升,往往忽视了你干的活,是否有产出,能不能用来晋升。 + +这里就是很多所谓的“套路”,老司机一般挑的活符合如下几点: + +* 非常规整(周边没有烂糟的额外工作,例如还要开发各种小工具之类的) +* 独立模块(不需要和其他人扯皮,省事) +* 对项目组很重要(既有技术难点,又没有大量的业务增删改查) +* 风险系数比较低(上线出问题,锅甩不到他这里) + +这种活就是好活,用来晋升的利器,而且干着也舒服。 + +但一个项目,一定会有大量需要打杂的活,写各种脚本,各种处理数据,然后看各种问题,整理文章,汇报,开发边缘工具等等。 + +新人一般进来都是先打杂的,**但如果领导确实是细心培养你,还是额外给你一个小模块,让你做好,这个小模块就是让你用来晋升的或者转正的**。 + +这个建议不仅适用于实习生,对于初入职场的同学也很用帮助,这个部门是不是有在培养你,老司机一眼就能看出来,只不过新人可能自己很难发现。 + +所以需要过来人点拨一下,大家就知道自己现在的处境了。 + +希望对大家求职和工作有所帮助! + diff --git a/problems/知识星球精选/如何权衡实习与秋招复习.md b/problems/知识星球精选/如何权衡实习与秋招复习.md new file mode 100644 index 00000000..ee2fbd24 --- /dev/null +++ b/problems/知识星球精选/如何权衡实习与秋招复习.md @@ -0,0 +1,43 @@ + +# 已经在实习的录友如何准备秋招? + +最近在[知识星球](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)一位录友问了实习生如何权衡工作和准备秋招的问题。 + + +![](https://code-thinking-1253855093.cos.ap-guangzhou.myqcloud.com/pics/20210703230618.png) + +其实这个问题挺有代表性,我之前一直讲的都是没有去实习的录友应该怎么怎么办,其实已经在大厂实习的录友也有很多烦恼。 + +这里我说一说已经有大厂实习经历,准备秋招的重点。 + +一般来说有大厂的实习经历,**面试官的兴趣都在大厂实习的项目经验上**。 + +所以关于权衡实习和准备基础这两个方面,**可以把主要精力放在项目包装上,其次是看基础**。 + +要在包装实习项目上多花点心思,实习生做的项目偏上业务很正常,不可能让实习生搞太复杂的,一旦出了问题还得导师背锅。 + +自己干的活,或者项目很简单 不要紧,可以好好包装一下,如果没有难点,**可以结合业务自己“造”难点**,大厂内部研发文档都特别多而且特别全。 + +例如整个项目的立项,各个模块的开发,以及研发中遇到的困难,技术选型,上线事故,等等这些都是有完整的文档记录的。(当然大厂也有一些部门研发流程很原始,没有文档,全靠口述) + +从这些文档中也可以找出 难点糅合到自己的项目中。 + +假如线上出了事故,虽然自己不用去排查但可以跟着同事们一起看问题,一起分析,甚至帮他捞捞日志,打打下手。 + +这次事故的表现,起因,定位等等,排查问题的同事都会记录的清清楚楚,放在项目文档下。 + +可以把这些文档都多看看,然后就可以转变成自己排查线上事故的经历了。 + +**这种经历在面试官来看就是很大的加分项了**。 + +所以在大厂实习,想包装自己的项目方法有很多,只不过一些同学初入职场,对自己有帮助的资料或者内容不太敏感,不善于利用已有的资源。 + +**需要过来人点一下,基本就上道了,哈哈哈**。 + +当然不是说只要在大厂实习,基础完全就不用看了,抽空也要看,只不过 项目经验(项目的包装)更重要! + +关于提前批,一般来说本厂实习生是不能参加提前批的。 + +你可以参加 其他大厂的提前批,只不过参加提前批是有代价的,面试不通过都是有记录的,得看自己项目准备的如何,能不能拿出手,而且一边实习一边面试可能也不太方便,如果想试一试,就找自己不想去的企业的提前批试试水! + + diff --git a/problems/知识星球精选/客三消.md b/problems/知识星球精选/客三消.md new file mode 100644 index 00000000..869d39de --- /dev/null +++ b/problems/知识星球精选/客三消.md @@ -0,0 +1,91 @@ + +# 客三消! + +给大家科普一下:什么是客三消。 + +翻译过来就是客户端三年消失。 + +**听起来是不是有点吓人**!这种说法略夸张,但只要能传开,就说明客户端一定有一些困局,并不是空穴来风。 + +昨天卡哥在朋友圈里分享了一个段子的截图 + +![](https://code-thinking-1253855093.file.myqcloud.com/pics/IMG_3986.jpg) + +然后朋友圈就炸了,上百条的留言,问我这是为啥。 + +其实这个问题在[知识星球](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)里也有录友问过我。 + +![](https://code-thinking-1253855093.file.myqcloud.com/pics/20210905091037.png) + +当时已经详细的回答了一波,估计很多录友都有这方面的困惑,所以在公众号上再来给大家讲一讲。 + +**关于星球提问中SRE和后端,在这里就不介绍了,卡哥重点说一说,客户端**。 + +客户端目前比较突出的问题,主要是 这四点: + +1. 客户端岗位需求相对较小,而且有越来越小的趋势 + +2. 技术做深相对较难 + +3. 客户端晋升相对困难 + +4. 中年危机 (其实程序员有,不过客户端可能更明显一些) + + +## 岗位需求相对较小 + +客户端需求的减少,主要是体现在中小厂,或者说创业公司,因为大家都养不起原生客户端,基本都会采用跨端的技术方案,也就是大前端(即一套代码可以编译出各个端的版本,包括安卓,IOS等各种终端)。 + +这样就节省了很大的人力,不过目前在功能上一定没有 原生客户端体验好。 + +**但大前端取代客户端是技术趋势!** + +如果选择客户端,那么就多少要掌握一些跨端技术方案。 + +互联网软件的思维,就是轻前端,重后端,为什么PC软件搬到了浏览器上,移动APP搬到小程序上,都是这个道理,一般重头戏在后端。 + +所以后端的需求才会比较大。 + +## 技术做深相对较难 + +这里就不止客户端,其实前端都有这个问题。 + +关于前端和客户端的区别,其实没有那么严格的定义,大家可以理解 前端包含了客户端。一切可视化皆为前端。 + +前端框架、渲染引擎 变化相对快,可能你刚熟悉一个框架,然后就换了,最关键是可能还拿不准哪一种框架日后会成为主流,一不小心就跑偏了。 + +而后端框架变化相对就慢得多,而且 更容易(或者说更有机会)把技术做深,因为 高并发,高可用,低延迟 这些基本都是后端的工作。 + +正是前端 技术栈更新太快,所以要持续高强度学习 (这种学习可能不是往深去学习,而是 适应一个又一个框架的学习)。 + +而且前端 很容易陷入需求的反复变化之中,因为一个功能或者界面的修改,都是前端同学的工作量。 + +后端可能 什么都不用改,接口都是一样的,然后就可以空出时间研究技术。 + +## 晋升 + +目前在大厂,客户端职业天花板相对较低,一般情况下,可能到组长就到头了。 + +搞技术一路升上去,甚至到CTO的,基本都是后端,这也是因为前面讲过的:大部分的互联网产品,重头戏在后端,所有后端更有机会把技术做深,更直白说,后端更有机会在晋升做ppt的时候 “吹牛逼”。 + + +## 中年危机 + +这个就更范范一些了,程序员都有这个危机,不过客户端可能更突出一些。 + +原生客户端的岗位需求确实会越来越少,如果继续干下去,没有晋升到管理层,然后退居二线公司,发现二线公司都没有原生客户端的岗位,那么就非常被动了。 + +所以可以往大前端的方向去转。 + +大前端现在也有很多技术方案,ReactNative和weex(阿里,脸书的方案),Flutter(Google的方案),微信小程序(腾讯的方案) + +不过最终哪一个方案一统天下,这还是未知数,所以就需要持续学习咯。 + +# 总结 + +以上就是我在[知识星球](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)里的详细回答。 + +注意我这里说的一般情况,当然各个岗位都有佼佼者,或者说大牛,客户端也有大牛,也很香,不过这是极少数,就不在讨论范围内了。 + +希望对大家理解目前客户端的趋势有所帮助。 + diff --git a/problems/知识星球精选/技术不好如何选择技术方向.md b/problems/知识星球精选/技术不好如何选择技术方向.md new file mode 100644 index 00000000..ad8a777c --- /dev/null +++ b/problems/知识星球精选/技术不好如何选择技术方向.md @@ -0,0 +1,28 @@ + +# 技术不太好,也不知道对技术有没有兴趣,我该怎么选? + +最近在[知识星球](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)里解答了不少录友们的疑惑,其实发现一个挺普遍的问题: + +* 我技术很一般 +* 对技术也没有什么追求 +* 要是对什么岗位感兴趣我也不知道 +* 以后自己喜欢干什么也不太清楚 + +**相信说到了不少录友心里去了, 其实目前应试教育下 确实很难找到自己感兴趣的事情**。 + +但我想说的是,并不是技术不好就什么都做不了,依然有很多选择,只不过录友们没有接触过,所以就不知道自己接下来要往哪个方向走。 + +这里我给出一些路线,大家可以参考: + +方向一:走纯研发路线,去大厂,如果真的对技术提不上兴趣可能会走的很辛苦。 + +方向二:如果技术还凑合,不做纯研发,退一步也可以考虑去大厂做测试相关的工作,对技术要求没有那么高,但也需要技术能力,而且技术能力越强越吃香。 + +方向三:去银行,证券,国企类的企业去做研发岗位,这种国有企业的技术面试相对简单不少,比较轻松,还很稳定,收入虽说不高,但生活足够滋润。 + +方向四:做toC(面向普通用户)的产品经理,toC产品经理这个岗位就特别卷,因为这个岗位门槛太低了,任何专业的同学都可以去做产品经理。 这样自己所学的技术就基本没有用了,也凸显不出技术上的优势,但如果自己真的对APP之类的各种应用得心应手,优点缺点,用户爽点、日活、次留等等手到擒来,倒可以试一试。 + +方向五:做toB的产品经理,包括云计算,大数据这些产品都是需要产品经理的,例如百度云,腾讯云,阿里云等等,这种产品本身就是技术产品,所以需要懂技术的产品经理来做设计,即需要产品的抓住需求的能力,也需要懂点技术,既可以发挥自己的技术能力,还可以做产品规划,基本也不用写代码。 + +对技术要求不高的岗位也挺多的,发展也很好,只要大家多去了解,总会找打符合自己的岗位。 + diff --git a/problems/知识星球精选/提前批已经开始了.md b/problems/知识星球精选/提前批已经开始了.md new file mode 100644 index 00000000..ec8ede8e --- /dev/null +++ b/problems/知识星球精选/提前批已经开始了.md @@ -0,0 +1,42 @@ + +最近华为提前批已经开始了,不少同学已经陆续参加了提前批的面试。 + +在[知识星球](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)上就有录友问我这么个问题: + +![](https://code-thinking-1253855093.cos.ap-guangzhou.myqcloud.com/pics/20210711002802.png) + +华为是比较看重学历的,如果学校比较好(985以上),华为的面试会随意一些,几乎就是走一个流程。 + +我记得当初校招的时候,华为来我们学校就是 几个大巴把整个计算机学院的同学拉到酒店,批量面试,面试过程差不多就是走个形式,大家基本都拿到offer了。 + +不是说 非985/211的同学 技术就不好,而是当企业每年要招上万人的时候,通过学历还筛选相对来说是 效率较高且成本最低的一种方式。 + +不过现在竞争越来越激烈了,华为也很少这种粗暴方式来召人。再说华为给出的薪酬相对互联网大厂也 很有竞争力,发展前景也很不错。 + +那么在说一说面试的内容。 + +可能有的同学感觉,我为了面试,准备了这么多,结果面试都没问,就问问项目问问编程语言就完事了。 + +这其实很正常! + +不同的公司,同一个公司不同部门,同一个部门不同面试官 面试风格都不太一样。 + +可能部门就比较缺人,面试官看一下 简历 学校可以,技术看上去还凑合,项目也还行,那么面试可能就放水一点,然后就过了。 + +毕竟面试官也很忙,在大厂谁都不想当面试官,都是工作之余加班的工作量,**所以面试官也想快点结束**。 + +还有另一种可能,就是部门已经招满了,但依然安排了面试,那么面试官就随便问问,然后也不会招人了。 + +还有一种,就是有的面试官就习惯考察算法题,问完算法了,其他的他也就不会问了。 + +因为操作系统,网络,数据库这些面试官在面试之前也要突击准备的,工作那么多年,都忘的差不多了,**所以要复习一波,这也是工作(都是加班啊!)**。 + +甚至可能面试官前天刚要和女朋友过生日,然后就没准备计算机基础方面的内容,索性面试的时候也就不问了..... + +这都是有可能的,很多同学可能没做过面试官,所以对一些面试过程就容易想不通,其实面试官也是普普通通的打工仔,他也不想加班,也想只要人品端正,积极肯干,随便召个技术差不多的就算了,哈哈哈。 + +所以说,面试有的时候也很看缘分的,大家辛辛苦苦造火箭,结果面试都没问是很正常的。 + +大家也放平心态,把该做的做好,剩下的交个天意了。 + + diff --git a/problems/知识星球精选/秋招的上半场.md b/problems/知识星球精选/秋招的上半场.md new file mode 100644 index 00000000..8ba938ea --- /dev/null +++ b/problems/知识星球精选/秋招的上半场.md @@ -0,0 +1,51 @@ + +# 秋招上半场的总结 + +八月份已经接近尾声,不少录友已经在[知识星球](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) 已经总结了秋招的上半场。 + +![](https://code-thinking-1253855093.file.myqcloud.com/pics/20210829214839.png) + +可以看出 这位录友也是身经百战,目前也拿到了几个offer。 + +星球里还有不少录友已经拿到了字节,阿里,百度提前批的offer。 + +![](https://code-thinking-1253855093.file.myqcloud.com/pics/20210829231035.png) + +不过绝大部分录友还在经受着考验,处于焦虑的状态,秋招上半场也是几多欢喜几多愁。 + +找工作其实是一个很虐心的过程,没有offer、没准备好、面试没发挥好、差一点就能把这题过了 等等,每一个场景都给大家增添一份焦虑。 + +星球里有一些录友就找到了一个方向,或者一个准备同一家公司的伙伴,就会好一些。 + +![找到了同伴交流](https://code-thinking-1253855093.file.myqcloud.com/pics/20210820093109.png) + +**有时候,有压力,自己憋着,没人交流,只会压力越来越大**。 + +对于还没有offer的录友,我对大家的建议也是,**心态要稳住,要适当放低预期,不是所有人都要大厂,但不能放低自己对学习的要求!** + +有些同学,经过几次面试的打击之后,直接就自暴自弃,狂玩几天想释放压力,这么做的结果,只会压力越来越大。 + +所以,**秋招进行时,大家不要过于放松,无论什么情况,只要没有拿到心仪offer,就不能放松,一旦放松之后,换来的就是更看不到希望**。 + +有的同学可能学校不好,有的同学是转行计算机,一路下来确实艰难。 + +我在星球里,看到的不仅是大家准备秋招过程的每日学习总结、打卡,也看到了一些录友的不容易。 + +![](https://code-thinking-1253855093.file.myqcloud.com/pics/20210829221259.png) + +说实话,看着评论区,还是很感动的,估计这位 打卡的录友,也得到了很大的鼓励。 + +这可能是 成千上万 目前正在冲刺秋招应届生 的一个缩影。 + +面试不仅仅是只看技术,也挺看缘分的,有的同学可能感觉莫名其妙的就挂了,有的同学可能感觉莫名其妙的就拿到了offer。 + +我就简单列举几个原因。 + +* 可能部门缺人,或满了 +* 可能是当天面试的同学都不太行,就矬子里拔大个 +* 可能之前有几个优秀的毕业生,但按照之前的标准都没过,然后面试官发现这么下去招不到人了,一下子就把标准降低了,然后轮到了你,你感觉你发挥的并不好,但也给你offer了。 + +所以面试也有很多很多其他因素,也很看缘分。 + +大家放平心态就好。 + diff --git a/problems/知识星球精选/秋招进行中的迷茫与焦虑.md b/problems/知识星球精选/秋招进行中的迷茫与焦虑.md new file mode 100644 index 00000000..bdd59d4f --- /dev/null +++ b/problems/知识星球精选/秋招进行中的迷茫与焦虑.md @@ -0,0 +1,50 @@ +# 秋招进行时,其实大家都很焦虑 + +大家应该都发现了,提前批和秋招都越来越提前的,大部分的录友此时的心态还是挺焦虑的。 + +特别是大三的同学吧,同时面临这找工作和考研两个方向的诱惑。 + +一位录友就在[知识星球](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)问了我这个问题: + +![](https://code-thinking-1253855093.cos.ap-guangzhou.myqcloud.com/pics/20210724183240.png) + +其实 互联网35岁中年危机,应该吓退了不少小鲜肉,互联网35岁中年危机是真的,网上哪些关于裁员的消息 大多数也都是真的。 + +但我想说:特别是正在找工作的同学,大家应该都是 95后,00后,可以先不用想那么长远,能把未来五年规划好,就不错不错的了,现在行业的变化,都是两三年一变天,等大家35岁的时候,都不一定啥样了。 + +而且卡哥还替大家先趟趟路,哈哈哈 + +现在的大三,最忙的就是两伙人,**考研的和找工作的,这两条路都挺难的**。 + +另外还有不少录友应该在考研与找工作之间摇摆。可能有的学校大多数都在找工作,有的学校大多数都在考研,不过应该考研占绝大多数。 + +关于考研我的观点是,如果 本科毕业能拿到大厂或者中厂offer,可以不考研,看看自己比较优秀的学长学姐,毕业都去哪了,是否去了大厂,如果比较优秀的学长学姐 就业也一般,我还是推荐读研的,因为顺便也提升一下学历。 + +但有的同学是从大一入学就规划了自己 以后直接工作的,这种无论学校如何,我都是比较支持的! + +**因为从大一就明确自己的方向,按找工作的要求来学习,一般来说 最后找工作都不会差**。 + +最危险的是,大一大二没计划,到了大三开始摇摆,考研还是找工作。这种是最危险的,如果在纠结一阵纠结到 现在的话,那基本哪条路都走不好了。 + +对于现在找工作的录友,可能发现身边的同学都在考研,而且在准备找工作中,可能还没有明确的学习计划,东打一发西扯一下,就会很焦虑,主要也是身边一起工作的同学可能不多,交流的少。 + +找工作是一个累心的过程,非常累,恨不得哪家公司赶紧给我个offer,让我干啥都行,甚至怀疑自己是不是要再去考研。 + +**其实这时候,不是自己方法不对,也不是自己选择的路错了,而是需要一个过来人,给你打打气**。 + +静下心来,把最近开始面试的公司排一下,把自己还要学习的内容做好计划,都列出来,按着一步一步去执行,心里会踏实的多。 + +再说考研,考研也一点都不轻松,进大厂卷,**现在计算机考研比进大厂还卷(特别是名校计算机)**,如果考研没考上,找工作还挺难的,毕竟考研那套知识,对于找工作来说几乎没啥用。 + +所以不论是找工作,还是考研,大家都是一样的焦虑,每条路都不好走,但自己一旦选择了,就坚持下来,走好自己的路。 + +话再说回来,**现在不论身在什么阶段,都离不开“卷”,就算最后进了大厂工作,依然也是卷**。 + +大家都感觉自己准备面试好辛苦,好心累。其实给你面试的面试官,可能自己手上的模块线上又出问题了,还要担心年底是不是要背锅了,是不是年终没了,晋升不了了,是不是要准备跳槽了,甚至应届生的工资比自己的还高 等等。 + +**所以面试官也许比你还心累!** + +是不是想到这里,心里就舒服点了,哈哈哈哈,其实是有时候自己很迷茫但没有人沟通,就会陷入一个死循环,只要和过来人聊一聊,没啥大不了的。 + +大家其实这这样。 + diff --git a/problems/知识星球精选/英语到底重不重要.md b/problems/知识星球精选/英语到底重不重要.md new file mode 100644 index 00000000..d1f647c0 --- /dev/null +++ b/problems/知识星球精选/英语到底重不重要.md @@ -0,0 +1,53 @@ +# 对程序员来说,英语到底重不重要 + +在[知识星球](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)有一位录友问了我这么一个问题。 + +![](https://gitee.com/programmercarl/pics/raw/master/pic1/20210605193955.png) + +这个问题我曾经在上学的时候也思考过。 + +这次正好来好好说一说。 + +当时我搞ACM的时候都是英文题目的,哪会有中文题目,现在力扣全面汉化也是最近几年的事情。 + +如今又工作了这么多年后重新看待这个问题,又有更全面的看法了。 + +其实我想说,**对英语对程序员即重要,也不重要!** 这是要看环境,看背景的。 + +如果你现在在准备秋招,或者是跳槽,目标是冲刺国内大厂,那么暂时不用花费精力学英语,就算四级都没过,大厂面试官也不会问你过没过四六级的。 + +貌似华为对英语四六级是有要求的,如果面试BAT,英语不是关键性问题。 + +但工作之后,英语其实就很重要了,也要注意程序员英语和普通英语是不一样的。 + +一手的技术资料,和优秀的问答 基本都是英文的,国内的资料都是别人嚼过的,质量参差不齐。 + +而且国内的问答社区其实环境并不好(懂的人都懂),真正解决问题,还得靠Stack Overflow。 + +**所以技术文档(英文),Stack Overflow , Quora才是程序员的利器**。 + +工作以后如果你把程序员英语(注意不是普通英语)练好,其实对技能和视野的提升是很有帮助的。 + +这里为什么强调是程序员英语呢, 因为有太多专业词是CS特有的,而不是日常英语。 + +**继承,多态,变量,英文怎么说? 估计可以难住不少人了**。 + +所以当我们查问题的时候,第一反应 一定是用 中文关键词去搜索,因为我们不知道对应的英文关键词(也懒的去查一下)。 + +所以英语好,这是一种技术壁垒,可以任意遨游在中文和英文的世界里,有两极的思考! + +**那么对程序员来说,英语口语重要么?** + +如果你直接想去外企的话,练一练吧,也是挺重要的,如果在国内的话,用处不太大。 + +那一定有人说了:练好口语 一定是有利的。 + +这个我也 赞同,练啥其实都有有利的,但我们要看**投入产出比** + +我在学校的时候英语口语还是挺不错的,当时学校的留学生我基本都认识,和他们扯扯皮没问题,可工作这些年之后,全!都!还!回!去!了! + +所以如果练习口语,一定要有目标,要么就是雅思托付考试要出国,要么就一定要去外企找机会transfer美帝,这样有一个环境可以一直保持下去。 + +否则,花费大量时间练习,其实仅仅是感动自己,过不了多久,就都还回去。(例如说我,哈哈哈哈) + + diff --git a/problems/知识星球精选/要不要考研.md b/problems/知识星球精选/要不要考研.md new file mode 100644 index 00000000..d67b95b7 --- /dev/null +++ b/problems/知识星球精选/要不要考研.md @@ -0,0 +1,40 @@ +# 到底要不要读研 + +在[知识星球](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)里讨论了一下关于要不要读研的问题。 + +![](https://gitee.com/programmercarl/pics/raw/master/pic1/20210613230829.png) + +其实不少同学纠结于计算机专业要不要考研。 + +我的观点是 **如果认为自己本科毕业 可以拿到大厂的研发岗offer,那么就不用读研**(除非保研到一个特别好的学校了)。 + +那么怎么能发现自己本科毕业能不能拿到大厂offer呢。 + +看看自己学生学哥学姐,大部分人的就业去向,如果很多都可以进入大厂,那么就可以追寻他们的足迹。 + +如果自己学校本科毕业,就业比较一般,那么就去读研吧。 + +当然这里还有一个情况,就是算法岗,算法岗一般要求研究生学历以上。但算法岗现在很卷,985高校研究生,找算法岗的工作都很难,既要顶会,也要coding的能力。 + +目前的现况是很多搞AI的985研究生都在转研发岗,**但如果你依然立志从事人工智能(说明是真的热爱),那么就去读研吧**。 + +研究生毕业去做研发岗和本科毕业做的研发,其工作其实没区别。那么为什么读研呢。 + +现在环境就比较卷,两个候选人,实力差不多,一个本科,一个研究生,价钱也差不多,那么大厂就要个研究生呗,在招生简章里看着也好看,说明公司都是高学历人才。 + +当然一般来说 研究生毕竟又多读了 两三年,普遍会比本科强一些。 + +**PS:大厂研发岗校招本科和研究生薪资几乎没啥差别**。 + + +那么话在说回来,**如果打算考研,那么就争取更好学校的研究生,提升一下学历,把考研所付出的努力最大化**。 + +最后关于本科生要不要读研: + +* 本科如果有实力去大厂做研发,那么就去! + +* 如果本科去不了大厂,可以考虑考研,考研是一次提升学历的机会,一旦选择考研这条路,就给自己点压力! + +* 如果知道AI岗位目前就业情况,依然要立志从事AI,那么就去读研 + + diff --git a/problems/知识星球精选/面试中发散性问题.md b/problems/知识星球精选/面试中发散性问题.md new file mode 100644 index 00000000..550b51d2 --- /dev/null +++ b/problems/知识星球精选/面试中发散性问题.md @@ -0,0 +1,40 @@ +# 面试中遇到发散性问题,应该怎么办? + +这周在[知识星球](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)有一位录友问了我这么一个问题,我感觉挺有代表性的,应该不少录友在面试中不论是社招还是校招都会遇到这一类的问题。 + +问题如下: + +![](https://gitee.com/programmercarl/pics/raw/master/pic1/20210529183636.png) + +首先面试官评价:基本的知识都懂还可以,但是知识碎片化。 + +因为现在基本大家都是背面经,所以对考点知识点掌握的都不错,确实对整体上理解不够。 + +但如果想对整体上理解深入,需要花费很大精力的,而且要把书看的很透彻,那这种候选人基本都是拔尖的。 + +关于操作系统啊,数据库等等,大多数录友应该都是靠面经,其实背面经也是最高效,性价比最高的方式的,如果真的去把书看透,一本《深入理解计算机系统》够看一年了。。。。 + +所以面试官基本不会因为这个问题而把你pass掉,那位提问的录友也是顺利进入了三面。 + +那么面试中遇到这种发散性问题应该怎么办呢? + +其实他问的这种问题,**就没指望你能说出 正确的答案,这是没有标准答案的**,例如淘宝京东的那种登录的场景 没有经历过 是不知道究竟怎么回事的。 + +而问你对操作系统的理解,也是千人千面没有标准的。 + +遇到这种问题,你就结合自己的想法,大胆说,不要说这个我不知道,那个我没遇到过之类的。 + +你说的解决方案,一定是有问题的,面试官在质疑你的时候,**你要表现出很兴奋,积极和面试官讨论:为什么不对**,然后说出你的观点,结合你所知道的理论知识。 + +大胆说,不要怕,一般情况你的理论知识都比面试官强,面试官工作好多年了基础知识早忘了,基本都是面试你前一天突击一个小时(暴漏真相了哈哈哈) + +**忌讳:面试官一质疑你,你就开始怀疑自己,心想那我说的不对吧,然后就不说话了。。。** + +最后这种发散性的问题,也没法去专门准备,因为这种问题主要是**考察候选人对技术的态度和沟通能力!** + +所以大家如果在面试遇到这一类发散性问题,一定要积极沟通,**表现出你对技术的追求和优秀的沟通能力**。 + +**注意 和面试官讨论的时候要面带微笑,不要板着脸,面试官也会喜欢以后和这样的人做同事!** + +好咯,心法已经传授给大家了。 + diff --git a/problems/算法模板.md b/problems/算法模板.md index 888ae773..b56678ab 100644 --- a/problems/算法模板.md +++ b/problems/算法模板.md @@ -174,7 +174,7 @@ vector postorderTraversal(TreeNode* root) { ``` ### 广度优先遍历(队列) -相关题解:[0102.二叉树的层序遍历](https://github.com/youngyangyang04/leetcode/blob/master/problems/0102.二叉树的层序遍历.md) +相关题解:[0102.二叉树的层序遍历](https://programmercarl.com/0102.二叉树的层序遍历.html) ``` vector> levelOrder(TreeNode* root) { @@ -202,13 +202,13 @@ vector> levelOrder(TreeNode* root) { 可以直接解决如下题目: -* [0102.二叉树的层序遍历](https://github.com/youngyangyang04/leetcode/blob/master/problems/0102.二叉树的层序遍历.md) +* [0102.二叉树的层序遍历](https://programmercarl.com/0102.二叉树的层序遍历.html) * [0199.二叉树的右视图](https://github.com/youngyangyang04/leetcode/blob/master/problems/0199.二叉树的右视图.md) * [0637.二叉树的层平均值](https://github.com/youngyangyang04/leetcode/blob/master/problems/0637.二叉树的层平均值.md) -* [0104.二叉树的最大深度 (迭代法)](https://github.com/youngyangyang04/leetcode/blob/master/problems/0104.二叉树的最大深度.md) +* [0104.二叉树的最大深度 (迭代法)](https://programmercarl.com/0104.二叉树的最大深度.html) -* [0111.二叉树的最小深度(迭代法)]((https://github.com/youngyangyang04/leetcode/blob/master/problems/0111.二叉树的最小深度.md)) -* [0222.完全二叉树的节点个数(迭代法)](https://github.com/youngyangyang04/leetcode/blob/master/problems/0222.完全二叉树的节点个数.md) +* [0111.二叉树的最小深度(迭代法)](https://programmercarl.com/0111.二叉树的最小深度.html) +* [0222.完全二叉树的节点个数(迭代法)](https://programmercarl.com/0222.完全二叉树的节点个数.html) ### 二叉树深度 @@ -296,4 +296,4 @@ Go: * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/背包总结篇.md b/problems/背包总结篇.md index 0dd407ac..f3732c8d 100644 --- a/problems/背包总结篇.md +++ b/problems/背包总结篇.md @@ -30,30 +30,30 @@ ## 背包递推公式 问能否能装满背包(或者最多装多少):dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]); ,对应题目如下: -* [动态规划:416.分割等和子集](https://mp.weixin.qq.com/s/sYw3QtPPQ5HMZCJcT4EaLQ) -* [动态规划:1049.最后一块石头的重量 II](https://mp.weixin.qq.com/s/WbwAo3jaUaNJjvhHgq0BGg) +* [动态规划:416.分割等和子集](https://programmercarl.com/0416.分割等和子集.html) +* [动态规划:1049.最后一块石头的重量 II](https://programmercarl.com/1049.最后一块石头的重量II.html) 问装满背包有几种方法:dp[j] += dp[j - nums[i]] ,对应题目如下: -* [动态规划:494.目标和](https://mp.weixin.qq.com/s/2pWmaohX75gwxvBENS-NCw) -* [动态规划:518. 零钱兑换 II](https://mp.weixin.qq.com/s/PlowDsI4WMBOzf3q80AksQ) -* [动态规划:377.组合总和Ⅳ](https://mp.weixin.qq.com/s/Iixw0nahJWQgbqVNk8k6gA) -* [动态规划:70. 爬楼梯进阶版(完全背包)](https://mp.weixin.qq.com/s/e_wacnELo-2PG76EjrUakA) +* [动态规划:494.目标和](https://programmercarl.com/0494.目标和.html) +* [动态规划:518. 零钱兑换 II](https://programmercarl.com/0518.零钱兑换II.html) +* [动态规划:377.组合总和Ⅳ](https://programmercarl.com/0377.组合总和Ⅳ.html) +* [动态规划:70. 爬楼梯进阶版(完全背包)](https://programmercarl.com/0070.爬楼梯完全背包版本.html) 问背包装满最大价值:dp[j] = max(dp[j], dp[j - weight[i]] + value[i]); ,对应题目如下: -* [动态规划:474.一和零](https://mp.weixin.qq.com/s/x-u3Dsp76DlYqtCe0xEKJw) +* [动态规划:474.一和零](https://programmercarl.com/0474.一和零.html) 问装满背包所有物品的最小个数:dp[j] = min(dp[j - coins[i]] + 1, dp[j]); ,对应题目如下: -* [动态规划:322.零钱兑换](https://mp.weixin.qq.com/s/dyk-xNilHzNtVdPPLObSeQ) -* [动态规划:279.完全平方数](https://mp.weixin.qq.com/s/VfJT78p7UGpDZsapKF_QJQ) +* [动态规划:322.零钱兑换](https://programmercarl.com/0322.零钱兑换.html) +* [动态规划:279.完全平方数](https://programmercarl.com/0279.完全平方数.html) ## 遍历顺序 ### 01背包 -在[动态规划:关于01背包问题,你该了解这些!](https://mp.weixin.qq.com/s/FwIiPPmR18_AJO5eiidT6w)中我们讲解二维dp数组01背包先遍历物品还是先遍历背包都是可以的,且第二层for循环是从小到大遍历。 +在[动态规划:关于01背包问题,你该了解这些!](https://programmercarl.com/背包理论基础01背包-1.html)中我们讲解二维dp数组01背包先遍历物品还是先遍历背包都是可以的,且第二层for循环是从小到大遍历。 -和[动态规划:关于01背包问题,你该了解这些!(滚动数组)](https://mp.weixin.qq.com/s/M4uHxNVKRKm5HPjkNZBnFA)中,我们讲解一维dp数组01背包只能先遍历物品再遍历背包容量,且第二层for循环是从大到小遍历。 +和[动态规划:关于01背包问题,你该了解这些!(滚动数组)](https://programmercarl.com/背包理论基础01背包-2.html)中,我们讲解一维dp数组01背包只能先遍历物品再遍历背包容量,且第二层for循环是从大到小遍历。 **一维dp数组的背包在遍历顺序上和二维dp数组实现的01背包其实是有很大差异的,大家需要注意!** @@ -61,7 +61,7 @@ 说完01背包,再看看完全背包。 -在[动态规划:关于完全背包,你该了解这些!](https://mp.weixin.qq.com/s/akwyxlJ4TLvKcw26KB9uJw)中,讲解了纯完全背包的一维dp数组实现,先遍历物品还是先遍历背包都是可以的,且第二层for循环是从小到大遍历。 +在[动态规划:关于完全背包,你该了解这些!](https://programmercarl.com/背包问题理论基础完全背包.html)中,讲解了纯完全背包的一维dp数组实现,先遍历物品还是先遍历背包都是可以的,且第二层for循环是从小到大遍历。 但是仅仅是纯完全背包的遍历顺序是这样的,题目稍有变化,两个for循环的先后顺序就不一样了。 @@ -71,12 +71,12 @@ 相关题目如下: -* 求组合数:[动态规划:518.零钱兑换II](https://mp.weixin.qq.com/s/PlowDsI4WMBOzf3q80AksQ) -* 求排列数:[动态规划:377. 组合总和 Ⅳ](https://mp.weixin.qq.com/s/Iixw0nahJWQgbqVNk8k6gA)、[动态规划:70. 爬楼梯进阶版(完全背包)](https://mp.weixin.qq.com/s/e_wacnELo-2PG76EjrUakA) +* 求组合数:[动态规划:518.零钱兑换II](https://programmercarl.com/0518.零钱兑换II.html) +* 求排列数:[动态规划:377. 组合总和 Ⅳ](https://mp.weixin.qq.com/s/Iixw0nahJWQgbqVNk8k6gA)、[动态规划:70. 爬楼梯进阶版(完全背包)](https://programmercarl.com/0070.爬楼梯完全背包版本.html) 如果求最小数,那么两层for循环的先后顺序就无所谓了,相关题目如下: -* 求最小数:[动态规划:322. 零钱兑换](https://mp.weixin.qq.com/s/dyk-xNilHzNtVdPPLObSeQ)、[动态规划:279.完全平方数](https://mp.weixin.qq.com/s/VfJT78p7UGpDZsapKF_QJQ) +* 求最小数:[动态规划:322. 零钱兑换](https://programmercarl.com/0322.零钱兑换.html)、[动态规划:279.完全平方数](https://programmercarl.com/0279.完全平方数.html) **对于背包问题,其实递推公式算是容易的,难是难在遍历顺序上,如果把遍历顺序搞透,才算是真正理解了**。 @@ -88,7 +88,7 @@ **而且每一个点,我都给出了对应的力扣题目**。 -最后如果你想了解多重背包,可以看这篇[动态规划:关于多重背包,你该了解这些!](https://mp.weixin.qq.com/s/b-UUUmbvG7URWyCjQkiuuQ),力扣上还没有多重背包的题目,也不是面试考察的重点。 +最后如果你想了解多重背包,可以看这篇[动态规划:关于多重背包,你该了解这些!](https://programmercarl.com/背包问题理论基础多重背包.html),力扣上还没有多重背包的题目,也不是面试考察的重点。 如果把我本篇总结出来的内容都掌握的话,可以说对背包问题理解的就很深刻了,用来对付面试中的背包问题绰绰有余! @@ -101,4 +101,4 @@ * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/背包理论基础01背包-1.md b/problems/背包理论基础01背包-1.md index 3f603366..7d04b514 100644 --- a/problems/背包理论基础01背包-1.md +++ b/problems/背包理论基础01背包-1.md @@ -34,7 +34,7 @@ leetcode上没有纯01背包的问题,都是01背包应用方面的题目, ## 01 背包 -有N件物品和一个最多能被重量为W 的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。**每件物品只能用一次**,求解将哪些物品装入背包里物品价值总和最大。 +有N件物品和一个最多能背重量为W 的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。**每件物品只能用一次**,求解将哪些物品装入背包里物品价值总和最大。 ![动态规划-背包问题](https://img-blog.csdnimg.cn/20210117175428387.jpg) @@ -82,8 +82,8 @@ leetcode上没有纯01背包的问题,都是01背包应用方面的题目, 那么可以有两个方向推出来dp[i][j], -* 由dp[i - 1][j]推出,即背包容量为j,里面不放物品i的最大价值,此时dp[i][j]就是dp[i - 1][j]。(其实就是当物品i的重量大于背包j的重量时,物品i无法放进背包中,所以被背包内的价值依然和前面相同。) -* 由dp[i - 1][j - weight[i]]推出,dp[i - 1][j - weight[i]] 为背包容量为j - weight[i]的时候不放物品i的最大价值,那么dp[i - 1][j - weight[i]] + value[i] (物品i的价值),就是背包放物品i得到的最大价值 +* **不放物品i**:由dp[i - 1][j]推出,即背包容量为j,里面不放物品i的最大价值,此时dp[i][j]就是dp[i - 1][j]。(其实就是当物品i的重量大于背包j的重量时,物品i无法放进背包中,所以被背包内的价值依然和前面相同。) +* **放物品i**:由dp[i - 1][j - weight[i]]推出,dp[i - 1][j - weight[i]] 为背包容量为j - weight[i]的时候不放物品i的最大价值,那么dp[i - 1][j - weight[i]] + value[i] (物品i的价值),就是背包放物品i得到的最大价值 所以递归公式: dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]); @@ -103,7 +103,7 @@ dp[0][j],即:i为0,存放编号0的物品的时候,各个容量的背包 那么很明显当 j < weight[0]的时候,dp[0][j] 应该是 0,因为背包容量比编号0的物品重量还小。 -当j >= weight[0]是,dp[0][j] 应该是value[0],因为背包容量放足够放编号0物品。 +当j >= weight[0]时,dp[0][j] 应该是value[0],因为背包容量放足够放编号0物品。 代码初始化如下: ``` @@ -137,7 +137,7 @@ dp[0][j] 和 dp[i][0] 都已经初始化了,那么其他下标应该初始化 ``` // 初始化 dp -vector> dp(weight.size() + 1, vector(bagWeight + 1, 0)); +vector> dp(weight.size(), vector(bagWeight + 1, 0)); for (int j = weight[0]; j <= bagWeight; j++) { dp[0][j] = value[0]; } @@ -223,14 +223,14 @@ dp[i-1][j]和dp[i - 1][j - weight[i]] 都在dp[i][j]的左上角方向(包括 ## 完整C++测试代码 -```C++ +```CPP void test_2_wei_bag_problem1() { vector weight = {1, 3, 4}; vector value = {15, 20, 30}; int bagWeight = 4; // 二维数组 - vector> dp(weight.size() + 1, vector(bagWeight + 1, 0)); + vector> dp(weight.size(), vector(bagWeight + 1, 0)); // 初始化 for (int j = weight[0]; j <= bagWeight; j++) { @@ -434,4 +434,4 @@ test(); * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/背包理论基础01背包-2.md b/problems/背包理论基础01背包-2.md index ee2fb6d7..07f74186 100644 --- a/problems/背包理论基础01背包-2.md +++ b/problems/背包理论基础01背包-2.md @@ -8,7 +8,7 @@ # 动态规划:关于01背包问题,你该了解这些!(滚动数组) -昨天[动态规划:关于01背包问题,你该了解这些!](https://mp.weixin.qq.com/s/FwIiPPmR18_AJO5eiidT6w)中是用二维dp数组来讲解01背包。 +昨天[动态规划:关于01背包问题,你该了解这些!](https://programmercarl.com/背包理论基础01背包-1.html)中是用二维dp数组来讲解01背包。 今天我们就来说一说滚动数组,其实在前面的题目中我们已经用到过滚动数组了,就是把二维dp降为一维dp,一些录友当时还表示比较困惑。 @@ -60,7 +60,7 @@ dp[j]可以通过dp[j - weight[i]]推导出来,dp[j - weight[i]]表示容量 dp[j - weight[i]] + value[i] 表示 容量为 j - 物品i重量 的背包 加上 物品i的价值。(也就是容量为j的背包,放入物品i了之后的价值即:dp[j]) -此时dp[j]有两个选择,一个是取自己dp[j],一个是取dp[j - weight[i]] + value[i],指定是取最大的,毕竟是求最大价值, +此时dp[j]有两个选择,一个是取自己dp[j] 相当于 二维dp数组中的dp[i-1][j],即不放物品i,一个是取dp[j - weight[i]] + value[i],即放物品i,指定是取最大的,毕竟是求最大价值, 所以递归公式为: @@ -153,7 +153,7 @@ dp[1] = dp[1 - weight[0]] + value[0] = 15 ## 一维dp01背包完整C++测试代码 -```C++ +```CPP void test_1_wei_bag_problem() { vector weight = {1, 3, 4}; vector value = {15, 20, 30}; @@ -199,7 +199,7 @@ int main() { 大家可以发现其实信息量还是挺大的。 -如果把[动态规划:关于01背包问题,你该了解这些!](https://mp.weixin.qq.com/s/FwIiPPmR18_AJO5eiidT6w)和本篇的内容都理解了,后面我们在做01背包的题目,就会发现非常简单了。 +如果把[动态规划:关于01背包问题,你该了解这些!](https://programmercarl.com/背包理论基础01背包-1.html)和本篇的内容都理解了,后面我们在做01背包的题目,就会发现非常简单了。 不用再凭感觉或者记忆去写背包,而是有自己的思考,了解其本质,代码的方方面面都在自己的掌控之中。 @@ -323,4 +323,4 @@ test(); * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/背包问题理论基础多重背包.md b/problems/背包问题理论基础多重背包.md index e14575d4..a2ef88a4 100644 --- a/problems/背包问题理论基础多重背包.md +++ b/problems/背包问题理论基础多重背包.md @@ -5,13 +5,15 @@

欢迎大家参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!

+ + # 动态规划:关于多重背包,你该了解这些! 之前我们已经体统的讲解了01背包和完全背包,如果没有看过的录友,建议先把如下三篇文章仔细阅读一波。 -* [动态规划:关于01背包问题,你该了解这些!](https://mp.weixin.qq.com/s/FwIiPPmR18_AJO5eiidT6w) -* [动态规划:关于01背包问题,你该了解这些!(滚动数组)](https://mp.weixin.qq.com/s/M4uHxNVKRKm5HPjkNZBnFA) -* [动态规划:关于完全背包,你该了解这些!](https://mp.weixin.qq.com/s/akwyxlJ4TLvKcw26KB9uJw) +* [动态规划:关于01背包问题,你该了解这些!](https://programmercarl.com/背包理论基础01背包-1.html) +* [动态规划:关于01背包问题,你该了解这些!(滚动数组)](https://programmercarl.com/背包理论基础01背包-2.html) +* [动态规划:关于完全背包,你该了解这些!](https://programmercarl.com/背包问题理论基础完全背包.html) 这次我们再来说一说多重背包 @@ -56,7 +58,7 @@ 这种方式来实现多重背包的代码如下: -```C++ +```CPP void test_multi_pack() { vector weight = {1, 3, 4}; vector value = {15, 20, 30}; @@ -96,7 +98,7 @@ int main() { 代码如下:(详看注释) -```C++ +```CPP void test_multi_pack() { vector weight = {1, 3, 4}; vector value = {15, 20, 30}; @@ -207,4 +209,4 @@ Go: * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/背包问题理论基础完全背包.md b/problems/背包问题理论基础完全背包.md index 1ad09c4b..455a3c33 100644 --- a/problems/背包问题理论基础完全背包.md +++ b/problems/背包问题理论基础完全背包.md @@ -35,8 +35,8 @@ 关于01背包我如下两篇已经进行深入分析了: -* [动态规划:关于01背包问题,你该了解这些!](https://mp.weixin.qq.com/s/FwIiPPmR18_AJO5eiidT6w) -* [动态规划:关于01背包问题,你该了解这些!(滚动数组)](https://mp.weixin.qq.com/s/M4uHxNVKRKm5HPjkNZBnFA) +* [动态规划:关于01背包问题,你该了解这些!](https://programmercarl.com/背包理论基础01背包-1.html) +* [动态规划:关于01背包问题,你该了解这些!(滚动数组)](https://programmercarl.com/背包理论基础01背包-2.html) 首先在回顾一下01背包的核心代码 ``` @@ -51,7 +51,7 @@ for(int i = 0; i < weight.size(); i++) { // 遍历物品 而完全背包的物品是可以添加多次的,所以要从小到大去遍历,即: -```C++ +```CPP // 先遍历物品,再遍历背包 for(int i = 0; i < weight.size(); i++) { // 遍历物品 for(int j = weight[i]; j < bagWeight ; j++) { // 遍历背包容量 @@ -61,7 +61,7 @@ for(int i = 0; i < weight.size(); i++) { // 遍历物品 } ``` -至于为什么,我在[动态规划:关于01背包问题,你该了解这些!(滚动数组)](https://mp.weixin.qq.com/s/M4uHxNVKRKm5HPjkNZBnFA)中也做了讲解。 +至于为什么,我在[动态规划:关于01背包问题,你该了解这些!(滚动数组)](https://programmercarl.com/背包理论基础01背包-2.html)中也做了讲解。 dp状态图如下: @@ -77,10 +77,10 @@ dp状态图如下: 看过这两篇的话: -* [动态规划:关于01背包问题,你该了解这些!](https://mp.weixin.qq.com/s/FwIiPPmR18_AJO5eiidT6w) -* [动态规划:关于01背包问题,你该了解这些!(滚动数组)](https://mp.weixin.qq.com/s/M4uHxNVKRKm5HPjkNZBnFA) +* [动态规划:关于01背包问题,你该了解这些!](https://programmercarl.com/背包理论基础01背包-1.html) +* [动态规划:关于01背包问题,你该了解这些!(滚动数组)](https://programmercarl.com/背包理论基础01背包-2.html) -就知道了,01背包中二维dp数组的两个for遍历的先后循序是可以颠倒了,一位dp数组的两个for循环先后循序一定是先遍历物品,再遍历背包容量。 +就知道了,01背包中二维dp数组的两个for遍历的先后循序是可以颠倒了,一维dp数组的两个for循环先后循序一定是先遍历物品,再遍历背包容量。 **在完全背包中,对于一维dp数组来说,其实两个for循环嵌套顺序同样无所谓!** @@ -92,13 +92,13 @@ dp状态图如下: 遍历背包容量在外层循环,遍历物品在内层循环,状态如图: -![动态规划-完全背包2](https://img-blog.csdnimg.cn/20210126104741304.jpg) +![动态规划-完全背包2](https://code-thinking-1253855093.file.myqcloud.com/pics/20210729234011.png) 看了这两个图,大家就会理解,完全背包中,两个for循环的先后循序,都不影响计算dp[j]所需要的值(这个值就是下标j之前所对应的dp[j])。 先遍历被背包在遍历物品,代码如下: -```C++ +```CPP // 先遍历背包,再遍历物品 for(int j = 0; j <= bagWeight; j++) { // 遍历背包容量 for(int i = 0; i < weight.size(); i++) { // 遍历物品 @@ -112,7 +112,7 @@ for(int j = 0; j <= bagWeight; j++) { // 遍历背包容量 完整的C++测试代码如下: -```C++ +```CPP // 先遍历物品,在遍历背包 void test_CompletePack() { vector weight = {1, 3, 4}; @@ -132,7 +132,7 @@ int main() { ``` -```C++ +```CPP // 先遍历背包,再遍历物品 void test_CompletePack() { @@ -311,11 +311,42 @@ func main() { fmt.Println(test_CompletePack2(weight, price, 4)) } ``` +Javascript: +```Javascript +// 先遍历物品,再遍历背包容量 +function test_completePack1() { + let weight = [1, 3, 5] + let value = [15, 20, 30] + let bagWeight = 4 + let dp = new Array(bagWeight + 1).fill(0) + for(let i = 0; i <= weight.length; i++) { + for(let j = weight[i]; j <= bagWeight; j++) { + dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]) + } + } + console.log(dp) +} +// 先遍历背包容量,再遍历物品 +function test_completePack2() { + let weight = [1, 3, 5] + let value = [15, 20, 30] + let bagWeight = 4 + let dp = new Array(bagWeight + 1).fill(0) + for(let j = 0; j <= bagWeight; j++) { + for(let i = 0; i < weight.length; i++) { + if (j >= weight[i]) { + dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]) + } + } + } + console.log(2, dp); +} +``` ----------------------- * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/贪心算法总结篇.md b/problems/贪心算法总结篇.md index 999797ad..94e292ba 100644 --- a/problems/贪心算法总结篇.md +++ b/problems/贪心算法总结篇.md @@ -26,7 +26,7 @@ ## 贪心理论基础 -在贪心系列开篇词[关于贪心算法,你该了解这些!](https://mp.weixin.qq.com/s/O935TaoHE9Eexwe_vSbRAg)中,我们就讲解了大家对贪心的普遍疑惑。 +在贪心系列开篇词[关于贪心算法,你该了解这些!](https://programmercarl.com/贪心算法理论基础.html)中,我们就讲解了大家对贪心的普遍疑惑。 1. 贪心很简单,就是常识? @@ -48,42 +48,42 @@ Carl个人认为:如果找出局部最优并可以推出全局最优,就是 就像是 要用一下 1 + 1 = 2,没有必要再证明一下 1 + 1 究竟为什么等于 2。(例子极端了点,但是这个道理) -相信大家读完[关于贪心算法,你该了解这些!](https://mp.weixin.qq.com/s/O935TaoHE9Eexwe_vSbRAg),就对贪心有了一个基本的认识了。 +相信大家读完[关于贪心算法,你该了解这些!](https://programmercarl.com/贪心算法理论基础.html),就对贪心有了一个基本的认识了。 ## 贪心简单题 以下三道题目就是简单题,大家会发现贪心感觉就是常识。是的,如下三道题目,就是靠常识,但我都具体分析了局部最优是什么,全局最优是什么,贪心也要贪的有理有据! -* [贪心算法:分发饼干](https://mp.weixin.qq.com/s/YSuLIAYyRGlyxbp9BNC1uw) -* [贪心算法:K次取反后最大化的数组和](https://mp.weixin.qq.com/s/dMTzBBVllRm_Z0aaWvYazA) -* [贪心算法:柠檬水找零](https://mp.weixin.qq.com/s/0kT4P-hzY7H6Ae0kjQqnZg) +* [贪心算法:分发饼干](https://programmercarl.com/0455.分发饼干.html) +* [贪心算法:K次取反后最大化的数组和](https://programmercarl.com/1005.K次取反后最大化的数组和.html) +* [贪心算法:柠檬水找零](https://programmercarl.com/0860.柠檬水找零.html) ## 贪心中等题 贪心中等题,靠常识可能就有点想不出来了。开始初现贪心算法的难度与巧妙之处。 -* [贪心算法:摆动序列](https://mp.weixin.qq.com/s/Xytl05kX8LZZ1iWWqjMoHA) -* [贪心算法:单调递增的数字](https://mp.weixin.qq.com/s/TAKO9qPYiv6KdMlqNq_ncg) +* [贪心算法:摆动序列](https://programmercarl.com/0376.摆动序列.html) +* [贪心算法:单调递增的数字](https://programmercarl.com/0738.单调递增的数字.html) ### 贪心解决股票问题 大家都知道股票系列问题是动规的专长,其实用贪心也可以解决,而且还不止就这两道题目,但这两道比较典型,我就拿来单独说一说 -* [贪心算法:买卖股票的最佳时机II](https://mp.weixin.qq.com/s/VsTFA6U96l18Wntjcg3fcg) -* [贪心算法:买卖股票的最佳时机含手续费](https://mp.weixin.qq.com/s/olWrUuDEYw2Jx5rMeG7XAg) +* [贪心算法:买卖股票的最佳时机II](https://programmercarl.com/0122.买卖股票的最佳时机II.html) +* [贪心算法:买卖股票的最佳时机含手续费](https://programmercarl.com/0714.买卖股票的最佳时机含手续费.html) ### 两个维度权衡问题 在出现两个维度相互影响的情况时,两边一起考虑一定会顾此失彼,要先确定一个维度,再确定另一个一个维度。 -* [贪心算法:分发糖果](https://mp.weixin.qq.com/s/8MwlgFfvaNYmjGwjuMlETQ) -* [贪心算法:根据身高重建队列](https://mp.weixin.qq.com/s/-2TgZVdOwS-DvtbjjDEbfw) +* [贪心算法:分发糖果](https://programmercarl.com/0135.分发糖果.html) +* [贪心算法:根据身高重建队列](https://programmercarl.com/0406.根据身高重建队列.html) 在讲解本题的过程中,还强调了编程语言的重要性,模拟插队的时候,使用C++中的list(链表)替代了vector(动态数组),效率会高很多。 -所以在[贪心算法:根据身高重建队列(续集)](https://mp.weixin.qq.com/s/K-pRN0lzR-iZhoi-1FgbSQ)详细讲解了,为什么用list(链表)更快! +所以在[贪心算法:根据身高重建队列(续集)](https://programmercarl.com/根据身高重建队列(vector原理讲解).html)详细讲解了,为什么用list(链表)更快! **大家也要掌握自己所用的编程语言,理解其内部实现机制,这样才能写出高效的算法!** @@ -95,21 +95,20 @@ Carl个人认为:如果找出局部最优并可以推出全局最优,就是 关于区间问题,大家应该印象深刻,有一周我们专门讲解的区间问题,各种覆盖各种去重。 -* [贪心算法:跳跃游戏](https://mp.weixin.qq.com/s/606_N9j8ACKCODoCbV1lSA) -* [贪心算法:跳跃游戏II](https://mp.weixin.qq.com/s/kJBcsJ46DKCSjT19pxrNYg) -* [贪心算法:用最少数量的箭引爆气球](https://mp.weixin.qq.com/s/HxVAJ6INMfNKiGwI88-RFw) -* [贪心算法:无重叠区间](https://mp.weixin.qq.com/s/oFOEoW-13Bm4mik-aqAOmw) -* [贪心算法:划分字母区间](https://mp.weixin.qq.com/s/pdX4JwV1AOpc_m90EcO2Hw) -* [贪心算法:合并区间](https://mp.weixin.qq.com/s/royhzEM5tOkUFwUGrNStpw) +* [贪心算法:跳跃游戏](https://programmercarl.com/0055.跳跃游戏.html) +* [贪心算法:跳跃游戏II](https://programmercarl.com/0045.跳跃游戏II.html) +* [贪心算法:用最少数量的箭引爆气球](https://programmercarl.com/0452.用最少数量的箭引爆气球.html) +* [贪心算法:无重叠区间](https://programmercarl.com/0435.无重叠区间.html) +* [贪心算法:划分字母区间](https://programmercarl.com/0763.划分字母区间.html) +* [贪心算法:合并区间](https://programmercarl.com/0056.合并区间.html) ### 其他难题 -[贪心算法:最大子序和](https://mp.weixin.qq.com/s/DrjIQy6ouKbpletQr0g1Fg) 其实是动态规划的题目,但贪心性能更优,很多同学也是第一次发现贪心能比动规更优的题目。 +[贪心算法:最大子序和](https://programmercarl.com/0053.最大子序和.html) 其实是动态规划的题目,但贪心性能更优,很多同学也是第一次发现贪心能比动规更优的题目。 +[贪心算法:加油站](https://programmercarl.com/0134.加油站.html)可能以为是一道模拟题,但就算模拟其实也不简单,需要把while用的很娴熟。但其实是可以使用贪心给时间复杂度降低一个数量级。 -[贪心算法:加油站](https://mp.weixin.qq.com/s/aDbiNuEZIhy6YKgQXvKELw)可能以为是一道模拟题,但就算模拟其实也不简单,需要把while用的很娴熟。但其实是可以使用贪心给时间复杂度降低一个数量级。 - -最后贪心系列压轴题目[贪心算法:我要监控二叉树!](https://mp.weixin.qq.com/s/kCxlLLjWKaE6nifHC3UL2Q),不仅贪心的思路不好想,而且需要对二叉树的操作特别娴熟,这就是典型的交叉类难题了。 +最后贪心系列压轴题目[贪心算法:我要监控二叉树!](https://programmercarl.com/0968.监控二叉树.html),不仅贪心的思路不好想,而且需要对二叉树的操作特别娴熟,这就是典型的交叉类难题了。 ## 贪心每周总结 @@ -120,10 +119,10 @@ Carl个人认为:如果找出局部最优并可以推出全局最优,就是 所以周总结一定要看! -* [本周小结!(贪心算法系列一)](https://mp.weixin.qq.com/s/KQ2caT9GoVXgB1t2ExPncQ) -* [本周小结!(贪心算法系列二)](https://mp.weixin.qq.com/s/RiQri-4rP9abFmq_mlXNiQ) -* [本周小结!(贪心算法系列三)](https://mp.weixin.qq.com/s/JfeuK6KgmifscXdpEyIm-g) -* [本周小结!(贪心算法系列四)](https://mp.weixin.qq.com/s/zAMHT6JfB19ZSJNP713CAQ) +* [本周小结!(贪心算法系列一)](https://programmercarl.com/周总结/20201126贪心周末总结.html) +* [本周小结!(贪心算法系列二)](https://programmercarl.com/周总结/20201203贪心周末总结.html) +* [本周小结!(贪心算法系列三)](https://programmercarl.com/周总结/20201217贪心周末总结.html) +* [本周小结!(贪心算法系列四)](https://programmercarl.com/周总结/20201224贪心周末总结.html) ## 总结 @@ -159,4 +158,4 @@ Go: * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/贪心算法理论基础.md b/problems/贪心算法理论基础.md index 5385aa60..fdb7abf3 100644 --- a/problems/贪心算法理论基础.md +++ b/problems/贪心算法理论基础.md @@ -63,7 +63,7 @@ **那么刷题的时候什么时候真的需要数学推导呢?** -例如这道题目:[链表:环找到了,那入口呢?](https://mp.weixin.qq.com/s/_QVP3IkRZWx9zIpQRgajzA),这道题不用数学推导一下,就找不出环的起始位置,想试一下就不知道怎么试,这种题目确实需要数学简单推导一下。 +例如这道题目:[链表:环找到了,那入口呢?](https://programmercarl.com/0142.环形链表II.html),这道题不用数学推导一下,就找不出环的起始位置,想试一下就不知道怎么试,这种题目确实需要数学简单推导一下。 ## 贪心一般解题步骤 @@ -108,4 +108,4 @@ Go: * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/链表总结篇.md b/problems/链表总结篇.md index 6404dd6e..fed8cb60 100644 --- a/problems/链表总结篇.md +++ b/problems/链表总结篇.md @@ -10,7 +10,7 @@ ## 链表的理论基础 -在这篇文章[关于链表,你该了解这些!](https://mp.weixin.qq.com/s/fDGMmLrW7ZHlzkzlf_dZkw)中,介绍了如下几点: +在这篇文章[关于链表,你该了解这些!](https://programmercarl.com/链表理论基础.html)中,介绍了如下几点: * 链表的种类主要为:单链表,双链表,循环链表 * 链表的存储方式:链表的节点在内存中是分散存储的,通过指针连在一起。 @@ -23,17 +23,17 @@ ### 虚拟头结点 -在[链表:听说用虚拟头节点会方便很多?](https://mp.weixin.qq.com/s/L5aanfALdLEwVWGvyXPDqA)中,我们讲解了链表操作中一个非常总要的技巧:虚拟头节点。 +在[链表:听说用虚拟头节点会方便很多?](https://programmercarl.com/0203.移除链表元素.html)中,我们讲解了链表操作中一个非常总要的技巧:虚拟头节点。 链表的一大问题就是操作当前节点必须要找前一个节点才能操作。这就造成了,头结点的尴尬,因为头结点没有前一个节点了。 **每次对应头结点的情况都要单独处理,所以使用虚拟头结点的技巧,就可以解决这个问题**。 -在[链表:听说用虚拟头节点会方便很多?](https://mp.weixin.qq.com/s/L5aanfALdLEwVWGvyXPDqA)中,我给出了用虚拟头结点和没用虚拟头结点的代码,大家对比一下就会发现,使用虚拟头结点的好处。 +在[链表:听说用虚拟头节点会方便很多?](https://programmercarl.com/0203.移除链表元素.html)中,我给出了用虚拟头结点和没用虚拟头结点的代码,大家对比一下就会发现,使用虚拟头结点的好处。 ### 链表的基本操作 -在[链表:一道题目考察了常见的五个操作!](https://mp.weixin.qq.com/s/jnC_LAD0ZKCsj-FZc57F1g)中,我们通设计链表把链表常见的五个操作练习了一遍。 +在[链表:一道题目考察了常见的五个操作!](https://programmercarl.com/0707.设计链表.html)中,我们通设计链表把链表常见的五个操作练习了一遍。 这是练习链表基础操作的非常好的一道题目,考察了: @@ -49,13 +49,13 @@ ### 反转链表 -在[链表:听说过两天反转链表又写不出来了?](https://mp.weixin.qq.com/s/ckEvIVGcNLfrz6OLOMoT0A)中,讲解了如何反转链表。 +在[链表:听说过两天反转链表又写不出来了?](https://programmercarl.com/0206.翻转链表.html)中,讲解了如何反转链表。 因为反转链表的代码相对简单,有的同学可能直接背下来了,但一写还是容易出问题。 反转链表是面试中高频题目,很考察面试者对链表操作的熟练程度。 -我在[文章](https://mp.weixin.qq.com/s/ckEvIVGcNLfrz6OLOMoT0A)中,给出了两种反转的方式,迭代法和递归法。 +我在[文章](https://programmercarl.com/0206.翻转链表.html)中,给出了两种反转的方式,迭代法和递归法。 建议大家先学透迭代法,然后再看递归法,因为递归法比较绕,如果迭代还写不明白,递归基本也写不明白了。 @@ -63,16 +63,16 @@ ### 删除倒数第N个节点 -在[链表:删除链表倒数第N个节点,怎么删?](https://mp.weixin.qq.com/s/gxu65X1343xW_sBrkTz0Eg)中我们结合虚拟头结点 和 双指针法来移除链表倒数第N个节点。 +在[链表:删除链表倒数第N个节点,怎么删?](https://programmercarl.com/0019.删除链表的倒数第N个节点.html)中我们结合虚拟头结点 和 双指针法来移除链表倒数第N个节点。 ### 链表相交 -[链表:链表相交](https://mp.weixin.qq.com/s/BhfFfaGvt9Zs7UmH4YehZw)使用双指针来找到两个链表的交点(引用完全相同,即:内存地址完全相同的交点) +[链表:链表相交](https://programmercarl.com/面试题02.07.链表相交.html)使用双指针来找到两个链表的交点(引用完全相同,即:内存地址完全相同的交点) ## 环形链表 -在[链表:环找到了,那入口呢?](https://mp.weixin.qq.com/s/gt_VH3hQTqNxyWcl1ECSbQ)中,讲解了在链表如何找环,以及如何找环的入口位置。 +在[链表:环找到了,那入口呢?](https://programmercarl.com/0142.环形链表II.html)中,讲解了在链表如何找环,以及如何找环的入口位置。 这道题目可以说是链表的比较难的题目了。 但代码却十分简洁,主要在于一些数学证明。 @@ -80,15 +80,15 @@ 考察链表的操作其实就是考察指针的操作,是面试中的常见类型。 -链表篇中开头介绍[链表理论知识](https://mp.weixin.qq.com/s/slM1CH5Ew9XzK93YOQYSjA),然后分别通过经典题目介绍了如下知识点: +链表篇中开头介绍[链表理论知识](https://programmercarl.com/0203.移除链表元素.html),然后分别通过经典题目介绍了如下知识点: -1. [关于链表,你该了解这些!](https://mp.weixin.qq.com/s/fDGMmLrW7ZHlzkzlf_dZkw) -2. [虚拟头结点的技巧](https://mp.weixin.qq.com/s/L5aanfALdLEwVWGvyXPDqA) -3. [链表的增删改查](https://mp.weixin.qq.com/s/jnC_LAD0ZKCsj-FZc57F1g) -4. [反转一个链表](https://mp.weixin.qq.com/s/ckEvIVGcNLfrz6OLOMoT0A) -5. [删除倒数第N个节点](https://mp.weixin.qq.com/s/gxu65X1343xW_sBrkTz0Eg) -6. [链表相交](https://mp.weixin.qq.com/s/BhfFfaGvt9Zs7UmH4YehZw) -7. [有否环形,以及环的入口](https://mp.weixin.qq.com/s/gt_VH3hQTqNxyWcl1ECSbQ) +1. [关于链表,你该了解这些!](https://programmercarl.com/链表理论基础.html) +2. [虚拟头结点的技巧](https://programmercarl.com/0203.移除链表元素.html) +3. [链表的增删改查](https://programmercarl.com/0707.设计链表.html) +4. [反转一个链表](https://programmercarl.com/0206.翻转链表.html) +5. [删除倒数第N个节点](https://programmercarl.com/0019.删除链表的倒数第N个节点.html) +6. [链表相交](https://programmercarl.com/面试题02.07.链表相交.html) +7. [有否环形,以及环的入口](https://programmercarl.com/0142.环形链表II.html) @@ -98,4 +98,4 @@ * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/链表理论基础.md b/problems/链表理论基础.md index 252247c7..d210b6bd 100644 --- a/problems/链表理论基础.md +++ b/problems/链表理论基础.md @@ -159,4 +159,4 @@ Go: * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+
diff --git a/problems/面试题02.07.链表相交.md b/problems/面试题02.07.链表相交.md index c6779427..d59256c6 100644 --- a/problems/面试题02.07.链表相交.md +++ b/problems/面试题02.07.链表相交.md @@ -9,7 +9,7 @@ ## 面试题 02.07. 链表相交 -题目链接:https://leetcode-cn.com/problems/intersection-of-two-linked-lists-lcci/ +[力扣题目链接](https://leetcode-cn.com/problems/intersection-of-two-linked-lists-lcci/) 给定两个(单向)链表,判定它们是否相交并返回交点。请注意相交的定义基于节点的引用,而不是基于节点的值。换句话说,如果一个链表的第k个节点与另一个链表的第j个节点是同一节点(引用完全相同),则这两个链表相交。 @@ -44,7 +44,7 @@ C++代码如下: -```C++ +```CPP class Solution { public: ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) { @@ -160,34 +160,21 @@ Python: class Solution: def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode: - lengthA,lengthB = 0,0 - curA,curB = headA,headB - while(curA!=None): #求链表A的长度 - curA = curA.next - lengthA +=1 - - while(curB!=None): #求链表B的长度 - curB = curB.next - lengthB +=1 - - curA, curB = headA, headB + """ + 根据快慢法则,走的快的一定会追上走得慢的。 + 在这道题里,有的链表短,他走完了就去走另一条链表,我们可以理解为走的快的指针。 - if lengthB>lengthA: #让curA为最长链表的头,lenA为其长度 - lengthA, lengthB = lengthB, lengthA - curA, curB = curB, curA + 那么,只要其中一个链表走完了,就去走另一条链表的路。如果有交点,他们最终一定会在同一个 + 位置相遇 + """ + cur_a, cur_b = headA, headB # 用两个指针代替a和b - gap = lengthA - lengthB #求长度差 - while(gap!=0): - curA = curA.next #让curA和curB在同一起点上 - gap -= 1 - while(curA!=None): - if curA == curB: - return curA - else: - curA = curA.next - curB = curB.next - return None + while cur_a != cur_b: + cur_a = cur_a.next if cur_a else headB # 如果a走完了,那么就切换到b走 + cur_b = cur_b.next if cur_b else headA # 同理,b走完了就切换到a + + return cur_a ``` Go: @@ -271,4 +258,4 @@ var getIntersectionNode = function(headA, headB) { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
+