代码随想录拓展day5 129. 求根节点到叶节点数字之和;1382.将二叉搜索树变平衡;100. 相同的树;116. 填充每个节点的下一个右侧节点指针
全部都是关于二叉树的题目,对二叉树的遍历方式又是一个复习。
129. 求根节点到叶节点数字之和
https://leetcode.cn/problems/sum-root-to-leaf-numbers/
关键是把数组转数字的这个操作要熟悉。其余了还是类似回溯的二叉树递归的方法。
思路
首先思路很明确,就是要遍历整个树把更节点到叶子节点组成的数字相加。
那么先按递归三部曲来分析:
递归三部曲
- 确定递归函数返回值及其参数
这里我们要遍历整个二叉树,且需要要返回值做逻辑处理,所有返回值为void。
参数只需要把根节点传入,此时还需要定义两个全局遍历,一个是result,记录最终结果,一个是vector path。
为什么用vector类型(就是数组)呢? 因为用vector方便我们做回溯!
所以代码如下:
int result;
vector<int> path;
void traversal(TreeNode* cur)
- 确定终止条件
递归什么时候终止呢?
当然是遇到叶子节点,此时要收集结果了,通知返回本层递归,因为单条路径的结果使用vector,我们需要一个函数vectorToInt把vector转成int。
终止条件代码如下:
if (!cur->left && !cur->right) { // 遇到了叶子节点
result += vectorToInt(path);
return;
}
这里vectorToInt函数就是把数组转成int,代码如下:
int vectorToInt(const vector<int>& vec) {
int sum = 0;
for (int i = 0; i < vec.size(); i++) {
sum = sum * 10 + vec[i];
}
return sum;
}
- 确定递归单层逻辑
本题其实采用前中后序都不无所谓, 因为也没有中间几点的处理逻辑。
这里主要是当左节点不为空,path收集路径,并递归左孩子,右节点同理。
但别忘了回溯。
如图:
代码如下:
// 中
if (cur->left) { // 左 (空节点不遍历)
path.push_back(cur->left->val);
traversal(cur->left); // 递归
path.pop_back(); // 回溯
}
if (cur->right) { // 右 (空节点不遍历)
path.push_back(cur->right->val);
traversal(cur->right); // 递归
path.pop_back(); // 回溯
}
这里要注意回溯和递归要永远在一起,一个递归,对应一个回溯,是一对一的关系,有的同学写成如下代码:
if (cur->left) { // 左 (空节点不遍历)
path.push_back(cur->left->val);
traversal(cur->left); // 递归
}
if (cur->right) { // 右 (空节点不遍历)
path.push_back(cur->right->val);
traversal(cur->right); // 递归
}
path.pop_back(); // 回溯
把回溯放在花括号外面了,世界上最遥远的距离,是你在花括号里,而我在花括号外! 这就不对了。
整体C++代码
关键逻辑分析完了,整体C++代码如下:
class Solution {
private:
int result;
vector<int> path;
// 把vector转化为int
int vectorToInt(const vector<int>& vec) {
int sum = 0;
for (int i = 0; i < vec.size(); i++) {
sum = sum * 10 + vec[i];
}
return sum;
}
void traversal(TreeNode* cur) {
if (!cur->left && !cur->right) { // 遇到了叶子节点
result += vectorToInt(path);
return;
}
if (cur->left) { // 左 (空节点不遍历)
path.push_back(cur->left->val); // 处理节点
traversal(cur->left); // 递归
path.pop_back(); // 回溯,撤销
}
if (cur->right) { // 右 (空节点不遍历)
path.push_back(cur->right->val); // 处理节点
traversal(cur->right); // 递归
path.pop_back(); // 回溯,撤销
}
return ;
}
public:
int sumNumbers(TreeNode* root) {
path.clear();
if (root == nullptr) return 0;
path.push_back(root->val);
traversal(root);
return result;
}
};
1382.将二叉搜索树变平衡
https://leetcode.cn/problems/balance-a-binary-search-tree/
本来以为要原地调整,但是可以把搜索二叉树转成有序数组,这就成了一道还原平衡二叉树的题目了。
思路
这道题目,可以中序遍历把二叉树转变为有序数组,然后在根据有序数组构造平衡二叉搜索树。
代码如下:
class Solution {
private:
vector<int> vec;
// 有序树转成有序数组
void traversal(TreeNode* cur) {
if (cur == nullptr) {
return;
}
traversal(cur->left);
vec.push_back(cur->val);
traversal(cur->right);
}
// 有序数组转平衡二叉树
TreeNode* getTree(vector<int>& nums, int left, int right) {
if (left > right) return nullptr;
int mid = left + ((right - left) / 2);
TreeNode* root = new TreeNode(nums[mid]);
root->left = getTree(nums, left, mid - 1);
root->right = getTree(nums, mid + 1, right);
return root;
}
public:
TreeNode* balanceBST(TreeNode* root) {
traversal(root);
return getTree(vec, 0, vec.size() - 1);
}
};
100. 相同的树
https://leetcode.cn/problems/same-tree/
和验证对称二叉树有些类似。
思路
我们讲到对于二叉树是否对称,要比较的是根节点的左子树与右子树是不是相互翻转的,理解这一点就知道了其实我们要比较的是两个树(这两个树是根节点的左右子树),所以在递归遍历的过程中,也是要同时遍历两棵树。
理解这一本质之后,就会发现,求二叉树是否对称,和求二叉树是否相同几乎是同一道题目。
递归三部曲中:
- 确定递归函数的参数和返回值
我们要比较的是两个树是否是相互相同的,参数也就是两个树的根节点。
返回值自然是bool类型。
代码如下:
bool compare(TreeNode* tree1, TreeNode* tree2)
- 确定终止条件
要比较两个节点数值相不相同,首先要把两个节点为空的情况弄清楚!否则后面比较数值的时候就会操作空指针了。
节点为空的情况有:
- 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
- 确定单层递归的逻辑
- 比较二叉树是否相同 :传入的是tree1的左孩子,tree2的右孩子。
- 如果左右都相同就返回true ,有一侧不相同就返回false 。
代码如下:
bool left = compare(tree1->left, tree2->left); // 左子树:左、 右子树:左
bool right = compare(tree1->right, tree2->right); // 左子树:右、 右子树:右
bool isSame = left && right; // 左子树:中、 右子树:中(逻辑处理)
return isSame;
最后递归的C++整体代码如下:
class Solution {
public:
bool compare(TreeNode* tree1, TreeNode* 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
// 此时就是:左右节点都不为空,且数值相同的情况
// 此时才做递归,做下一层的判断
bool left = compare(tree1->left, tree2->left); // 左子树:左、 右子树:左
bool right = compare(tree1->right, tree2->right); // 左子树:右、 右子树:右
bool isSame = left && right; // 左子树:中、 右子树:中(逻辑处理)
return isSame;
}
bool isSameTree(TreeNode* p, TreeNode* q) {
return compare(p, q);
}
};
116. 填充每个节点的下一个右侧节点指针
https://leetcode.cn/problems/populating-next-right-pointers-in-each-node/
层序遍历好想一点,递归的方法还是有点绕的。
思路
注意题目提示内容,:
- 你只能使用常量级额外空间。
- 使用递归解题也符合要求,本题中递归程序占用的栈空间不算做额外的空间复杂度。
基本上就是要求使用递归了,迭代的方式一定会用到栈或者队列。
递归
一想用递归怎么做呢,虽然层序遍历是最直观的,但是递归的方式确实不好想。
如图,假如当前操作的节点是cur:
最关键的点是可以通过上一层递归 搭出来的线,进行本次搭线。
图中cur节点为元素4,那么搭线的逻辑代码:(注意注释中操作1和操作2和图中的对应关系)
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 = NULL;
}
理解到这里,使用前序遍历,那么不难写出如下代码:
class Solution {
private:
void traversal(Node* cur) {
if (cur == NULL) 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 = NULL;
}
traversal(cur->left); // 左
traversal(cur->right); // 右
}
public:
Node* connect(Node* root) {
traversal(root);
return root;
}
};
迭代(层序遍历)
本题使用层序遍历是最为直观的,如果对层序遍历不了解,看这篇:二叉树:层序遍历登场! (opens new window)。
遍历每一行的时候,如果不是最后一个Node,则指向下一个Node;如果是最后一个Node,则指向nullptr。
代码如下:
class Solution {
public:
Node* connect(Node* root) {
queue<Node*> que;
if (root != nullptr) que.push(root);
while (!que.empty()) {
int size = que.size();
for (int i = 0; i < size; ++i) {
Node* node = que.front();
que.pop();
if (i != size - 1) {
node->next = que.front(); //如果不是最后一个Node 则指向下一个Node
} else node->next = nullptr; //如果是最后一个Node 则指向nullptr
if (node->left != nullptr) que.push(node->left);
if (node->right != nullptr) que.push(node->right);
}
}
return root;
}
};
还有一种方法,可以看出每一层的指向方向是从左向右,但是层序遍历也是从左向右,这样要判断下一个指向的节点还要判断是不是这一层最右边的node。如果从右向左进行层序遍历,则可有效的把“上一个”节点记录下来,转换为next指针的指向。
class Solution {
public:
Node* connect(Node* root) {
queue<Node*> que;
if(root != NULL){
que.push(root);
}
while(!que.empty()){
int size = que.size();
Node* next = NULL;
for(int i = 0; i < size; i++){
Node* node = que.front();
que.pop();
node->next = next;
next = node;
if(node->right){
que.push(node->right);
}
if(node->left){
que.push(node->left);
}
}
}
return root;
}
};