This commit is contained in:
youngyangyang04
2020-09-22 15:04:05 +08:00
parent 4fc2915484
commit d2311e0fcf
8 changed files with 340 additions and 0 deletions

View File

@ -7,6 +7,7 @@ https://leetcode-cn.com/problems/binary-tree-level-order-traversal/
相似题目:
* [0102.二叉树的层序遍历](https://github.com/youngyangyang04/leetcode/blob/master/problems/0102.二叉树的层序遍历.md)
* [0107.二叉树的层次遍历II](https://github.com/youngyangyang04/leetcode/blob/master/problems/0107.二叉树的层次遍历II.md)
* [0199.二叉树的右视图](https://github.com/youngyangyang04/leetcode/blob/master/problems/0199.二叉树的右视图.md)
* [0637.二叉树的层平均值](https://github.com/youngyangyang04/leetcode/blob/master/problems/0637.二叉树的层平均值.md)

View File

@ -0,0 +1,116 @@
## 题目地址
https://leetcode-cn.com/problems/convert-bst-to-greater-tree/
## 思路
一看到累加树,相信很多小伙伴一脸懵逼,如何累加,遇到一个节点,然后在遍历其他节点累加?怎么一想这么麻烦呢。
然后发现这是一颗二叉搜索树,二叉搜索树啊,这是有序的啊。
那么有序的元素如果求累加呢?
**其实这就是一棵树,大家可能看起来有点别扭,换一个角度来看,这就是一个有序数组[2, 5, 13],求从后到前的累加数组,也就是[20, 18, 13],大家是不是感觉这就是送分题了。**
为什么变成数组就是送分题了呢,因为数组大家都知道怎么遍历啊,从后向前,挨个累加就完事了,这换成了二叉搜索树,看起来就别扭了一些是不是。
那么知道如何遍历这个二叉树,也就迎刃而解了,从树中可以看出累加的顺讯是 右中左,所以我们需要中序遍历反过来遍历这个二叉树,然后顺序累加就可以了。
遍历顺序如图所示:
<img src='../pics/538.把二叉搜索树转换为累加树.png' width=600> </img></div>
以下我给出一种递归的写法,两种迭代法的写法,别问我为什么写出了这么多写法,把我写的这个题解[彻底吃透二叉树的前中后序递归法和迭代法!!](https://leetcode-cn.com/problems/binary-tree-inorder-traversal/solution/che-di-chi-tou-er-cha-shu-de-qian-zhong-hou-xu-d-2/)看了,你也能分分钟写出来三种写法![机智]
## C++递归代码
```
class Solution {
private:
int pre; // 记录前一个节点的数值
void traversal(TreeNode* cur) { // 右中左遍历
if (cur == NULL) return;
traversal(cur->right);
cur->val += pre;
pre = cur->val;
traversal(cur->left);
}
public:
TreeNode* convertBST(TreeNode* root) {
pre = 0;
traversal(root);
return root;
}
};
```
## C++迭代法(一)代码
```
class Solution {
private:
int pre; // 记录前一个节点的数值
void traversal(TreeNode* root) {
stack<TreeNode*> st;
TreeNode* cur = root;
while (cur != NULL || !st.empty()) {
if (cur != NULL) {
st.push(cur);
cur = cur->right; // 右
} else {
cur = st.top(); // 中
st.pop();
cur->val += pre;
pre = cur->val;
cur = cur->left; // 左
}
}
}
public:
TreeNode* convertBST(TreeNode* root) {
pre = 0;
traversal(root);
return root;
}
};
```
## C++迭代法(二)代码
```
class Solution {
private:
int pre; // 记录前一个节点的数值
void traversal(TreeNode* root) {
stack<TreeNode*> st;
if (root != NULL) st.push(root);
while (!st.empty()) {
TreeNode* node = st.top();
if (node != NULL) {
st.pop();
if (node->left) st.push(node->left); // 左
st.push(node); // 中
st.push(NULL);
if (node->right) st.push(node->right); // 右
} else {
st.pop();
node = st.top();
st.pop();
node->val += pre; // 处理中间节点
pre = node->val;
}
}
}
public:
TreeNode* convertBST(TreeNode* root) {
pre = 0;
traversal(root);
return root;
}
};
```

View File

@ -0,0 +1,217 @@
# 题目地址
https://leetcode-cn.com/problems/binary-tree-cameras/
## 思路
这道题目其实不是那么好理解的,题目举的示例不是很典型,会误以为摄像头必须要放在中间,其实放哪里都可以只要覆盖了就行。
这道题目难在两点:
1. 需要确定遍历方式
2. 需要状态转移的方程
我们之前做动态规划的时候,只要最难的地方在于确定状态转移方程,至于遍历方式无非就是在数组或者二维数组上。
**而本题,不仅要确定状态转移方式,而且要在树上进行推导,所以难度就上来了,一些同学知道这道题目难,但其实说不上难点究竟在哪。**
1. 需要确定遍历方式
首先先确定遍历方式,才能确定转移方程,那么该如何遍历呢?
在安排选择摄像头的位置的时候,**我们要从底向上进行推导,因为尽量让叶子节点的父节点安装摄像头,这样摄像头的数量才是最少的**
如何从低向上推导呢?
就是后序遍历也就是左右中的顺序,这样就可以从下到上进行推导了。
后序遍历代码如下:
```
int traversal(TreeNode* cur) {
// 空节点,该节点有覆盖
if (终止条件) return ;
int left = traversal(cur->left); // 左
int right = traversal(cur->right); // 右
逻辑处理 // 中
return ;
}
```
**注意在以上代码中我们取了左孩子的返回值右孩子的返回值即left 和 right 以后推导中间节点的状态**
2. 需要状态转移的方程
确定了遍历顺序,再看看这个状态应该如何转移,先来看看每个节点可能有几种状态:
可以说有如下三种:
* 该节点无覆盖
* 本节点有摄像头
* 本节点有覆盖
我们分别有三个数字来表示:
* 0该节点无覆盖
* 1本节点有摄像头
* 2本节点有覆盖
大家应该找不出第四个节点的状态了。
那么问题来了,空节点究竟是哪一种状态呢? 空节点表示无覆盖? 表示有摄像头?还是有覆盖呢?
回归本质,为了让摄像头数量最少,我们要尽量让叶子节点的父节点安装摄像头,这样才能摄像头的数量最少。
那么空节点不能是无覆盖的状态,这样叶子节点就可以放摄像头了,空节点也不能是有摄像头的状态,这样叶子节点的父节点就没有必要放摄像头了,而是可以把摄像头放在叶子节点的爷爷节点上。
**所以空节点的状态只能是有覆盖,这样就可以在叶子节点的父节点放摄像头了**
接下来就是递推关系。
那么递归的终止条件应该是遇到了空节点此时应该返回2有覆盖原因上面已经解释过了。
代码如下:
```
// 空节点,该节点有覆盖
if (cur == NULL) return 2;
```
递归的函数,以及终止条件已经确定了,再来看单层逻辑处理。
主要有如下四类情况:
1. 情况1左右节点都有覆盖
左孩子有覆盖,右孩子有覆盖,那么此时中间节点应该就是无覆盖的状态了。
如图:
<img src='../pics/968.监控二叉树2.png' width=600> </img></div>
代码如下:
```
// 左右节点都有覆盖
if (left == 2 && right == 2) return 0;
```
2. 情况2左右节点至少有一个无覆盖的情况
如果是以下情况,则中间节点(父节点)应该放摄像头:
left == 0 && right == 0 左右节点无覆盖
left == 1 && right == 0 左节点有摄像头,右节点无覆盖
left == 0 && right == 1 左节点有无覆盖,右节点摄像头
left == 0 && right == 2 左节点无覆盖,右节点覆盖
left == 2 && right == 0 左节点覆盖,右节点无覆盖
这个不难理解,毕竟有一个孩子没有覆盖,父节点就应该放摄像头。
此时摄像头的数量要加一并且return 1代表中间节点放摄像头。
代码如下:
```
if (left == 0 || right == 0) {
result++;
return 1;
}
```
3. 情况3左右节点至少有一个有摄像头
如果是以下情况,其实就是 左右孩子节点有一个有摄像头了那么其父节点就应该是2覆盖的状态
left == 1 && right == 2 左节点有摄像头,右节点有覆盖
left == 2 && right == 1 左节点有覆盖,右节点有摄像头
left == 1 && right == 1 左右节点都有摄像头
代码如下:
```
if (left == 1 || right == 1) return 2;
```
**从这个代码中可以看出如果left == 1, right == 0 怎么办其实这种条件在情况2中已经判断过了**,如图:
<img src='../pics/968.监控二叉树1.png' width=600> </img></div>
这种情况也是大多数同学容易迷惑的情况。
4. 情况4
以上都处理完了,递归结束之后,可能头结点 还有一个无覆盖的情况,如图:
<img src='../pics/968.监控二叉树3.png' width=600> </img></div>
所以递归结束之后还要判断根节点如果没有覆盖result++,代码如下:
```
int minCameraCover(TreeNode* root) {
result = 0;
if (traversal(root) == 0) { // root 无覆盖
result++;
}
return result;
}
```
以上四种情况我们分析完了,代码也差不多了,整体代码如下:
**以下我的代码是可以精简的,但是我是为了把情况说清楚,特别把每种情况列出来,因为精简之后的代码读者不好理解。**
## C++代码
```
class Solution {
private:
int result;
int traversal(TreeNode* cur) {
// 空节点,该节点有覆盖
if (cur == NULL) return 2;
int left = traversal(cur->left); // 左
int right = traversal(cur->right); // 右
// 情况1
// 左右节点都有覆盖
if (left == 2 && right == 2) return 0;
// 情况2
// left == 0 && right == 0 左右节点无覆盖
// left == 1 && right == 0 左节点有摄像头,右节点无覆盖
// left == 0 && right == 1 左节点有无覆盖,右节点摄像头
// left == 0 && right == 2 左节点无覆盖,右节点覆盖
// left == 2 && right == 0 左节点覆盖,右节点无覆盖
if (left == 0 || right == 0) {
result++;
return 1;
}
// 情况3
// left == 1 && right == 2 左节点有摄像头,右节点有覆盖
// left == 2 && right == 1 左节点有覆盖,右节点有摄像头
// left == 1 && right == 1 左右节点都有摄像头
// 其他情况前段代码均已覆盖
if (left == 1 || right == 1) return 2;
// 以上代码我没有使用else主要是为了把各个分支条件展现出来这样代码有助于读者理解
// 这个 return -1 逻辑不会走到这里。
return -1;
}
public:
int minCameraCover(TreeNode* root) {
result = 0;
// 情况4
if (traversal(root) == 0) { // root 无覆盖
result++;
}
return result;
}
};
```