From 029cf012eef7729e01bf530fb29ce131bd8ee287 Mon Sep 17 00:00:00 2001 From: youngyangyang04 <826123027@qq.com> Date: Fri, 6 Aug 2021 15:34:33 +0800 Subject: [PATCH] Update --- README.md | 1 + problems/0072.编辑距离.md | 4 +- problems/0084.柱状图中最大的矩形.md | 193 ++++++++ problems/0101.对称二叉树.md | 2 + problems/0102.二叉树的层序遍历.md | 430 ++++++------------ problems/0104.二叉树的最大深度.md | 294 ++++++------ problems/0110.平衡二叉树.md | 43 +- problems/0111.二叉树的最小深度.md | 20 +- .../0222.完全二叉树的节点个数.md | 57 ++- problems/0257.二叉树的所有路径.md | 113 +++-- problems/0343.整数拆分.md | 4 + problems/0941.有效的山脉数组.md | 68 ++- problems/二叉树中递归带着回溯.md | 6 +- .../20201003二叉树周末总结.md | 4 +- problems/数组理论基础.md | 2 +- problems/背包理论基础01背包-1.md | 4 +- problems/背包理论基础01背包-2.md | 2 +- .../背包问题理论基础完全背包.md | 2 +- 18 files changed, 674 insertions(+), 575 deletions(-) create mode 100644 problems/0084.柱状图中最大的矩形.md diff --git a/README.md b/README.md index bbceda2e..1de973a1 100644 --- a/README.md +++ b/README.md @@ -409,6 +409,7 @@ 2. [单调栈:下一个更大元素I](./problems/0496.下一个更大元素I.md) 3. [单调栈:下一个更大元素II](./problems/0503.下一个更大元素II.md) 4. [单调栈:接雨水](./problems/0042.接雨水.md) +5. [单调栈:柱状图中最大的矩形](./problems/0084.柱状图中最大的矩形.md) (持续更新中....) diff --git a/problems/0072.编辑距离.md b/problems/0072.编辑距离.md index 9ddca7c0..3a3b584a 100644 --- a/problems/0072.编辑距离.md +++ b/problems/0072.编辑距离.md @@ -91,12 +91,12 @@ 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增加一个元素,使其word1[i - 1]与word2[j - 1]相同,那么就是以下标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添加一个元素,使其word1[i - 1]与word2[j - 1]相同,那么就是以下标i-1为结尾的word1 与 j-2为结尾的word2的最近编辑距离 加上一个增加元素的操作。 即 `dp[i][j] = dp[i][j - 1] + 1;` diff --git a/problems/0084.柱状图中最大的矩形.md b/problems/0084.柱状图中最大的矩形.md new file mode 100644 index 00000000..1abe6623 --- /dev/null +++ b/problems/0084.柱状图中最大的矩形.md @@ -0,0 +1,193 @@ + + +# 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://mp.weixin.qq.com/s/QogENxhotboct9wn7GgYUw),是遥相呼应的两道题目,建议都要仔细做一做,原理上有很多相同的地方,但细节上又有差异,更可以加深对单调栈的理解! + +其实这两道题目先做那一道都可以,但我先写的42.接雨水的题解,所以如果没做过接雨水的话,建议先做一做接雨水,可以参考我的题解:[42. 接雨水](https://mp.weixin.qq.com/s/QogENxhotboct9wn7GgYUw) + +我们先来看一下双指针的解法: + +## 双指针解法 + +```C++ +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://mp.weixin.qq.com/s/QogENxhotboct9wn7GgYUw)是一致的,但要比[42. 接雨水](https://mp.weixin.qq.com/s/QogENxhotboct9wn7GgYUw)难一些。 + +难就难在本题要记录记录每个柱子 左边第一个小于该柱子的下标,而不是左边第一个小于该柱子的高度。 + +所以需要循环查找,也就是下面在寻找的过程中使用了while,详细请看下面注释,整理思路在题解:[42. 接雨水](https://mp.weixin.qq.com/s/QogENxhotboct9wn7GgYUw)中已经介绍了。 + +```C++ +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://mp.weixin.qq.com/s/QogENxhotboct9wn7GgYUw)是找每个柱子左右两边第一个大于该柱子高度的柱子,而本题是找每个柱子左右两边第一个小于该柱子的柱子。 + +**这里就涉及到了单调栈很重要的性质,就是单调栈里的顺序,是从小到大还是从大到小**。 + +在题解[42. 接雨水](https://mp.weixin.qq.com/s/QogENxhotboct9wn7GgYUw)中我讲解了接雨水的单调栈从栈头(元素从栈头弹出)到栈底的顺序应该是从小到大的顺序。 + +那么因为本题是要找每个柱子左右两边第一个小于该柱子的柱子,所以从栈头(元素从栈头弹出)到栈底的顺序应该是从大到小的顺序! + +我来举一个例子,如图: + +![84.柱状图中最大的矩形](https://img-blog.csdnimg.cn/20210223155303971.jpg) + +只有栈里从大到小的顺序,才能保证栈顶元素找到左右两边第一个小于栈顶元素的柱子。 + +所以本题单调栈的顺序正好与接雨水反过来。 + +此时大家应该可以发现其实就是**栈顶和栈顶的下一个元素以及要入栈的三个元素组成了我们要求最大面积的高度和宽度** + +理解这一点,对单调栈就掌握的比较到位了。 + +除了栈内元素顺序和接雨水不同,剩下的逻辑就都差不多了,在题解[42. 接雨水](https://mp.weixin.qq.com/s/QogENxhotboct9wn7GgYUw)我已经对单调栈的各个方面做了详细讲解,这里就不赘述了。 + +剩下就是分析清楚如下三种情况: + +* 情况一:当前遍历的元素heights[i]小于栈顶元素heights[st.top()]的情况 +* 情况二:当前遍历的元素heights[i]等于栈顶元素heights[st.top()]的情况 +* 情况三:当前遍历的元素heights[i]大于栈顶元素heights[st.top()]的情况 + +C++代码如下: + +```C++ +// 版本一 +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; + } +}; + +``` + +代码精简之后: + +```C++ +// 版本二 +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; + } +}; +``` + +这里我依然建议大家按部就班把版本一写出来,把情况一二三分析清楚,然后在精简代码到版本二。 直接看版本二容易忽略细节! + diff --git a/problems/0101.对称二叉树.md b/problems/0101.对称二叉树.md index 241564e9..9f3ceb73 100644 --- a/problems/0101.对称二叉树.md +++ b/problems/0101.对称二叉树.md @@ -251,6 +251,8 @@ public: # 相关题目推荐 +这两道题目基本和本题是一样的,只要稍加修改就可以AC。 + * 100.相同的树 * 572.另一个树的子树 diff --git a/problems/0102.二叉树的层序遍历.md b/problems/0102.二叉树的层序遍历.md index 8be3ac47..f63373fc 100644 --- a/problems/0102.二叉树的层序遍历.md +++ b/problems/0102.二叉树的层序遍历.md @@ -6,11 +6,8 @@

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

-# 二叉树的层序遍历 -看完这篇文章虽然不能打十个,但是可以迅速打八个!而且够快! - -学会二叉树的层序遍历,可以一口气撸完leetcode上八道题目: +学会二叉树的层序遍历,可以一口气打完以下十题: * 102.二叉树的层序遍历 * 107.二叉树的层次遍历II @@ -20,9 +17,16 @@ * 515.在每个树行中找最大值 * 116.填充每个节点的下一个右侧节点指针 * 117.填充每个节点的下一个右侧节点指针II +* 104.二叉树的最大深度 +* 111.二叉树的最小深度 + +在之前写过这篇文章 [二叉树:层序遍历登场!](https://mp.weixin.qq.com/s/Gb3BjakIKGNpup2jYtTzog),可惜当时只打了5个,还不够,再给我一次机会,我打十个! + +![我要打十个](https://tva1.sinaimg.cn/large/008eGmZEly1gnadnltbpjg309603w4qp.gif) -## 102.二叉树的层序遍历 + +# 102.二叉树的层序遍历 题目地址:https://leetcode-cn.com/problems/binary-tree-level-order-traversal/ @@ -38,7 +42,6 @@ * [二叉树:前中后序迭代法](https://mp.weixin.qq.com/s/OH7aCVJ5-Gi32PkNCoZk4A) * [二叉树:前中后序迭代方式统一写法](https://mp.weixin.qq.com/s/ATQMPCpBlaAgrqdLDMVPZA) - 接下来我们再来介绍二叉树的另一种遍历方式:层序遍历。 层序遍历一个二叉树。就是从左到右一层一层的去遍历二叉树。这种遍历的方式和我们之前讲过的都不太一样。 @@ -53,11 +56,11 @@ 这样就实现了层序从左到右遍历二叉树。 -代码如下:**这份代码也可以作为二叉树层序遍历的模板,以后再打七个就靠它了**。 +代码如下:**这份代码也可以作为二叉树层序遍历的模板,打十个就靠它了**。 C++代码: -``` +```C++ class Solution { public: vector> levelOrder(TreeNode* root) { @@ -225,9 +228,10 @@ var levelOrder = function(root) { ``` -**此时我们就掌握了二叉树的层序遍历了,那么如下五道leetcode上的题目,只需要修改模板的一两行代码(不能再多了),便可打倒!** +**此时我们就掌握了二叉树的层序遍历了,那么如下九道力扣上的题目,只需要修改模板的两三行代码(不能再多了),便可打倒!** -## 107.二叉树的层次遍历 II + +# 107.二叉树的层次遍历 II 题目链接:https://leetcode-cn.com/problems/binary-tree-level-order-traversal-ii/ @@ -404,7 +408,7 @@ var levelOrderBottom = function(root) { ``` -## 199.二叉树的右视图 +# 199.二叉树的右视图 题目链接:https://leetcode-cn.com/problems/binary-tree-right-side-view/ @@ -581,7 +585,7 @@ var rightSideView = function(root) { }; ``` -## 637.二叉树的层平均值 +# 637.二叉树的层平均值 题目链接:https://leetcode-cn.com/problems/average-of-levels-in-binary-tree/ @@ -765,7 +769,7 @@ var averageOfLevels = function(root) { }; ``` -## 429.N叉树的层序遍历 +# 429.N叉树的层序遍历 题目链接:https://leetcode-cn.com/problems/n-ary-tree-level-order-traversal/ @@ -985,7 +989,7 @@ var levelOrder = function(root) { }; ``` -## 515.在每个树行中找最大值 +# 515.在每个树行中找最大值 题目链接:https://leetcode-cn.com/problems/find-largest-value-in-each-tree-row/ @@ -1115,7 +1119,7 @@ var largestValues = function(root) { }; ``` -## 116.填充每个节点的下一个右侧节点指针 +# 116.填充每个节点的下一个右侧节点指针 题目链接:https://leetcode-cn.com/problems/populating-next-right-pointers-in-each-node/ @@ -1254,7 +1258,7 @@ func connect(root *Node) *Node { } ``` -## 117.填充每个节点的下一个右侧节点指针II +# 117.填充每个节点的下一个右侧节点指针II 题目地址:https://leetcode-cn.com/problems/populating-next-right-pointers-in-each-node-ii/ @@ -1295,6 +1299,7 @@ public: } }; ``` + python代码: ```python @@ -1378,12 +1383,119 @@ 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++代码如下: + +```C++ +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: + + +Python: + +Go: + +JavaScript: + +# 111.二叉树的最小深度 + +相对于 104.二叉树的最大深度 ,本题还也可以使用层序遍历的方式来解决,思路是一样的。 + +**需要注意的是,只有当左右孩子都为空的时候,才说明遍历的最低点了。如果其中一个孩子为空则不是最低点** + +代码如下:(详细注释) + +```C++ +class Solution { +public: + int minDepth(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); + if (!node->left && !node->right) { // 当左右孩子都为空的时候,说明是最低点的一层了,退出 + return depth; + } + } + } + return depth; + } +}; +``` + +Java: + + +Python: + +Go: + +JavaScript: + + + + +# 总结 + +二叉树的层序遍历,**就是图论中的广度优先搜索在二叉树中的应用**,需要借助队列来实现(此时又发现队列的一个应用了)。 + +来吧,一口气打十个: * 102.二叉树的层序遍历 * 107.二叉树的层次遍历II @@ -1393,287 +1505,13 @@ func connect(root *Node) *Node { * 515.在每个树行中找最大值 * 116.填充每个节点的下一个右侧节点指针 * 117.填充每个节点的下一个右侧节点指针II +* 104.二叉树的最大深度 +* 111.二叉树的最小深度 -如果非要打十个,还得找叶师傅! - -![我要打十个](https://tva1.sinaimg.cn/large/008eGmZEly1gnadnltbpjg309603w4qp.gif) +**致敬叶师傅!** - -# 其他语言版本 - - -> 二叉树的层序遍历(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) diff --git a/problems/0104.二叉树的最大深度.md b/problems/0104.二叉树的最大深度.md index 218a966c..252f9e61 100644 --- a/problems/0104.二叉树的最大深度.md +++ b/problems/0104.二叉树的最大深度.md @@ -8,10 +8,11 @@ 看完本篇可以一起做了如下两道题目: -* 104.二叉树的最大深度 -* 559.N叉树的最大深度 -## 104.二叉树的最大深度 +* 104.二叉树的最大深度 +* 559.n叉树的最大深度 + +# 104.二叉树的最大深度 题目地址: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,31 +99,31 @@ 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); + getdepth(root, 1); return result; } }; @@ -132,31 +133,31 @@ 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) { // 左 - getDepth(node->left, depth + 1); + getdepth(node->left, depth + 1); } if (node->right) { // 右 - getDepth(node->right, depth + 1); + getdepth(node->right, depth + 1); } return ; } - int maxDepth(TreeNode* root) { + int maxdepth(treenode* root) { result = 0; if (root == 0) return result; - getDepth(root, 1); + getdepth(root, 1); return result; } }; ``` -### 迭代法 +## 迭代法 使用迭代法的话,使用层序遍历是最为合适的,因为最大的深度就是二叉树的层数,和层序遍历的方式极其吻合。 @@ -166,23 +167,23 @@ public: 所以这道题的迭代法就是一道模板题,可以使用二叉树层序遍历的模板来解决的。 -如果对层序遍历还不清楚的话,可以看这篇:[二叉树:层序遍历登场!](https://mp.weixin.qq.com/s/Gb3BjakIKGNpup2jYtTzog) +如果对层序遍历还不清楚的话,可以看这篇:[二叉树:层序遍历登场!](https://mp.weixin.qq.com/s/4-bDKi7SdwfBGRm9FYduiA) -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/ -给定一个 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,39 @@ class Solution { } ``` -Python: +## python -104.二叉树的最大深度 -> 递归法: +### 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 +361,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 +395,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 +406,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 +422,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 +440,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,46 +474,49 @@ 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) { +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); diff --git a/problems/0110.平衡二叉树.md b/problems/0110.平衡二叉树.md index b6e50853..324b3678 100644 --- a/problems/0110.平衡二叉树.md +++ b/problems/0110.平衡二叉树.md @@ -9,7 +9,7 @@ > 求高度还是求深度,你搞懂了不? -## 110.平衡二叉树 +# 110.平衡二叉树 题目地址:https://leetcode-cn.com/problems/balanced-binary-tree/ @@ -33,9 +33,10 @@ 返回 false 。 -## 题外话 +# 题外话 -咋眼一看这道题目和[104.二叉树的最大深度](https://mp.weixin.qq.com/s/guKwV-gSNbA1CcbvkMtHBg)很像,其实有很大区别。 + +咋眼一看这道题目和[104.二叉树的最大深度](https://mp.weixin.qq.com/s/jRaRcRerhEHepQbt-aKstw)很像,其实有很大区别。 这里强调一波概念: @@ -50,11 +51,11 @@ 因为求深度可以从上到下去查 所以需要前序遍历(中左右),而高度只能从下到上去查,所以只能后序遍历(左右中) -有的同学一定疑惑,为什么[104.二叉树的最大深度](https://mp.weixin.qq.com/s/guKwV-gSNbA1CcbvkMtHBg)中求的是二叉树的最大深度,也用的是后序遍历。 +有的同学一定疑惑,为什么[104.二叉树的最大深度](https://mp.weixin.qq.com/s/jRaRcRerhEHepQbt-aKstw)中求的是二叉树的最大深度,也用的是后序遍历。 **那是因为代码的逻辑其实是求的根节点的高度,而根节点的高度就是这颗树的最大深度,所以才可以使用后序遍历。** -在[104.二叉树的最大深度](https://mp.weixin.qq.com/s/guKwV-gSNbA1CcbvkMtHBg)中,如果真正求取二叉树的最大深度,代码应该写成如下:(前序遍历) +在[104.二叉树的最大深度](https://mp.weixin.qq.com/s/jRaRcRerhEHepQbt-aKstw)中,如果真正求取二叉树的最大深度,代码应该写成如下:(前序遍历) ```C++ class Solution { @@ -114,9 +115,9 @@ public: }; ``` -## 本题思路 +# 本题思路 -### 递归 +## 递归 此时大家应该明白了既然要求比较高度,必然是要后序遍历。 @@ -160,7 +161,7 @@ if (node == NULL) { 代码如下: -``` +```C++ int leftDepth = depth(node->left); // 左 if (leftDepth == -1) return -1; int rightDepth = depth(node->right); // 右 @@ -178,7 +179,7 @@ return result; 代码精简之后如下: -``` +```C++ int leftDepth = getDepth(node->left); if (leftDepth == -1) return -1; int rightDepth = getDepth(node->right); @@ -225,9 +226,9 @@ public: }; ``` -### 迭代 +## 迭代 -在[104.二叉树的最大深度](https://mp.weixin.qq.com/s/guKwV-gSNbA1CcbvkMtHBg)中我们可以使用层序遍历来求深度,但是就不能直接用层序遍历来求高度了,这就体现出求高度和求深度的不同。 +在[104.二叉树的最大深度](https://mp.weixin.qq.com/s/jRaRcRerhEHepQbt-aKstw)中我们可以使用层序遍历来求深度,但是就不能直接用层序遍历来求高度了,这就体现出求高度和求深度的不同。 本题的迭代方式可以先定义一个函数,专门用来求高度。 @@ -266,7 +267,7 @@ int getDepth(TreeNode* cur) { 然后再用栈来模拟前序遍历,遍历每一个节点的时候,再去判断左右孩子的高度是否符合,代码如下: -``` +```C++ bool isBalanced(TreeNode* root) { stack st; if (root == NULL) return true; @@ -286,7 +287,7 @@ bool isBalanced(TreeNode* root) { 整体代码如下: -``` +```C++ 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 diff --git a/problems/0111.二叉树的最小深度.md b/problems/0111.二叉树的最小深度.md index a36faeff..2ef380ab 100644 --- a/problems/0111.二叉树的最小深度.md +++ b/problems/0111.二叉树的最小深度.md @@ -9,7 +9,7 @@ > 和求最大深度一个套路? -## 111.二叉树的最小深度 +# 111.二叉树的最小深度 题目地址: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://mp.weixin.qq.com/s/jRaRcRerhEHepQbt-aKstw),再来看看如何求最小深度。 直觉上好像和求最大深度差不多,其实还是差不少的。 @@ -154,9 +154,9 @@ public: ## 迭代法 -相对于[104.二叉树的最大深度](https://mp.weixin.qq.com/s/guKwV-gSNbA1CcbvkMtHBg),本题还可以使用层序遍历的方式来解决,思路是一样的。 +相对于[104.二叉树的最大深度](https://mp.weixin.qq.com/s/jRaRcRerhEHepQbt-aKstw),本题还可以使用层序遍历的方式来解决,思路是一样的。 -如果对层序遍历还不清楚的话,可以看这篇:[二叉树:层序遍历登场!](https://mp.weixin.qq.com/s/Gb3BjakIKGNpup2jYtTzog) +如果对层序遍历还不清楚的话,可以看这篇:[二叉树:层序遍历登场!](https://mp.weixin.qq.com/s/4-bDKi7SdwfBGRm9FYduiA) **需要注意的是,只有当左右孩子都为空的时候,才说明遍历的最低点了。如果其中一个孩子为空则不是最低点** @@ -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 递归法: diff --git a/problems/0222.完全二叉树的节点个数.md b/problems/0222.完全二叉树的节点个数.md index ec68b6c6..c9319372 100644 --- a/problems/0222.完全二叉树的节点个数.md +++ b/problems/0222.完全二叉树的节点个数.md @@ -7,24 +7,23 @@

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

-## 222.完全二叉树的节点个数 +# 222.完全二叉树的节点个数 题目地址: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://mp.weixin.qq.com/s/4-bDKi7SdwfBGRm9FYduiA)遍历模板稍稍修改一下,记录遍历的节点数量就可以了。 递归遍历的顺序依然是后序(左右中)。 ### 递归 -如果对求二叉树深度还不熟悉的话,看这篇:[二叉树:看看这些树的最大深度](https://mp.weixin.qq.com/s/guKwV-gSNbA1CcbvkMtHBg)。 +如果对求二叉树深度还不熟悉的话,看这篇:[二叉树:看看这些树的最大深度](https://mp.weixin.qq.com/s/jRaRcRerhEHepQbt-aKstw)。 1. 确定递归函数的参数和返回值:参数就是传入树的根节点,返回就返回以该节点为根节点二叉树的节点数量,所以返回值为int类型。 @@ -107,15 +107,15 @@ public: }; ``` -时间复杂度:O(n) -空间复杂度:O(logn),算上了递归系统栈占用的空间 +* 时间复杂度:O(n) +* 空间复杂度:O(logn),算上了递归系统栈占用的空间 **网上基本都是这个精简的代码版本,其实不建议大家照着这个来写,代码确实精简,但隐藏了一些内容,连遍历的顺序都看不出来,所以初学者建议学习版本一的代码,稳稳的打基础**。 ### 迭代法 -如果对求二叉树层序遍历还不熟悉的话,看这篇:[二叉树:层序遍历登场!](https://mp.weixin.qq.com/s/Gb3BjakIKGNpup2jYtTzog)。 +如果对求二叉树层序遍历还不熟悉的话,看这篇:[二叉树:层序遍历登场!](https://mp.weixin.qq.com/s/4-bDKi7SdwfBGRm9FYduiA)。 那么只要模板少做改动,加一个变量result,统计节点数量就可以了 @@ -140,12 +140,12 @@ public: } }; ``` -时间复杂度:O(n) -空间复杂度:O(n) +* 时间复杂度:O(n) +* 空间复杂度:O(n) ## 完全二叉树 -以上方法都是按照普通二叉树来做的,对于完全二叉树特性不了解的同学可以看这篇 [关于二叉树,你该了解这些!](https://mp.weixin.qq.com/s/_ymfWYvTNd2GvWvC5HOE4A),这篇详细介绍了各种二叉树的特性。 +以上方法都是按照普通二叉树来做的,对于完全二叉树特性不了解的同学可以看这篇 [关于二叉树,你该了解这些!](https://mp.weixin.qq.com/s/q_eKfL8vmSbSFcptZ3aeRA),这篇详细介绍了各种二叉树的特性。 完全二叉树只有两种情况,情况一:就是满二叉树,情况二:最后一层叶子节点没有满。 @@ -187,13 +187,12 @@ public: }; ``` -时间复杂度:O(logn * logn) -空间复杂度:O(logn) +* 时间复杂度:O(logn * logn) +* 空间复杂度:O(logn) -## 其他语言版本 +# 其他语言版本 - -Java: +## Java ```java class Solution { // 通用递归解法 @@ -238,9 +237,9 @@ class Solution { } ``` -Python: +## Python -> 递归法: +递归法: ```python class Solution: def countNodes(self, root: TreeNode) -> int: @@ -255,7 +254,7 @@ class Solution: return treeNum ``` -> 递归法:精简版 +递归法:精简版 ```python class Solution: def countNodes(self, root: TreeNode) -> int: @@ -264,7 +263,7 @@ class Solution: return 1 + self.countNodes(root.left) + self.countNodes(root.right) ``` -> 迭代法: +迭代法: ```python import collections class Solution: @@ -285,7 +284,7 @@ class Solution: return result ``` -> 完全二叉树 +完全二叉树 ```python class Solution: def countNodes(self, root: TreeNode) -> int: @@ -306,7 +305,7 @@ class Solution: return self.countNodes(root.left) + self.countNodes(root.right) + 1 ``` -Go: +## Go 递归版本 @@ -361,7 +360,7 @@ func countNodes(root *TreeNode) int { -JavaScript: +## JavaScript: 递归版本 ```javascript diff --git a/problems/0257.二叉树的所有路径.md b/problems/0257.二叉树的所有路径.md index ce596396..bb0513de 100644 --- a/problems/0257.二叉树的所有路径.md +++ b/problems/0257.二叉树的所有路径.md @@ -9,7 +9,7 @@ > 以为只用了递归,其实还用了回溯 -## 257. 二叉树的所有路径 +# 257. 二叉树的所有路径 题目地址:https://leetcode-cn.com/problems/binary-tree-paths/ @@ -20,7 +20,7 @@ 示例: ![257.二叉树的所有路径1](https://img-blog.csdnimg.cn/2021020415161576.png) -## 思路 +# 思路 这道题目要求从根节点到叶子的路径,所以需要前序遍历,这样才方便让父节点指向孩子节点,找到对应的路径。 @@ -215,8 +215,52 @@ public: 那么在如上代码中,**貌似没有看到回溯的逻辑,其实不然,回溯就隐藏在`traversal(cur->left, path + "->", result);`中的 `path + "->"`。** 每次函数调用完,path依然是没有加上"->" 的,这就是回溯了。 -**如果这里还不理解的话,可以看这篇[二叉树:以为使用了递归,其实还隐藏着回溯](https://mp.weixin.qq.com/s/ivLkHzWdhjQQD1rQWe6zWA),我这这篇中详细的解释了递归中如何隐藏着回溯。 ** +为了把这份精简代码的回溯过程展现出来,大家可以试一试把: +```C++ +if (cur->left) traversal(cur->left, path + "->", result); // 左 回溯就隐藏在这里 +``` + +改成如下代码: + +```C++ +path += "->"; +traversal(cur->left, path, result); // 左 +``` + +即: + +```C++ +if (cur->left) { + path += "->"; + traversal(cur->left, path, result); // 左 +} +if (cur->right) { + path += "->"; + traversal(cur->right, path, result); // 右 +} +``` + +此时就没有回溯了,这个代码就是通过不了的了。 + +如果想把回溯加上,就要 在上面代码的基础上,加上回溯,就可以AC了。 + +```C++ +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,7 +269,8 @@ public: ## 迭代法 -至于非递归的方式,我们可以依然可以使用前序遍历的迭代方式来模拟遍历路径的过程,对该迭代方式不了解的同学,可以看文章[二叉树:听说递归能做的,栈也能做!](https://mp.weixin.qq.com/s/c_zCrGHIVlBjUH_hJtghCg)和[二叉树:前中后序迭代方式的写法就不能统一一下么?](https://mp.weixin.qq.com/s/WKg0Ty1_3SZkztpHubZPRg)。 + +至于非递归的方式,我们可以依然可以使用前序遍历的迭代方式来模拟遍历路径的过程,对该迭代方式不了解的同学,可以看文章[二叉树:听说递归能做的,栈也能做!](https://mp.weixin.qq.com/s/OH7aCVJ5-Gi32PkNCoZk4A)和[二叉树:前中后序迭代方式统一写法](https://mp.weixin.qq.com/s/ATQMPCpBlaAgrqdLDMVPZA)。 这里除了模拟递归需要一个栈,同时还需要一个栈来存放对应的遍历路径。 @@ -262,7 +307,7 @@ public: ``` 当然,使用java的同学,可以直接定义一个成员变量为object的栈`Stack stack = new Stack<>();`,这样就不用定义两个栈了,都放到一个栈里就可以了。 -## 总结 +# 总结 **本文我们开始初步涉及到了回溯,很多同学过了这道题目,可能都不知道自己其实使用了回溯,回溯和递归都是相伴相生的。** @@ -278,7 +323,7 @@ public: -## 其他语言版本 +# 其他语言版本 Java: @@ -321,62 +366,10 @@ class Solution { } } } - -//解法二(常规前序遍历,不用回溯),更容易理解 -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; - } - helper(root.left,new StringBuilder(sb).append(root.val + "->"),res); - helper(root.right,new StringBuilder(sb).append(root.val + "->"),res); - } -} - -//针对解法二优化,思路本质是一样的 -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=[] @@ -396,8 +389,8 @@ class Solution: return ["->".join(list(map(str,i))) for i in res] ``` -Go: +Go: ```go func binaryTreePaths(root *TreeNode) []string { res := make([]string, 0) @@ -422,7 +415,9 @@ func binaryTreePaths(root *TreeNode) []string { ``` JavaScript: + 1.递归版本 + ```javascript var binaryTreePaths = function(root) { //递归遍历+递归三部曲 diff --git a/problems/0343.整数拆分.md b/problems/0343.整数拆分.md index 7b0dbd0f..ec1ed91a 100644 --- a/problems/0343.整数拆分.md +++ b/problems/0343.整数拆分.md @@ -59,6 +59,10 @@ j是从1开始遍历,拆分j的情况,在遍历j的过程中其实都计算 所以递推公式:dp[i] = max({dp[i], (i - j) * j, dp[i - j] * j}); +那么在取最大值的时候,为什么还要比较dp[i]呢? + +因为在递推公式推导的过程中,每次计算dp[i],取最大的而已。 + 3. dp的初始化 diff --git a/problems/0941.有效的山脉数组.md b/problems/0941.有效的山脉数组.md index 167cfb1a..bf3f232e 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://mp.weixin.qq.com/s/_p7grwjISfMh0U65uOyCjA) + +# 其他语言版本 + +## 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/二叉树中递归带着回溯.md b/problems/二叉树中递归带着回溯.md index 372dc40c..bd987f12 100644 --- a/problems/二叉树中递归带着回溯.md +++ b/problems/二叉树中递归带着回溯.md @@ -120,13 +120,13 @@ public: 为了把这份精简代码的回溯过程展现出来,大家可以试一试把: -``` +```C++ if (cur->left) traversal(cur->left, path + "->", result); // 左 回溯就隐藏在这里 ``` 改成如下代码: -``` +```C++ path += "->"; traversal(cur->left, path, result); // 左 ``` @@ -149,7 +149,7 @@ if (cur->right) { 如果想把回溯加上,就要 在上面代码的基础上,加上回溯,就可以AC了。 -``` +```C++ if (cur->left) { path += "->"; traversal(cur->left, path, result); // 左 diff --git a/problems/周总结/20201003二叉树周末总结.md b/problems/周总结/20201003二叉树周末总结.md index 0cb8b654..e69e36f8 100644 --- a/problems/周总结/20201003二叉树周末总结.md +++ b/problems/周总结/20201003二叉树周末总结.md @@ -223,12 +223,12 @@ public: 文中我明确的说了:**回溯就隐藏在traversal(cur->left, path + "->", result);中的 path + "->"。 每次函数调用完,path依然是没有加上"->" 的,这就是回溯了。** 如果还不理解的话,可以把 -``` +```C++ traversal(cur->left, path + "->", result); ``` 改成 -``` +```C++ string tmp = path + "->"; traversal(cur->left, tmp, result); ``` diff --git a/problems/数组理论基础.md b/problems/数组理论基础.md index b2837375..77ecae9d 100644 --- a/problems/数组理论基础.md +++ b/problems/数组理论基础.md @@ -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。 diff --git a/problems/背包理论基础01背包-1.md b/problems/背包理论基础01背包-1.md index 3f603366..9f09103d 100644 --- a/problems/背包理论基础01背包-1.md +++ b/problems/背包理论基础01背包-1.md @@ -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]); diff --git a/problems/背包理论基础01背包-2.md b/problems/背包理论基础01背包-2.md index ee2fb6d7..598a7be0 100644 --- a/problems/背包理论基础01背包-2.md +++ b/problems/背包理论基础01背包-2.md @@ -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,指定是取最大的,毕竟是求最大价值, 所以递归公式为: diff --git a/problems/背包问题理论基础完全背包.md b/problems/背包问题理论基础完全背包.md index 1ad09c4b..8f91d606 100644 --- a/problems/背包问题理论基础完全背包.md +++ b/problems/背包问题理论基础完全背包.md @@ -92,7 +92,7 @@ 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])。