mirror of
https://github.com/youngyangyang04/leetcode-master.git
synced 2025-07-06 15:09:40 +08:00
Update
This commit is contained in:
@ -284,6 +284,7 @@
|
||||
|[0486.预测赢家](https://github.com/youngyangyang04/leetcode/blob/master/problems/0486.预测赢家.md) |动态规划 |中等| **递归** **记忆递归** **动态规划**|
|
||||
|[0491.递增子序列](https://github.com/youngyangyang04/leetcode/blob/master/problems/0491.递增子序列.md) |深度优先搜索 |中等|**深度优先搜索/回溯算法**|
|
||||
|[0501.二叉搜索树中的众数](https://github.com/youngyangyang04/leetcode/blob/master/problems/0501.二叉搜索树中的众数.md) |二叉树 |简单|**递归/中序遍历**|
|
||||
|[0513.找树左下角的值](https://github.com/youngyangyang04/leetcode/blob/master/problems/0513.找树左下角的值.md) |二叉树 |中等|**递归** **迭代**|
|
||||
|[0515.在每个树行中找最大值](https://github.com/youngyangyang04/leetcode/blob/master/problems/0515.在每个树行中找最大值.md) |二叉树 |简单|**广度优先搜索/队列**|
|
||||
|[0538.把二叉搜索树转换为累加树](https://github.com/youngyangyang04/leetcode/blob/master/problems/0538.把二叉搜索树转换为累加树.md) |二叉树 |简单|**递归** **迭代**|
|
||||
|[0541.反转字符串II](https://github.com/youngyangyang04/leetcode/blob/master/problems/0541.反转字符串II.md) |字符串 |简单| **模拟**|
|
||||
|
BIN
pics/100.相同的树.png
Normal file
BIN
pics/100.相同的树.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 58 KiB |
BIN
pics/110.平衡二叉树2.png
Normal file
BIN
pics/110.平衡二叉树2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 163 KiB |
Binary file not shown.
Before Width: | Height: | Size: 114 KiB After Width: | Height: | Size: 146 KiB |
BIN
pics/257.二叉树的所有路径1.png
Normal file
BIN
pics/257.二叉树的所有路径1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 29 KiB |
@ -1,9 +1,115 @@
|
||||
## 题目地址
|
||||
https://leetcode-cn.com/problems/same-tree/
|
||||
|
||||
## 思路
|
||||
(没写完)
|
||||
|
||||
这道题目和101 基本是一样的
|
||||
# 100. 相同的树
|
||||
|
||||
给定两个二叉树,编写一个函数来检验它们是否相同。
|
||||
|
||||
如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。
|
||||
|
||||
<img src='../pics/100.相同的树.png' width=600> </img></div>
|
||||
|
||||
# 思路
|
||||
|
||||
在[二叉树:我对称么?](https://mp.weixin.qq.com/s/Kgf0gjvlDlNDfKIH2b1Oxg)中,我们讲到对于二叉树是否对称,要比较的是根节点的左子树与右子树是不是相互翻转的,理解这一点就知道了**其实我们要比较的是两个树(这两个树是根节点的左右子树)**,所以在递归遍历的过程中,也是要同时遍历两棵树。
|
||||
|
||||
理解这一本质之后,就会发现,求二叉树是否对称,和求二叉树是否相同几乎是同一道题目。
|
||||
|
||||
**如果没有读过[二叉树:我对称么?](https://mp.weixin.qq.com/s/Kgf0gjvlDlNDfKIH2b1Oxg)这一篇,请认真读完再做这道题,就会有感觉了。**
|
||||
|
||||
递归三部曲中:
|
||||
|
||||
1. 确定递归函数的参数和返回值
|
||||
|
||||
我们要比较的是两个树是否是相互相同的,参数也就是两个树的根节点。
|
||||
|
||||
返回值自然是bool类型。
|
||||
|
||||
代码如下:
|
||||
```
|
||||
bool compare(TreeNode* tree1, TreeNode* tree2)
|
||||
```
|
||||
|
||||
分析过程同[二叉树:我对称么?](https://mp.weixin.qq.com/s/Kgf0gjvlDlNDfKIH2b1Oxg)。
|
||||
|
||||
2. 确定终止条件
|
||||
|
||||
**要比较两个节点数值相不相同,首先要把两个节点为空的情况弄清楚!否则后面比较数值的时候就会操作空指针了。**
|
||||
|
||||
节点为空的情况有:
|
||||
|
||||
* tree1为空,tree2不为空,不对称,return false
|
||||
* tree1不为空,tree2为空,不对称 return false
|
||||
* tree1,tree2都为空,对称,返回true
|
||||
|
||||
此时已经排除掉了节点为空的情况,那么剩下的就是tree1和tree2不为空的时候:
|
||||
|
||||
* tree1、tree2都不为空,比较节点数值,不相同就return false
|
||||
|
||||
此时tree1、tree2节点不为空,且数值也不相同的情况我们也处理了。
|
||||
|
||||
代码如下:
|
||||
```
|
||||
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
|
||||
```
|
||||
|
||||
分析过程同[二叉树:我对称么?](https://mp.weixin.qq.com/s/Kgf0gjvlDlNDfKIH2b1Oxg)
|
||||
|
||||
3. 确定单层递归的逻辑
|
||||
|
||||
* 比较二叉树是否相同 :传入的是tree1的左孩子,tree2的右孩子。
|
||||
* 如果左右都相同就返回true ,有一侧不相同就返回false 。
|
||||
|
||||
代码如下:
|
||||
|
||||
```
|
||||
bool outside = compare(tree1->left, tree2->right); // 左子树:左、 右子树:左
|
||||
bool inside = compare(tree1->right, tree2->left); // 左子树:右、 右子树:右
|
||||
bool isSame = outside && inside; // 左子树:中、 右子树:中(逻辑处理)
|
||||
return isSame;
|
||||
```
|
||||
-------------------------------------------------------------- 写到这
|
||||
最后递归的C++整体代码如下:
|
||||
|
||||
```
|
||||
class Solution {
|
||||
public:
|
||||
bool compare(TreeNode* left, TreeNode* right) {
|
||||
// 首先排除空节点的情况
|
||||
if (left == NULL && right != NULL) return false;
|
||||
else if (left != NULL && right == NULL) return false;
|
||||
else if (left == NULL && right == NULL) return true;
|
||||
// 排除了空节点,再排除数值不相同的情况
|
||||
else if (left->val != right->val) return false;
|
||||
|
||||
// 此时就是:左右节点都不为空,且数值相同的情况
|
||||
// 此时才做递归,做下一层的判断
|
||||
bool outside = compare(left->left, right->right); // 左子树:左、 右子树:右
|
||||
bool inside = compare(left->right, right->left); // 左子树:右、 右子树:左
|
||||
bool isSame = outside && inside; // 左子树:中、 右子树:中 (逻辑处理)
|
||||
return isSame;
|
||||
|
||||
}
|
||||
bool isSymmetric(TreeNode* root) {
|
||||
if (root == NULL) return true;
|
||||
return compare(root->left, root->right);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
**我给出的代码并不简洁,但是把每一步判断的逻辑都清楚的描绘出来了。**
|
||||
|
||||
如果上来就看网上各种简洁的代码,看起来真的很简单,但是很多逻辑都掩盖掉了,而题解可能也没有把掩盖掉的逻辑说清楚。
|
||||
|
||||
**盲目的照着抄,结果就是:发现这是一道“简单题”,稀里糊涂的就过了,但是真正的每一步判断逻辑未必想到清楚。**
|
||||
|
||||
当然我可以把如上代码整理如下:
|
||||
这道题目本质上和[二叉树:我对称么?](https://mp.weixin.qq.com/s/Kgf0gjvlDlNDfKIH2b1Oxg)是一样,因为
|
||||
|
||||
## 递归
|
||||
|
||||
@ -42,7 +148,6 @@ public:
|
||||
if (!leftNode && !rightNode) { //
|
||||
continue;
|
||||
}
|
||||
|
||||
//
|
||||
if ((!leftNode || !rightNode || (leftNode->val != rightNode->val))) {
|
||||
return false;
|
||||
|
@ -1,6 +1,8 @@
|
||||
## 题目地址
|
||||
https://leetcode-cn.com/problems/balanced-binary-tree/
|
||||
|
||||
> 求高度还是求深度,你搞懂了不?
|
||||
|
||||
# 110.平衡二叉树
|
||||
|
||||
给定一个二叉树,判断它是否是高度平衡的二叉树。
|
||||
@ -17,30 +19,117 @@ https://leetcode-cn.com/problems/balanced-binary-tree/
|
||||
|
||||
示例 2:
|
||||
|
||||
给定二叉树 [1,2,2,3,3,null,null,4,4
|
||||
给定二叉树 [1,2,2,3,3,null,null,4,4]
|
||||
|
||||
<img src='../pics/110.平衡二叉树1.png' width=600> </img></div>
|
||||
|
||||
返回 false 。
|
||||
|
||||
## 思路
|
||||
# 题外话
|
||||
|
||||
分析一下这道题是不是和求深度很像,在[二叉树:看看这些树的最大深度](https://mp.weixin.qq.com/s/guKwV-gSNbA1CcbvkMtHBg)中是求左右子树的最大深度,本题呢,是要求左右子树高度差绝对值不超过1。
|
||||
咋眼一看这道题目和[二叉树:看看这些树的最大深度](https://mp.weixin.qq.com/s/guKwV-gSNbA1CcbvkMtHBg)很像,其实有很大区别。
|
||||
|
||||
这里强调一波概念:
|
||||
|
||||
* 二叉树节点的深度:指从根节点到该节点的最长简单路径边的条数。
|
||||
* 二叉树节点的高度:指从该节点到叶子节点的最长简单路径边的条数。
|
||||
|
||||
但leetcode中强调的深度和高度很明显是按照节点来计算的,如图:
|
||||
|
||||
<img src='../pics/110.平衡二叉树2.png' width=600> </img></div>
|
||||
|
||||
关于根节点的深度究竟是1 还是 0,不同的地方有不一样的标准,leetcode的题目中都是以节点为一度,即根节点深度是1。但维基百科上定义用边为一度,即根节点的深度是0,我们暂时以leetcode为准(毕竟要在这上面刷题)。
|
||||
|
||||
因为求深度可以从上到下去查 所以需要前序遍历(中左右),而高度只能从下到上去查,所以只能后序遍历(左右中)
|
||||
|
||||
有的同学一定疑惑,为什么[二叉树:看看这些树的最大深度](https://mp.weixin.qq.com/s/guKwV-gSNbA1CcbvkMtHBg)中求的是二叉树的最大深度,也用的是后序遍历。
|
||||
|
||||
**那是因为代码的逻辑其实是求的根节点的高度,而根节点的高度就是这颗树的最大深度,所以才可以使用后序遍历。**
|
||||
|
||||
在[二叉树:看看这些树的最大深度](https://mp.weixin.qq.com/s/guKwV-gSNbA1CcbvkMtHBg)中,如果真正求取二叉树的最大深度,代码应该写成如下:(前序遍历)
|
||||
|
||||
```
|
||||
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) { // 左
|
||||
depth++; // 深度+1
|
||||
getDepth(node->left, depth);
|
||||
depth--; // 回溯,深度-1
|
||||
}
|
||||
if (node->right) { // 右
|
||||
depth++; // 深度+1
|
||||
getDepth(node->right, depth);
|
||||
depth--; // 回溯,深度-1
|
||||
}
|
||||
return ;
|
||||
}
|
||||
int maxDepth(TreeNode* root) {
|
||||
result = 0;
|
||||
if (root == 0) return result;
|
||||
getDepth(root, 1);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
**可以看出使用了前序(中左右)的遍历顺序,这才是真正求深度的逻辑!**
|
||||
|
||||
注意以上代码是为了把细节体现出来,简化一下代码如下:
|
||||
|
||||
```
|
||||
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;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
# 本题思路
|
||||
|
||||
## 递归
|
||||
|
||||
此时大家应该明白了既然要求比较高度,必然是要后序遍历。
|
||||
|
||||
递归三步曲分析:
|
||||
|
||||
1. 明确递归函数的参数和返回值
|
||||
|
||||
参数的话为传入的节点指针,就没有其他参数需要传递了,**返回值要注意,我们的返回值是要求传入节点为根节点树的深度**,否则如何标记左右子树是否差值大于1呢。
|
||||
参数的话为传入的节点指针,就没有其他参数需要传递了,返回值要返回传入节点为根节点树的深度。
|
||||
|
||||
这里还要注意一点,如果当前传入节点为根节点的二叉树已经不是二叉平衡树了,返回高度还有意义么? 此时可以返回-1 来标记已经不符合平衡树的规则了。
|
||||
那么如何标记左右子树是否差值大于1呢。
|
||||
|
||||
如果当前传入节点为根节点的二叉树已经不是二叉平衡树了,还返回高度的话就没有意义了。
|
||||
|
||||
所以如果已经不是二叉平衡树了,可以返回-1 来标记已经不符合平衡树的规则了。
|
||||
|
||||
代码如下:
|
||||
|
||||
|
||||
```
|
||||
int depth(TreeNode* node)
|
||||
// -1 表示已经不是平衡二叉树了,否则返回值是以该节点为根节点树的高度
|
||||
int getDepth(TreeNode* node)
|
||||
```
|
||||
|
||||
2. 明确终止条件
|
||||
@ -64,52 +153,193 @@ if (node == NULL) {
|
||||
代码如下:
|
||||
|
||||
```
|
||||
int leftDepth = depth(node->left);
|
||||
if (leftDepth == -1) return -1;
|
||||
int rightDepth = depth(node->right);
|
||||
int leftDepth = depth(node->left); // 左
|
||||
if (leftDepth == -1) return -1;
|
||||
int rightDepth = depth(node->right); // 右
|
||||
if (rightDepth == -1) return -1;
|
||||
|
||||
int result;
|
||||
if (abs(leftDepth - rightDepth) > 1) { // 中
|
||||
result = -1;
|
||||
} else {
|
||||
result = 1 + max(leftDepth, rightDepth); // 以当前节点为根节点的最大高度
|
||||
}
|
||||
|
||||
return result;
|
||||
```
|
||||
|
||||
代码精简之后如下:
|
||||
|
||||
```
|
||||
int leftDepth = getDepth(node->left);
|
||||
if (leftDepth == -1) return -1;
|
||||
int rightDepth = getDepth(node->right);
|
||||
if (rightDepth == -1) return -1;
|
||||
return abs(leftDepth - rightDepth) > 1 ? -1 : 1 + max(leftDepth, rightDepth);
|
||||
```
|
||||
|
||||
此时递归的函数就已经写出来了,这个递归的函数传入节点指针,返回以该节点为根节点的二叉树的高度,如果不是二叉平衡树了则返回-1。
|
||||
此时递归的函数就已经写出来了,这个递归的函数传入节点指针,返回以该节点为根节点的二叉树的高度,如果不是二叉平衡树,则返回-1。
|
||||
|
||||
代码如下:
|
||||
getDepth整体代码如下:
|
||||
|
||||
```
|
||||
int depth(TreeNode* node) {
|
||||
int getDepth(TreeNode* node) {
|
||||
if (node == NULL) {
|
||||
return 0;
|
||||
}
|
||||
int leftDepth = depth(node->left);
|
||||
int leftDepth = getDepth(node->left);
|
||||
if (leftDepth == -1) return -1;
|
||||
int rightDepth = depth(node->right);
|
||||
int rightDepth = getDepth(node->right);
|
||||
if (rightDepth == -1) return -1;
|
||||
return abs(leftDepth - rightDepth) > 1 ? -1 : 1 + max(leftDepth, rightDepth);
|
||||
}
|
||||
```
|
||||
|
||||
整体代码如下:
|
||||
|
||||
## C++代码
|
||||
最后本题整体递归代码如下:
|
||||
|
||||
```
|
||||
class Solution {
|
||||
public:
|
||||
// 返回以该节点为根节点的二叉树的高度,如果不是二叉搜索树了则返回-1
|
||||
int depth(TreeNode* node) {
|
||||
int getDepth(TreeNode* node) {
|
||||
if (node == NULL) {
|
||||
return 0;
|
||||
}
|
||||
int leftDepth = depth(node->left);
|
||||
int leftDepth = getDepth(node->left);
|
||||
if (leftDepth == -1) return -1; // 说明左子树已经不是二叉平衡树
|
||||
int rightDepth = depth(node->right);
|
||||
int rightDepth = getDepth(node->right);
|
||||
if (rightDepth == -1) return -1; // 说明右子树已经不是二叉平衡树
|
||||
return abs(leftDepth - rightDepth) > 1 ? -1 : 1 + max(leftDepth, rightDepth);
|
||||
}
|
||||
bool isBalanced(TreeNode* root) {
|
||||
return depth(root) == -1 ? false : true;
|
||||
return getDepth(root) == -1 ? false : true;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## 迭代
|
||||
|
||||
在[二叉树:看看这些树的最大深度](https://mp.weixin.qq.com/s/guKwV-gSNbA1CcbvkMtHBg)中我们可以使用层序遍历来求深度,但是就不能直接用层序遍历来求高度了,这就体现出求高度和求深度的不同。
|
||||
|
||||
本题的迭代方式可以先定义一个函数,专门用来求高度。
|
||||
|
||||
这个函数通过栈模拟的后序遍历找每一个节点的高度(其实是通过求传入节点为根节点的最大深度来求的高度)
|
||||
|
||||
代码如下:
|
||||
|
||||
```
|
||||
// cur节点的最大深度,就是cur的高度
|
||||
int getDepth(TreeNode* cur) {
|
||||
stack<TreeNode*> st;
|
||||
if (cur != NULL) st.push(cur);
|
||||
int depth = 0; // 记录深度
|
||||
int result = 0;
|
||||
while (!st.empty()) {
|
||||
TreeNode* node = st.top();
|
||||
if (node != NULL) {
|
||||
st.pop();
|
||||
st.push(node); // 中
|
||||
st.push(NULL);
|
||||
depth++;
|
||||
if (node->right) st.push(node->right); // 右
|
||||
if (node->left) st.push(node->left); // 左
|
||||
|
||||
} else {
|
||||
st.pop();
|
||||
node = st.top();
|
||||
st.pop();
|
||||
depth--;
|
||||
}
|
||||
result = result > depth ? result : depth;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
```
|
||||
|
||||
然后再用栈来模拟前序遍历,遍历每一个节点的时候,再去判断左右孩子的高度是否符合,代码如下:
|
||||
|
||||
```
|
||||
bool isBalanced(TreeNode* root) {
|
||||
stack<TreeNode*> st;
|
||||
if (root == NULL) return true;
|
||||
st.push(root);
|
||||
while (!st.empty()) {
|
||||
TreeNode* node = st.top(); // 中
|
||||
st.pop();
|
||||
if (abs(getDepth(node->left) - getDepth(node->right)) > 1) { // 判断左右孩子高度是否符合
|
||||
return false;
|
||||
}
|
||||
if (node->right) st.push(node->right); // 右(空节点不入栈)
|
||||
if (node->left) st.push(node->left); // 左(空节点不入栈)
|
||||
}
|
||||
return true;
|
||||
}
|
||||
```
|
||||
|
||||
整体代码如下:
|
||||
|
||||
```
|
||||
class Solution {
|
||||
private:
|
||||
int getDepth(TreeNode* cur) {
|
||||
stack<TreeNode*> st;
|
||||
if (cur != NULL) st.push(cur);
|
||||
int depth = 0; // 记录深度
|
||||
int result = 0;
|
||||
while (!st.empty()) {
|
||||
TreeNode* node = st.top();
|
||||
if (node != NULL) {
|
||||
st.pop();
|
||||
st.push(node); // 中
|
||||
st.push(NULL);
|
||||
depth++;
|
||||
if (node->right) st.push(node->right); // 右
|
||||
if (node->left) st.push(node->left); // 左
|
||||
|
||||
} else {
|
||||
st.pop();
|
||||
node = st.top();
|
||||
st.pop();
|
||||
depth--;
|
||||
}
|
||||
result = result > depth ? result : depth;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public:
|
||||
bool isBalanced(TreeNode* root) {
|
||||
stack<TreeNode*> st;
|
||||
if (root == NULL) return true;
|
||||
st.push(root);
|
||||
while (!st.empty()) {
|
||||
TreeNode* node = st.top(); // 中
|
||||
st.pop();
|
||||
if (abs(getDepth(node->left) - getDepth(node->right)) > 1) {
|
||||
return false;
|
||||
}
|
||||
if (node->right) st.push(node->right); // 右(空节点不入栈)
|
||||
if (node->left) st.push(node->left); // 左(空节点不入栈)
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
当然此题用迭代法,其实效率很低,因为没有很好的模拟回溯的过程,所以迭代法有很多重复的计算。
|
||||
|
||||
虽然理论上所有的递归都可以用迭代来实现,但是有的场景难度可能比较大。
|
||||
|
||||
**例如:都知道回溯法其实就是递归,但是很少人用迭代的方式去实现回溯算法!**
|
||||
|
||||
因为对于回溯算法已经是非常复杂的递归了,如果在用迭代的话,就是自己给自己找麻烦,效率也并不一定高。
|
||||
|
||||
# 总结
|
||||
|
||||
通过本题可以了解求二叉树深度 和 二叉树高度的差异,求深度适合用前序遍历,而求高度适合用后序遍历。
|
||||
|
||||
本题迭代法其实有点复杂,大家可以有一个思路,也不一定说非要写出来。
|
||||
|
||||
但是递归方式是一定要掌握的!
|
||||
|
||||
> 更多算法干货文章持续更新,可以微信搜索「代码随想录」第一时间围观,关注后,回复「Java」「C++」 「python」「简历模板」「数据结构与算法」等等,就可以获得我多年整理的学习资料。
|
||||
|
@ -1,42 +1,34 @@
|
||||
## 题目地址
|
||||
https://leetcode-cn.com/problems/binary-tree-paths/
|
||||
|
||||
> 以为只用了递归,其实还用了回溯
|
||||
|
||||
## 思路
|
||||
# 257. 二叉树的所有路径
|
||||
|
||||
首先要知道遍历二叉树有两种遍历方式:二叉树深度优先遍历和二叉树广度优先遍历,那么每种遍历方式下还有不同的顺序。如下所示:
|
||||
* 二叉树深度优先遍历
|
||||
* 前序遍历: [0144.二叉树的前序遍历](https://github.com/youngyangyang04/leetcode/blob/master/problems/0144.二叉树的前序遍历.md)
|
||||
* 后序遍历: [0145.二叉树的后序遍历](https://github.com/youngyangyang04/leetcode/blob/master/problems/0145.二叉树的后序遍历.md)
|
||||
* 中序遍历: [0094.二叉树的中序遍历](https://github.com/youngyangyang04/leetcode/blob/master/problems/0094.二叉树的中序遍历.md)
|
||||
* 二叉树广度优先遍历
|
||||
* 层序遍历:[0102.二叉树的层序遍历](https://github.com/youngyangyang04/leetcode/blob/master/problems/0102.二叉树的层序遍历.md)
|
||||
给定一个二叉树,返回所有从根节点到叶子节点的路径。
|
||||
|
||||
这道题目要打印出根节点到叶子节点的所有路径,很明显广度优先遍历不合适,那么深度优先遍历中,应该选哪一种循序来遍历呢?
|
||||
说明: 叶子节点是指没有子节点的节点。
|
||||
|
||||
**要打印路径,就要选前序遍历**,因为中序和后序遍历都不能打印出路径来。
|
||||
示例:
|
||||
<img src='../pics/257.二叉树的所有路径1.png' width=600> </img></div>
|
||||
|
||||
一些同学可能代码都写出来,而且都提交通过了,却不知道自己用了哪一种遍历,以及那种顺序来遍历的。
|
||||
# 思路
|
||||
|
||||
前序遍历如题:
|
||||
这道题目要求从根节点到叶子的路径,所以需要前序遍历,这样才方便让父节点指向孩子节点,找到对应的路径。
|
||||
|
||||
在这道题目中将第一次涉及到回溯,因为我们要把路径记录下来,需要回溯来回退一一个路径在进入另一个路径。
|
||||
|
||||
前序遍历以及回溯的过程如图:
|
||||
|
||||
<img src='../pics/257.二叉树的所有路径.png' width=600> </img></div>
|
||||
|
||||
确定了是前序遍历,那么就是中左右的顺序。前序遍历 框架如下:
|
||||
```
|
||||
void traversal(TreeNode* cur, vector<int>& vec) {
|
||||
if (cur == NULL) return;
|
||||
vec.push_back(cur->val); // 中 ,同时也是处理节点逻辑的地方
|
||||
traversal(cur->left, vec); // 左
|
||||
traversal(cur->right, vec); // 右
|
||||
}
|
||||
```
|
||||
我们先使用递归的方式,来做前序遍历。**要知道递归和回溯就是一家的,本题也需要回溯。**
|
||||
|
||||
我们先使用递归的方式,来做前序遍历。那么要知道递归和回溯就是一家的,本题也需要回溯。
|
||||
## 递归
|
||||
|
||||
1. 递归函数函数参数以及返回值
|
||||
|
||||
要传入跟节点,记录每一条路径的path,和存放结果集的result,这里递归不需要返回值,代码如下:
|
||||
要传入根节点,记录每一条路径的path,和存放结果集的result,这里递归不需要返回值,代码如下:
|
||||
|
||||
```
|
||||
void traversal(TreeNode* cur, vector<int>& path, vector<string>& result)
|
||||
@ -44,7 +36,7 @@ void traversal(TreeNode* cur, vector<int>& path, vector<string>& result)
|
||||
|
||||
2. 确定递归终止条件
|
||||
|
||||
在写递归的时候都习惯了这么写:
|
||||
再写递归的时候都习惯了这么写:
|
||||
|
||||
```
|
||||
if (cur == NULL) {
|
||||
@ -63,29 +55,29 @@ if (cur->left == NULL && cur->right == NULL) {
|
||||
}
|
||||
```
|
||||
|
||||
为什么没有判断cur是否为空呢,下文在讲解单层递归逻辑的时候会提到。
|
||||
为什么没有判断cur是否为空呢,因为下面的逻辑可以控制空节点不入循环。
|
||||
|
||||
再来看一下终止处理的逻辑。
|
||||
|
||||
这里使用vector<int> 结构来记录路径,所以要把路径转为string格式,在把这个string 放进 result里。
|
||||
这里使用vector<int> 结构path来记录路径,所以要把vector<int> 结构的path转为string格式,在把这个string 放进 result里。
|
||||
|
||||
**那么为什么使用了vector<int> 结构来记录路径呢?** 因为在下面处理单层递归逻辑的时候,要做回溯,使用vector方便来做回溯。
|
||||
|
||||
那么有的同学问了,我看有些人的代码也没有回溯啊。
|
||||
可能有的同学问了,我看有些人的代码也没有回溯啊。
|
||||
|
||||
其实是有的,只不过隐藏在 函数调用时的参数赋值里,下文我还会提到。
|
||||
**其实是有回溯的,只不过隐藏在函数调用时的参数赋值里**,下文我还会提到。
|
||||
|
||||
这里我们先使用vector<int> 结构来记录路径,那么终止处理逻辑如下:
|
||||
这里我们先使用vector<int>结构的path容器来记录路径,那么终止处理逻辑如下:
|
||||
|
||||
```
|
||||
if (cur->left == NULL && cur->right == NULL) {
|
||||
if (cur->left == NULL && cur->right == NULL) { // 遇到叶子节点
|
||||
string sPath;
|
||||
for (int i = 0; i < path.size() - 1; i++) {
|
||||
for (int i = 0; i < path.size() - 1; i++) { // 将path里记录的路径转为string格式
|
||||
sPath += to_string(path[i]);
|
||||
sPath += "->";
|
||||
}
|
||||
sPath += to_string(path[path.size() - 1]);
|
||||
result.push_back(sPath);
|
||||
sPath += to_string(path[path.size() - 1]); // 记录最后一个节点(叶子节点)
|
||||
result.push_back(sPath); // 收集一个路径
|
||||
return;
|
||||
}
|
||||
```
|
||||
@ -142,8 +134,6 @@ if (cur->right) {
|
||||
|
||||
那么本题整体代码如下:
|
||||
|
||||
## C++代码第一种写法
|
||||
|
||||
```
|
||||
class Solution {
|
||||
private:
|
||||
@ -181,23 +171,22 @@ public:
|
||||
}
|
||||
};
|
||||
```
|
||||
如上的C++代码充分体现了回溯。
|
||||
|
||||
## C++代码第二种写法
|
||||
|
||||
接下来我介绍另一种写法,如下写法就是一个标准的前序遍历的过程。
|
||||
那么如上代码可以精简成如下代码:
|
||||
|
||||
```
|
||||
class Solution {
|
||||
private:
|
||||
|
||||
void traversal(TreeNode* cur, string path, vector<string>& result) {
|
||||
path += to_string(cur->val);
|
||||
path += to_string(cur->val); // 中
|
||||
if (cur->left == NULL && cur->right == NULL) {
|
||||
result.push_back(path);
|
||||
return;
|
||||
}
|
||||
if (cur->left) traversal(cur->left, path + "->", result);
|
||||
if (cur->right) traversal(cur->right, path + "->", result);
|
||||
if (cur->left) traversal(cur->left, path + "->", result); // 左
|
||||
if (cur->right) traversal(cur->right, path + "->", result); // 右
|
||||
}
|
||||
|
||||
public:
|
||||
@ -212,14 +201,64 @@ public:
|
||||
};
|
||||
```
|
||||
|
||||
注意在函数定义的时候`void traversal(TreeNode* cur, string path, vector<string>& result)` ,定义的是`string path`,说明每次都是复制赋值。
|
||||
如上代码精简了不少,也隐藏了不少东西。
|
||||
|
||||
那么在如上代码中,**貌似没有看到回溯的逻辑,其实不然,回溯就隐藏在`traversal(cur->left, path + "->", result);`中的 `path + "->"`。** 每次函数调用完,path依然是没有+ 上"->" 的,这就是回溯了。
|
||||
注意在函数定义的时候`void traversal(TreeNode* cur, string path, vector<string>& result)` ,定义的是`string path`,每次都是复制赋值,不用使用引用,否则就无法做到回溯的效果。
|
||||
|
||||
**综合以上,第二种写法更简洁,但是把很多重要的点隐藏在了代码细节里,第一种写法虽然代码多一些,但是每一个处理逻辑都完整的展现了出来。**
|
||||
那么在如上代码中,**貌似没有看到回溯的逻辑,其实不然,回溯就隐藏在`traversal(cur->left, path + "->", result);`中的 `path + "->"`。** 每次函数调用完,path依然是没有加上"->" 的,这就是回溯了。
|
||||
|
||||
至于还有非递归的方式,我在这篇题解[彻底吃透前中后序递归法(递归三部曲)和迭代法(不统一写法与统一写法)](https://leetcode-cn.com/problems/binary-tree-preorder-traversal/solution/dai-ma-sui-xiang-lu-chi-tou-qian-zhong-hou-xu-de-d/) 已经彻底介绍过了,感兴趣的同学可以去看一看。
|
||||
**综合以上,第二种递归的代码虽然精简但把很多重要的点隐藏在了代码细节里,第一种递归写法虽然代码多一些,但是把每一个逻辑处理都完整的展现了出来了。**
|
||||
|
||||
## 迭代法
|
||||
|
||||
至于非递归的方式,我们可以依然可以使用前序遍历的迭代方式来模拟遍历路径的过程,对该迭代方式不了解的同学,可以看文章[二叉树:听说递归能做的,栈也能做!](https://mp.weixin.qq.com/s/c_zCrGHIVlBjUH_hJtghCg)和[二叉树:前中后序迭代方式的写法就不能统一一下么?](https://mp.weixin.qq.com/s/WKg0Ty1_3SZkztpHubZPRg)。
|
||||
|
||||
这里除了模拟递归需要一个栈,同时还需要一个栈来存放对应的遍历路径。
|
||||
|
||||
C++代码如下:
|
||||
|
||||
```
|
||||
class Solution {
|
||||
public:
|
||||
vector<string> binaryTreePaths(TreeNode* root) {
|
||||
stack<TreeNode*> treeSt;// 保存树的遍历节点
|
||||
stack<string> pathSt; // 保存遍历路径的节点
|
||||
vector<string> result; // 保存最终路径集合
|
||||
if (root == NULL) return result;
|
||||
treeSt.push(root);
|
||||
pathSt.push(to_string(root->val));
|
||||
while (!treeSt.empty()) {
|
||||
TreeNode* node = treeSt.top(); treeSt.pop(); // 取出节点 中
|
||||
string path = pathSt.top();pathSt.pop(); // 取出该节点对应的路径
|
||||
if (node->left == NULL && node->right == NULL) { // 遇到叶子节点
|
||||
result.push_back(path);
|
||||
}
|
||||
if (node->right) { // 右
|
||||
treeSt.push(node->right);
|
||||
pathSt.push(path + "->" + to_string(node->right->val));
|
||||
}
|
||||
if (node->left) { // 左
|
||||
treeSt.push(node->left);
|
||||
pathSt.push(path + "->" + to_string(node->left->val));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
```
|
||||
当然,使用java的同学,可以直接定义一个成员变量为object的栈`Stack<Object> stack = new Stack<>();`,这样就不用定义两个栈了,都放到一个栈里就可以了。
|
||||
|
||||
# 总结
|
||||
|
||||
**本文我们开始初步涉及到了回溯,很多同学过了这道题目,可能都不知道自己其实使用了回溯,回溯和递归都是相伴相生的。**
|
||||
|
||||
我在第一版递归代码中,把递归与回溯的细节都充分的展现了出来,大家可以自己感受一下。
|
||||
|
||||
第二版递归代码对于初学者其实非常不友好,代码看上去简单,但是隐藏细节于无形。
|
||||
|
||||
最后我依然给出了迭代法。
|
||||
|
||||
对于本地充分了解递归与回溯的过程之后,有精力的同学可以在去实现迭代法。
|
||||
|
||||
|
||||
|
||||
|
58
problems/0513.找树左下角的值.md
Normal file
58
problems/0513.找树左下角的值.md
Normal file
@ -0,0 +1,58 @@
|
||||
|
||||
## C++代码递归
|
||||
|
||||
```
|
||||
class Solution {
|
||||
public:
|
||||
int maxLen = INT_MIN;
|
||||
int maxleftValue;
|
||||
void traversal(TreeNode* root, int leftLen) {
|
||||
if (root->left == NULL && root->right == NULL) {
|
||||
if (leftLen > maxLen) {
|
||||
maxleftValue = root->val;
|
||||
maxLen = leftLen;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (root->left) {
|
||||
leftLen++;
|
||||
traversal(root->left, leftLen);
|
||||
leftLen--;
|
||||
}
|
||||
if (root->right) {
|
||||
leftLen++;
|
||||
traversal(root->right, leftLen);
|
||||
leftLen--;
|
||||
}
|
||||
return;
|
||||
}
|
||||
int findBottomLeftValue(TreeNode* root) {
|
||||
traversal(root, 0);
|
||||
return maxleftValue;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## C++代码层序遍历
|
||||
|
||||
```
|
||||
class Solution {
|
||||
public:
|
||||
int findBottomLeftValue(TreeNode* root) {
|
||||
queue<TreeNode*> que;
|
||||
if (root != NULL) que.push(root);
|
||||
int result = 0;
|
||||
while (!que.empty()) {
|
||||
int size = que.size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
TreeNode* node = que.front();
|
||||
que.pop();
|
||||
if (i == 0) result = node->val;
|
||||
if (node->left) que.push(node->left);
|
||||
if (node->right) que.push(node->right);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
```
|
@ -23,3 +23,47 @@ public:
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
|
||||
```
|
||||
class Solution {
|
||||
public:
|
||||
bool isSameTree(TreeNode* p, TreeNode* q) {
|
||||
if (p == NULL && q == NULL) return true;
|
||||
if (p == NULL || q == NULL) return false;
|
||||
queue<TreeNode*> que;
|
||||
que.push(p); //
|
||||
que.push(q); //
|
||||
while (!que.empty()) { //
|
||||
TreeNode* leftNode = que.front(); que.pop();
|
||||
TreeNode* rightNode = que.front(); que.pop();
|
||||
if (!leftNode && !rightNode) { //
|
||||
continue;
|
||||
}
|
||||
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); //
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool isSubtree(TreeNode* s, TreeNode* t) {
|
||||
stack<TreeNode*> st;
|
||||
vector<int> result;
|
||||
if (s == NULL) return false;
|
||||
st.push(s);
|
||||
while (!st.empty()) {
|
||||
TreeNode* node = st.top(); // 中
|
||||
st.pop();
|
||||
if(isSameTree(node, t)) return true;
|
||||
if (node->right) st.push(node->right); // 右(空节点不入栈)
|
||||
if (node->left) st.push(node->left); // 左(空节点不入栈)
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
Reference in New Issue
Block a user