二叉树解题的思维模式分两类:
1、是否可以通过遍历一遍二叉树得到答案?如果可以,用一个 traverse
函数配合外部变量来实现,这叫「遍历」的思维模式。
2、是否可以定义一个递归函数,通过子问题(子树)的答案推导出原问题的答案?如果可以,写出这个递归函数的定义,并充分利用这个函数的返回值,这叫「分解问题」的思维模式。
无论使用哪种思维模式,你都需要思考:
如果单独抽出一个二叉树节点,它需要做什么事情?需要在什么时候(前/中/后序位置)做?其他的节点不用你操心,递归函数会帮你在所有节点上执行相同的操作。
本文主要针对第二个思维模式进行解题思路的总结:
例题:力扣第 226 题「 翻转二叉树」
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
}
};
解题思维模式:
1.尝试给invertTree函数赋予一个定义,可以解决这道题。(并且确定函数的参数和返回值)
定义:将以root为根的这颗二叉树翻转,然后返回二叉树的根节点
2.思考递归函数的终止条件是什么?
例如:if(root==NULL) reutrn root;
3.然后思考,对于某一个二叉树节点 x
执行 invertTree(x)
,你能利用这个递归函数的定义做点啥?(也叫做单层递归的逻辑)
我可以用 invertTree(x->left)
先把 x
的左子树翻转,再用 invertTree(x->right)
把 x
的右子树翻转,最后把 x
的左右子树交换,这恰好完成了以 x
为根的整棵二叉树的翻转,即完成了 invertTree(x)
的定义。
那么下面就是解题代码:
// 定义:将以 root 为根的这棵二叉树翻转,返回翻转后的二叉树的根节点
TreeNode* invertTree(TreeNode* root) {
if (root == nullptr) {
return nullptr;
}
// 利用函数定义,先翻转左右子树
TreeNode* left = invertTree(root->left);
TreeNode* right = invertTree(root->right);
// 然后交换左右子节点
root->left = right;
root->right = left;
// 和定义逻辑自恰:以 root 为根的这棵二叉树已经被翻转,返回 root
return root;
}
二、例题:力扣第 116 题「 填充每个二叉树节点的右侧指针」
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
void flatten(TreeNode* root) {
}
};
解题思维模式:
1.尝试给flatten函数赋予一个定义,可以解决这道题。(并且确定函数的参数和返回值)
定义:输入节点root,然后root为根的二叉树就会被拉平成一条链表
2.思考递归函数的终止条件是什么?
例如:if(root==NULL) reutrn;
3.有了这个函数定义,如何按题目要求把一棵树拉平成一条链表?
对于一个节点 x
,可以执行以下流程:
1、先利用 flatten(x->left)
和 flatten(x->right)
将 x
的左右子树拉平。
2、将 x
的右子树接到左子树下方,然后将整个左子树作为右子树。
3、先将左子树拉平,再将右子树拉平。将左子树置空,将右子树指向原来的左子树,再将原来的右子树接到现在的右子树下面。
那么下面就是解题代码:
// 定义:将以 root 为根的树拉平为链表
void flatten(TreeNode* root) {
// base case
if (root == nullptr) return;
// 利用定义,把左右子树拉平
flatten(root->left);
flatten(root->right);
/**** 后序遍历位置 ****/
// 1、左右子树已经被拉平成一条链表
TreeNode* left = root->left;
TreeNode* right = root->right;
// 2、将左子树作为右子树
root->left = nullptr;
root->right = left;
// 3、将原先的右子树接到当前右子树的末端
TreeNode* p = root;
while (p->right != nullptr) {
p = p->right;
}
p->right = right;
}