周日休息了一天,今天继续二叉树
之前二叉树问题的解法主要就是三种,一种是递归三部曲,是一种分解问题的思想,明确递归函数的作用很关键;另一种是回溯,回溯函数的内容就是刚到一个二叉树节点,应该怎么进行下一步行动;最后一种是通过队列迭代进行层序遍历,外层循环处理层内层循环处理层中单个节点
654.最大二叉树
本题该用哪种方法呢?递归三部曲!
首先,明确递归函数的功能:能够基于一个数组构建最大二叉树,并返回树的根节点
然后根据这个递归函数的功能思考递归三部曲的每一个部分
明确递归函数参数和返回值:基于数组构建最大二叉树,总要传进来一个数组吧,返回树的根节点
TreeNode* constructMaximumBinaryTree(vector<int>& nums)
确定终止条件:当数组为空,基于该数组构建的树为空树,直接返回NULL
if (nums.size() == 0) return NULL;
确定单层递归的逻辑: 根据题目描述和递归函数的功能,我们需要找到一个数组中的最大值,将其作为根节点的值,然后分别根据该最大值左边和右边的子数组构建左右子树并接在根节点上。构建子树能够通过递归函数本身实现,因为该递归函数的功能本身就是“基于数组构建最大二叉树”
/**
* 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* constructMaximumBinaryTree(vector<int>& nums) {
if (nums.size() == 0)
return nullptr;
auto iter = max_element(nums.begin(), nums.end());
TreeNode * root = new TreeNode;
root -> val = *iter;
vector<int> left, right;
left.assign(nums.begin(), iter);
right.assign(iter + 1, nums.end());
root -> left = constructMaximumBinaryTree(left);
root -> right = constructMaximumBinaryTree(right);
return root;
}
};
这道题其实和Day18讲的构建二叉树很像,主打一个找根节点,然后分割数组,然后递归调用
617.合并二叉树
本题一个关键点在于,要同时操作两个二叉树
又何妨?还是递归三部曲!
定义递归函数功能:能够按照规则合并两棵二叉树,返回合并后的二叉树的根节点
确定参数与返回值: 参数传入两棵二叉树,返回一个根节点
TreeNode* mergeTrees(TreeNode* t1, TreeNode* t2)
确定终止条件:要合并两二叉树吧, 总要两棵二叉树都有吧。当其中一棵二叉树为空树,则合并之后就应该是另一棵二叉树
if (t1 == NULL) return t2; // 如果t1为空,合并之后就应该是t2
if (t2 == NULL) return t1; // 如果t2为空,合并之后就应该是t1
确定单层递归逻辑:当两棵树都不是空树,说明这两棵树至少有根节点,那么我们应该怎样合并这两棵树呢?
单层递归逻辑
其中,合并这两棵树子树的操作可以通过递归调用函数本身实现,因为该递归函数本身作用就是合并两棵树
/**
* 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* mergeTrees(TreeNode* root1, TreeNode* root2) {
if (root1 == nullptr) return root2;
if (root2 == nullptr) return root1; // 终止条件:当其中一棵树为空树,则合并之后应该返回另一棵树
// 当两棵树都非空时才执行以下代码逻辑
TreeNode * root = new TreeNode(root1 -> val + root2 -> val); // 合并根节点
root -> left = mergeTrees(root1 -> left, root2 -> left); // 合并这两棵树的左子树得到合并后的左子树
root -> right = mergeTrees(root1 -> right, root2 -> right); // 合并这两棵树的右子树得到合并后的右子树
return root;
}
};
700.二叉搜索树中的搜索
什么是二叉搜索树(BST)?
- 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
- 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
- 它的左、右子树也分别为二叉搜索树
本题比较简单,可以用递归三部曲做,令递归函数的功能为:在 BST 中找到节点值等于 val 的节点,返回以该节点为根的子树
确定参数和返回值
TreeNode* searchBST(TreeNode* root, int val)
确定终止条件:若树本身为空树,则找不到满足条件的子树,返回一个空树
if (root == NULL) return root;
确定单层递归逻辑: 根据 val 与根节点值的大小关系决定继续在根节点的左子树还是右子树继续搜索,亦或是能够直接返回了。在子树继续搜索可以利用递归函数本身实现
/**
* 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* searchBST(TreeNode* root, int val) {
if (root == nullptr)
return nullptr; // 终止条件
if (root -> val < val) // 根据val与根节点值的大小关系决定继续在子树搜索还是直接返回
return searchBST(root -> right, val);
else if (root -> val > val)
return searchBST(root -> left, val);
else
return root;
}
};
本题只要掌握二叉搜索树的性质就很简单了
还能用迭代法:因为搜索方向是明确的
class Solution {
public:
TreeNode* searchBST(TreeNode* root, int val) {
while (root != NULL) {
if (root->val > val) root = root->left;
else if (root->val < val) root = root->right;
else return root;
}
return NULL;
}
};
98.验证二叉搜索树
根据二叉搜索树的特性:中序遍历下,输出的二叉搜索树节点的数值是递增序列
因此一种思路是对中序遍历的结果数组判断是否递增
class Solution {
private:
vector<int> vec;
void traversal(TreeNode* root) {
if (root == NULL) return;
traversal(root->left);
vec.push_back(root->val); // 将二叉搜索树转换为有序数组
traversal(root->right); // 中序遍历
}
public:
bool isValidBST(TreeNode* root) {
vec.clear(); // 不加这句在leetcode上也可以过,但最好加上
traversal(root);
for (int i = 1; i < vec.size(); i++) {
// 注意要小于等于,搜索树里不能有相同元素
if (vec[i] <= vec[i - 1]) return false;
}
return true;
}
};
以上代码中,我们把二叉树转变为数组来判断,是最直观的,但其实不用转变成数组,可以在递归遍历的过程中直接判断是否有序
因为在中序遍历的过程中,递归函数将依次走过每个节点,如果能一直记录前一个节点,那么在递归函数走到一个节点的时候,和前一个节点对比即可
如果还不太明白怎么写,直接上递归三部曲的套路
明确递归函数的功能:中序遍历并判断以 root 为根节点的二叉树是否是一个有效的二叉搜索树
确定参数和返回值:传入节点表示树,返回 bool 表示是否是有效二叉搜索树
bool isValidBST(TreeNode* root)
确定终止条件:如果是空树,可以认为空树是二叉搜索树,直接返回
if (root == NULL) return true;
确定单层递归逻辑:首先遍历左子树上的节点判断左子树是不是二叉搜索树,然后遍历处理根节点,将根节点和前一个节点做对比判断根节点是否满足二叉搜索树根节点所需要满足的条件,然后遍历右子树上的节点判断右子树是不是二叉搜索树。只有左子树为二叉搜索树、根节点满足二叉搜索树根节点所需要满足的条件、右子树为二叉搜索树三个条件同时满足的时候,才能判定整棵树为二叉搜索树。遍历子树并判断能够使用递归函数本身
class Solution {
public:
TreeNode* pre = NULL; // 用来记录前一个节点
bool isValidBST(TreeNode* root) { // 中序遍历
if (root == NULL) return true; // 终止条件
bool left = isValidBST(root->left); // 左
if (pre != NULL && pre->val >= root->val) return false; // 中
pre = root; // 处理完当前节点了,将其记录为前一个节点,便于接下来遍历右子树
bool right = isValidBST(root->right); // 右
return left && right;
}
};
这其实就是二叉树遍历过程中的双指针法,pre 指向前一个节点,然后递归函数正在处理的根节点是当前节点。注意访问 pre->val 之前需要判非空
回顾总结
二叉树遍历过程中利用双指针的方法留意一下