Merge pull request #1790 from juguagua/leetcode-modify-the-code-of-the-BinaryTree

更新完善二叉树部分:从二叉树最大深度至二叉树所有路径
This commit is contained in:
程序员Carl
2022-12-02 11:00:54 +08:00
committed by GitHub
5 changed files with 92 additions and 101 deletions

View File

@ -59,7 +59,7 @@ int getdepth(treenode* node)
if (node == NULL) return 0; if (node == NULL) return 0;
``` ```
3. 确定单层递归的逻辑:先求它的左子树的深度,再求右子树的深度,最后取左右深度最大的数值 再+1 加1是因为算上当前中间节点就是目前节点为根节点的树的深度。 3. 确定单层递归的逻辑:先求它的左子树的深度,再求右子树的深度,最后取左右深度最大的数值 再+1 加1是因为算上当前中间节点就是目前节点为根节点的树的深度。
代码如下: 代码如下:
@ -591,15 +591,15 @@ var maxdepth = function(root) {
var maxdepth = function(root) { var maxdepth = function(root) {
//使用递归的方法 递归三部曲 //使用递归的方法 递归三部曲
//1. 确定递归函数的参数和返回值 //1. 确定递归函数的参数和返回值
const getdepth=function(node){ const getdepth = function(node) {
//2. 确定终止条件 //2. 确定终止条件
if(node===null){ if(node === null) {
return 0; return 0;
} }
//3. 确定单层逻辑 //3. 确定单层逻辑
let leftdepth=getdepth(node.left); let leftdepth = getdepth(node.left);
let rightdepth=getdepth(node.right); let rightdepth = getdepth(node.right);
let depth=1+Math.max(leftdepth,rightdepth); let depth = 1 + Math.max(leftdepth, rightdepth);
return depth; return depth;
} }
return getdepth(root); return getdepth(root);

View File

@ -158,7 +158,7 @@ if (node == NULL) {
如何判断以当前传入节点为根节点的二叉树是否是平衡二叉树呢?当然是其左子树高度和其右子树高度的差值。 如何判断以当前传入节点为根节点的二叉树是否是平衡二叉树呢?当然是其左子树高度和其右子树高度的差值。
分别求出其左右子树的高度然后如果差值小于等于1则返回当前二叉树的高度否则返回-1表示已经不是二叉平衡树了。 分别求出其左右子树的高度然后如果差值小于等于1则返回当前二叉树的高度否则返回-1表示已经不是二叉平衡树了。
代码如下: 代码如下:
@ -342,7 +342,7 @@ public:
**例如:都知道回溯法其实就是递归,但是很少人用迭代的方式去实现回溯算法!** **例如:都知道回溯法其实就是递归,但是很少人用迭代的方式去实现回溯算法!**
因为对于回溯算法已经是非常复杂的递归了,如果用迭代的话,就是自己给自己找麻烦,效率也并不一定高。 因为对于回溯算法已经是非常复杂的递归了,如果用迭代的话,就是自己给自己找麻烦,效率也并不一定高。
## 总结 ## 总结
@ -559,37 +559,32 @@ class Solution:
### Go ### Go
```Go ```Go
func isBalanced(root *TreeNode) bool { func isBalanced(root *TreeNode) bool {
if root==nil{ h := getHeight(root)
return true if h == -1 {
}
if !isBalanced(root.Left) || !isBalanced(root.Right){
return false
}
LeftH:=maxdepth(root.Left)+1
RightH:=maxdepth(root.Right)+1
if abs(LeftH-RightH)>1{
return false return false
} }
return true return true
} }
func maxdepth(root *TreeNode)int{ // 返回以该节点为根节点的二叉树的高度,如果不是平衡二叉树了则返回-1
if root==nil{ func getHeight(root *TreeNode) int {
if root == nil {
return 0 return 0
} }
return max(maxdepth(root.Left),maxdepth(root.Right))+1 l, r := getHeight(root.Left), getHeight(root.Right)
if l == -1 || r == -1 {
return -1
}
if l - r > 1 || r - l > 1 {
return -1
}
return max(l, r) + 1
} }
func max(a,b int)int{ func max(a, b int) int {
if a>b{ if a > b {
return a return a
} }
return b return b
} }
func abs(a int)int{
if a<0{
return -a
}
return a
}
``` ```
### JavaScript ### JavaScript

View File

@ -39,7 +39,7 @@
* 二叉树节点的深度指从根节点到该节点的最长简单路径边的条数或者节点数取决于深度从0开始还是从1开始 * 二叉树节点的深度指从根节点到该节点的最长简单路径边的条数或者节点数取决于深度从0开始还是从1开始
* 二叉树节点的高度指从该节点到叶子节点的最长简单路径边的条数后者节点数取决于高度从0开始还是从1开始 * 二叉树节点的高度指从该节点到叶子节点的最长简单路径边的条数后者节点数取决于高度从0开始还是从1开始
那么使用后序遍历,其实求的是根节点到叶子节点的最小距离,就是求高度的过程,不过这个最小距离 也同样是最小深度。 那么使用后序遍历,其实求的是根节点到叶子节点的最小距离,就是求高度的过程,不过这个最小距离 也同样是最小深度。
以下讲解中遍历顺序上依然采用后序遍历(因为要比较递归返回之后的结果,本文我也给出前序遍历的写法)。 以下讲解中遍历顺序上依然采用后序遍历(因为要比较递归返回之后的结果,本文我也给出前序遍历的写法)。
@ -199,7 +199,7 @@ public:
如果对层序遍历还不清楚的话,可以看这篇:[二叉树:层序遍历登场!](https://programmercarl.com/0102.二叉树的层序遍历.html) 如果对层序遍历还不清楚的话,可以看这篇:[二叉树:层序遍历登场!](https://programmercarl.com/0102.二叉树的层序遍历.html)
**需要注意的是,只有当左右孩子都为空的时候,才说明遍历最低点了。如果其中一个孩子为空则不是最低点** **需要注意的是,只有当左右孩子都为空的时候,才说明遍历最低点了。如果其中一个孩子为空则不是最低点**
代码如下:(详细注释) 代码如下:(详细注释)

View File

@ -63,7 +63,7 @@ int getNodesNum(TreeNode* cur) {
if (cur == NULL) return 0; if (cur == NULL) return 0;
``` ```
3. 确定单层递归的逻辑:先求它的左子树的节点数量,再求右子树的节点数量,最后取总和再加一 加1是因为算上当前中间节点就是目前节点为根节点的节点数量。 3. 确定单层递归的逻辑:先求它的左子树的节点数量,再求右子树的节点数量,最后取总和再加一 加1是因为算上当前中间节点就是目前节点为根节点的节点数量。
代码如下: 代码如下:
@ -168,7 +168,7 @@ public:
可以看出如果整个树不是满二叉树,就递归其左右孩子,直到遇到满二叉树为止,用公式计算这个子树(满二叉树)的节点数量。 可以看出如果整个树不是满二叉树,就递归其左右孩子,直到遇到满二叉树为止,用公式计算这个子树(满二叉树)的节点数量。
这里关键在于如去判断一个左子树或者右子树是不是满二叉树呢? 这里关键在于如去判断一个左子树或者右子树是不是满二叉树呢?
在完全二叉树中,如果递归向左遍历的深度等于递归向右遍历的深度,那说明就是满二叉树。如图: 在完全二叉树中,如果递归向左遍历的深度等于递归向右遍历的深度,那说明就是满二叉树。如图:
@ -178,13 +178,13 @@ public:
![](https://code-thinking-1253855093.file.myqcloud.com/pics/20220829163709.png) ![](https://code-thinking-1253855093.file.myqcloud.com/pics/20220829163709.png)
有录友说了,这种情况,递归向左遍历的深度等于递归向右遍历的深度,但也不是满二叉树,如题: 有录友说了,这种情况,递归向左遍历的深度等于递归向右遍历的深度,但也不是满二叉树,如题:
![](https://code-thinking-1253855093.file.myqcloud.com/pics/20220829163811.png) ![](https://code-thinking-1253855093.file.myqcloud.com/pics/20220829163811.png)
如果这么想,大家就是对 完全二叉树理解有误区了,**以上这棵二叉树,它根本就不是一个完全二叉树** 如果这么想,大家就是对 完全二叉树理解有误区了,**以上这棵二叉树,它根本就不是一个完全二叉树**
判断其子树不是满二叉树,如果是则利用公式计算这个子树(满二叉树)的节点数量,如果不是则继续递归,那么 在递归三部曲中,第二部:终止条件的写法应该是这样的: 判断其子树不是满二叉树,如果是则利用公式计算这个子树(满二叉树)的节点数量,如果不是则继续递归,那么 在递归三部曲中,第二部:终止条件的写法应该是这样的:
```CPP ```CPP
if (root == nullptr) return 0; if (root == nullptr) return 0;
@ -292,26 +292,22 @@ class Solution {
* 满二叉树的结点数为2^depth - 1 * 满二叉树的结点数为2^depth - 1
*/ */
public int countNodes(TreeNode root) { public int countNodes(TreeNode root) {
if(root == null) { if (root == null) return 0;
return 0; TreeNode left = root.left;
TreeNode right = root.right;
int leftDepth = 0, rightDepth = 0; // 这里初始为0是有目的的为了下面求指数方便
while (left != null) { // 求左子树深度
left = left.left;
leftDepth++;
} }
int leftDepth = getDepth(root.left); while (right != null) { // 求右子树深度
int rightDepth = getDepth(root.right); right = right.right;
if (leftDepth == rightDepth) {// 左子树是满二叉树 rightDepth++;
// 2^leftDepth其实是 2^leftDepth - 1 + 1 ,左子树 + 根结点
return (1 << leftDepth) + countNodes(root.right);
} else {// 右子树是满二叉树
return (1 << rightDepth) + countNodes(root.left);
} }
} if (leftDepth == rightDepth) {
return (2 << leftDepth) - 1; // 注意(2<<1) 相当于2^2所以leftDepth初始为0
private int getDepth(TreeNode root) {
int depth = 0;
while (root != null) {
root = root.left;
depth++;
} }
return depth; return countNodes(root.left) + countNodes(root.right) + 1;
} }
} }
``` ```
@ -397,7 +393,7 @@ class Solution:
* Right *TreeNode * Right *TreeNode
* } * }
*/ */
//本题直接就是求有多少个节点,无脑存进数组算长度就行了。 //本题直接就是求有多少个节点,无脑存进结果变量就行了。
func countNodes(root *TreeNode) int { func countNodes(root *TreeNode) int {
if root == nil { if root == nil {
return 0 return 0
@ -473,15 +469,15 @@ func countNodes(root *TreeNode) int {
var countNodes = function(root) { var countNodes = function(root) {
//递归法计算二叉树节点数 //递归法计算二叉树节点数
// 1. 确定递归函数参数 // 1. 确定递归函数参数
const getNodeSum=function(node){ const getNodeSum = function(node) {
//2. 确定终止条件 //2. 确定终止条件
if(node===null){ if(node === null) {
return 0; return 0;
} }
//3. 确定单层递归逻辑 //3. 确定单层递归逻辑
let leftNum=getNodeSum(node.left); let leftNum = getNodeSum(node.left);
let rightNum=getNodeSum(node.right); let rightNum = getNodeSum(node.right);
return leftNum+rightNum+1; return leftNum + rightNum + 1;
} }
return getNodeSum(root); return getNodeSum(root);
}; };
@ -491,19 +487,19 @@ var countNodes = function(root) {
```javascript ```javascript
var countNodes = function(root) { var countNodes = function(root) {
//层序遍历 //层序遍历
let queue=[]; let queue = [];
if(root===null){ if(root === null) {
return 0; return 0;
} }
queue.push(root); queue.push(root);
let nodeNums=0; let nodeNums = 0;
while(queue.length){ while(queue.length) {
let length=queue.length; let length = queue.length;
while(length--){ while(length--) {
let node=queue.shift(); let node = queue.shift();
nodeNums++; nodeNums++;
node.left&&queue.push(node.left); node.left && queue.push(node.left);
node.right&&queue.push(node.right); node.right && queue.push(node.right);
} }
} }
return nodeNums; return nodeNums;
@ -514,24 +510,24 @@ var countNodes = function(root) {
```javascript ```javascript
var countNodes = function(root) { var countNodes = function(root) {
//利用完全二叉树的特点 //利用完全二叉树的特点
if(root===null){ if(root === null) {
return 0; return 0;
} }
let left=root.left; let left = root.left;
let right=root.right; let right = root.right;
let leftDepth=0,rightDepth=0; let leftDepth = 0, rightDepth = 0;
while(left){ while(left) {
left=left.left; left = left.left;
leftDepth++; leftDepth++;
} }
while(right){ while(right) {
right=right.right; right = right.right;
rightDepth++; rightDepth++;
} }
if(leftDepth==rightDepth){ if(leftDepth == rightDepth) {
return Math.pow(2,leftDepth+1)-1; return Math.pow(2, leftDepth+1) - 1;
} }
return countNodes(root.left)+countNodes(root.right)+1; return countNodes(root.left) + countNodes(root.right) + 1;
}; };
``` ```

View File

@ -24,7 +24,7 @@
这道题目要求从根节点到叶子的路径,所以需要前序遍历,这样才方便让父节点指向孩子节点,找到对应的路径。 这道题目要求从根节点到叶子的路径,所以需要前序遍历,这样才方便让父节点指向孩子节点,找到对应的路径。
在这道题目中将第一次涉及到回溯,因为我们要把路径记录下来,需要回溯来回退一个路径进入另一个路径。 在这道题目中将第一次涉及到回溯,因为我们要把路径记录下来,需要回溯来回退一个路径进入另一个路径。
前序遍历以及回溯的过程如图: 前序遍历以及回溯的过程如图:
@ -44,7 +44,7 @@ void traversal(TreeNode* cur, vector<int>& path, vector<string>& result)
2. 确定递归终止条件 2. 确定递归终止条件
写递归的时候都习惯了这么写: 写递归的时候都习惯了这么写:
``` ```
if (cur == NULL) { if (cur == NULL) {
@ -67,7 +67,7 @@ if (cur->left == NULL && cur->right == NULL) {
再来看一下终止处理的逻辑。 再来看一下终止处理的逻辑。
这里使用vector<int> 结构path来记录路径所以要把vector<int> 结构的path转为string格式把这个string 放进 result里。 这里使用vector<int> 结构path来记录路径所以要把vector<int> 结构的path转为string格式把这个string 放进 result里。
**那么为什么使用了vector<int> 结构来记录路径呢?** 因为在下面处理单层递归逻辑的时候要做回溯使用vector方便来做回溯。 **那么为什么使用了vector<int> 结构来记录路径呢?** 因为在下面处理单层递归逻辑的时候要做回溯使用vector方便来做回溯。
@ -123,7 +123,7 @@ if (cur->right) {
path.pop_back(); path.pop_back();
``` ```
这个回溯就很大的问题,我们知道,**回溯和递归是一一对应的,有一个递归,就要有一个回溯**,这么写的话相当于把递归和回溯拆开了, 一个在花括号里,一个在花括号外。 这个回溯就很大的问题,我们知道,**回溯和递归是一一对应的,有一个递归,就要有一个回溯**,这么写的话相当于把递归和回溯拆开了, 一个在花括号里,一个在花括号外。
**所以回溯要和递归永远在一起,世界上最遥远的距离是你在花括号里,而我在花括号外!** **所以回溯要和递归永远在一起,世界上最遥远的距离是你在花括号里,而我在花括号外!**
@ -300,16 +300,16 @@ public:
``` ```
**大家应该可以感受出来,如果把 `path + "->"`作为函数参数就是可以的,因为并没有改变path的数值执行完递归函数之后path依然是之前的数值相当于回溯了** **大家应该可以感受出来,如果把 `path + "->"`作为函数参数就是可以的因为并没有改变path的数值执行完递归函数之后path依然是之前的数值相当于回溯了**
**综合以上,第二种递归的代码虽然精简但把很多重要的点隐藏在了代码细节里,第一种递归写法虽然代码多一些,但是把每一个逻辑处理都完整的展现出来了。** **综合以上,第二种递归的代码虽然精简但把很多重要的点隐藏在了代码细节里,第一种递归写法虽然代码多一些,但是把每一个逻辑处理都完整的展现出来了。**
## 拓展 ## 拓展
这里讲解本题解的写法逻辑以及一些更具体的细节下面的讲解中涉及到C++语法特性如果不是C++的录友,就可以不看了,避免越看越晕。 这里讲解本题解的写法逻辑以及一些更具体的细节下面的讲解中涉及到C++语法特性如果不是C++的录友,就可以不看了,避免越看越晕。
如果是C++的录友,建议本题独立刷过两遍,看下面的讲解,同样避免越看越晕,造成不必要的负担。 如果是C++的录友,建议本题独立刷过两遍,看下面的讲解,同样避免越看越晕,造成不必要的负担。
在第二版本的代码中,其实仅仅是回溯了 `->` 部分调用两次pop_back一个pop`>` 一次pop`-`),大家应该疑惑那么 `path += to_string(cur->val);` 这一步为什么没有回溯呢? 一条路径能持续加节点 不做回溯吗? 在第二版本的代码中,其实仅仅是回溯了 `->` 部分调用两次pop_back一个pop`>` 一次pop`-`),大家应该疑惑那么 `path += to_string(cur->val);` 这一步为什么没有回溯呢? 一条路径能持续加节点 不做回溯吗?
@ -378,7 +378,7 @@ public:
最后我依然给出了迭代法。 最后我依然给出了迭代法。
对于本充分了解递归与回溯的过程之后,有精力的同学可以去实现迭代法。 对于本充分了解递归与回溯的过程之后,有精力的同学可以去实现迭代法。
@ -386,7 +386,7 @@ public:
# 其他语言版本 # 其他语言版本
Java ## Java
```Java ```Java
//解法一 //解法一
@ -466,7 +466,7 @@ class Solution {
} }
``` ```
--- ---
Python: ## Python:
递归法+隐形回溯 递归法+隐形回溯
```Python3 ```Python3
# Definition for a binary tree node. # Definition for a binary tree node.
@ -529,7 +529,7 @@ class Solution:
--- ---
Go ## Go
递归法: 递归法:
@ -591,28 +591,28 @@ func binaryTreePaths(root *TreeNode) []string {
``` ```
--- ---
JavaScript: ## JavaScript:
递归法: 递归法:
```javascript ```javascript
var binaryTreePaths = function(root) { var binaryTreePaths = function(root) {
//递归遍历+递归三部曲 //递归遍历+递归三部曲
let res=[]; let res = [];
//1. 确定递归函数 函数参数 //1. 确定递归函数 函数参数
const getPath=function(node,curPath){ const getPath = function(node,curPath) {
//2. 确定终止条件,到叶子节点就终止 //2. 确定终止条件,到叶子节点就终止
if(node.left===null&&node.right===null){ if(node.left === null && node.right === null) {
curPath+=node.val; curPath += node.val;
res.push(curPath); res.push(curPath);
return ; return;
} }
//3. 确定单层递归逻辑 //3. 确定单层递归逻辑
curPath+=node.val+'->'; curPath += node.val + '->';
node.left&&getPath(node.left,curPath); node.left && getPath(node.left, curPath);
node.right&&getPath(node.right,curPath); node.right && getPath(node.right, curPath);
} }
getPath(root,''); getPath(root, '');
return res; return res;
}; };
``` ```
@ -644,7 +644,7 @@ var binaryTreePaths = function(root) {
}; };
``` ```
TypeScript ## TypeScript
> 递归法 > 递归法
@ -698,7 +698,7 @@ function binaryTreePaths(root: TreeNode | null): string[] {
}; };
``` ```
Swift: ## Swift:
> 递归/回溯 > 递归/回溯
```swift ```swift
@ -765,7 +765,7 @@ func binaryTreePaths(_ root: TreeNode?) -> [String] {
} }
``` ```
Scala: ## Scala:
递归: 递归:
```scala ```scala