今天我们继续进入二叉树的下一个章节,今天的内容我在写今天的博客前大致看了一下部分题目难度不算大,那我们就进入今天的题目。
第一题对应力扣编号为654的题目最大二叉树
这道题目的坑相当多,我第一次题目没有看明白就是我不知道到底是如何构造出这棵二叉树的,我就直接看了视频讲解,现在我们先来看一下题目:
我们先看一下示例是什么意思,注意力扣上是这样解释的:
首先找出最大值,然后分割数组找到左边部分与右边部分然后递归构造出根节点的左子树与右子树就可以了,这样我才明白这道题目究竟是什么意思,既然要构造二叉树大家记住,我们大概率会选用前序遍历,为什么呢?因为我们构造二叉树一定是先要从根节点出发才能逐步构建起它的左子树与右子树进而递归构造出整棵二叉树,这个是大家一定要去了解的,后来看完讲解我发现这道题与我们昨天的最后一道题有异曲同工之妙,其实都还是递归构建,当然我们这里是需要先找出最大的元素作为根节点,随后我们就可以找到左子树与右子树,递归构建就可以了,我要说明一下注意我们后来的数组是左闭右开的,一定注意,我们传入数组递归构建就可以,我将代码放到下面大家可以自行参考:
class Solution {
public:
TreeNode* constructMaximumBinaryTree(vector<int>& nums) {
TreeNode* node = new TreeNode(0);
//特殊情况要考虑上不要忘记
if (nums.size() == 1) return new TreeNode(nums[0]);
//那为什么没有考虑数组为空的情况呢?因为题目说了数组的大小是大于等于1的
int maxValue = 0;//定义最大值
int index = 0;//定义最大值的下标
for (int i = 0; i < nums.size(); ++i)
{
if (nums[i] > maxValue)
{
maxValue = nums[i];
index = i;
}
}
node -> val = maxValue;
//构造左子树
if (index > 0)
{
vector<int> newVec(nums.begin(), nums.begin() + index);
node->left = constructMaximumBinaryTree(newVec);
}
//构造右子树
if (index < (nums.size() - 1))
{
vector<int> newVec(nums.begin() + index + 1, nums.end());
node -> right = constructMaximumBinaryTree(newVec);
}
return node;
}
};
那其实这道题目还有优化版本的代码大家可以发现我需要构造新的数组来存储左右子数,这样其实又耗时又耗空间,这里我再给出优化版的代码:
class Solution {
private:
// 在左闭右开区间[left, right),构造二叉树
TreeNode* traversal(vector<int>& nums, int left, int right) {
if (left >= right) return nullptr;
// 分割点下标:maxValueIndex
int maxValueIndex = left;
for (int i = left + 1; i < right; ++i) {
if (nums[i] > nums[maxValueIndex]) maxValueIndex = i;
}
TreeNode* root = new TreeNode(nums[maxValueIndex]);
// 左闭右开:[left, maxValueIndex)
root->left = traversal(nums, left, maxValueIndex);
// 左闭右开:[maxValueIndex + 1, right)
root->right = traversal(nums, maxValueIndex + 1, right);
return root;
}
public:
TreeNode* constructMaximumBinaryTree(vector<int>& nums) {
return traversal(nums, 0, nums.size());
}
};
这样就可以避免申请新的空间来重新建立数组,我们直接使用下标操作,不过建议大家先把最基础的版本搞明白其实就可以了。这道题目我就先分享到这里,接下来我们看下一道题目。
第二题对应力扣编号为617的题目合并二叉树
那这道题是什么意思呢?其实是将两棵树相对应位置的节点的数值相加最后可以得到一棵新的二叉树,我们来看一下具体的题目:
我们应该如何考虑这道题目呢?其实这道题目难点就在于它是同时操作两棵二叉树,而我们平时刷的题都是操作一棵二叉树,但是我希望这道题过后大家可以掌握应该如何同时操作两棵二叉树,首先大家要知道一点就是如果我在合并的过程中我如果其中一棵二叉树的当前的节点是空,其实我的新合并出来的二叉树就应该是另一棵二叉树的所对应节点的值,如果两棵二叉树都为空,那其实新二叉树的对应节点也为空,随后我们递归构建左子树与右子树就可以,最后返回根节点,当然我下面提供的代码是我在tree1的基础上更新,其实大家也可以开一棵新的二叉树,在这里我就提供一下更新版本的代码,如果开新的二叉树版本大家感兴趣的话可以去代码随想录网站去看:
class Solution {
public:
TreeNode* mergeTrees(TreeNode* root1, TreeNode* root2) {
if (root1 == NULL) return root2;
if (root2 == NULL) return root1;
//if (root1 == NULL && root2 == NULL) return NULL;
//其实两棵树的对应节点都为空的情况已经包含了(上面这一句可以不写)
root1 -> val += root2 -> val;
root1 -> left = mergeTrees(root1 -> left, root2 -> left);
root1 -> right = mergeTrees(root1 -> right, root2 -> right);
return root1;
}
};
第三题对应力扣编号为700的二叉搜索树中的搜索
在这里我们就引入了二叉搜索树了,我们先复习一下什么样的树叫做二叉搜索树,其实是左子树的所有数值小于根节点,右子树的所有数值大于根节点就是二叉搜索树,我们了解了定义以后我们一起来看一下题目:
我看到示例就大致明白了题目的具体要求了,其实题目给我们的是一个层序遍历和一个节点的值,要求我们返回以该节点为根的子树,那我们如何解决这道题目呢?其实这道题目的递归法与迭代法都不难理解,我两种思路都给大家展示一下,首先题目就很好利用了二叉搜索树的性质,其实我们是可以利用二叉搜索树的性质来判断我们当前搜索的是在左子树还是右子树,就是与根节点的值进行比较,如果小就说明我们目前在左子树,如果大说明我们目前在右子树,那这样我们可以写出如下的递归代码:
class Solution {
public:
TreeNode* searchBST(TreeNode* root, int val) {
if (root == NULL || root -> val == val) return root;
TreeNode* result = NULL;
//这时候在左子树
if (root -> val > val)
{
//注意我们使用一个临时变量来存储我们找到的值
result = searchBST(root -> left, val);
}
//这时候在右子树
else if (root -> val < val)
{
result = searchBST(root -> right, val);
}
return result;
}
};
接下来我们来看一下迭代法如何写,其实我们原来是不是写过迭代法,我们一些用到迭代法都是会用栈或者队列去模拟深度遍历和广度遍历,但这里我们考虑到二叉搜索树的特殊性,其实我们不需要这么麻烦了,它的节点是有序的我们就不需要四处搜索了,我们看一下代码是如何写的:
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;
//左根右的中序遍历
if (root -> left) traversal(root -> left);
vec.push_back(root -> val);
if (root -> right) traversal(root -> right);
}
public:
bool isValidBST(TreeNode* root) {
vec.clear();//先清空
traversal(root);//这样其实就将数组再次填满了
for (int i = 1; i < vec.size(); ++i)
{
if (vec[i] <= vec[i - 1]) return false;
}
return true;
}
};
这是一种最为直观的做法,大家务必要将这个技巧牢记于心,那其实还有其他方法,我们还是要注意我们二叉搜索树是左子树所有的节点都是小于根节点的,右子树所有的节点都是大于根节点的,千万不要不判断根节点与它的左右孩子的关系,这样是无法判断是不是二叉搜索树的,因为不符合我们二叉搜索树的定义,那我们应该如何判断呢?其实我们还是可以使用递归的,我们其实可以一直中序遍历,只要发现没有顺序的就可以直接return false,那我们的代码可以这样写:
class Solution {
public:
long long maxVal = LONG_MIN; // 因为后台测试数据中有int最小值
bool isValidBST(TreeNode* root) {
if (root == NULL) return true;
bool left = isValidBST(root->left);
// 中序遍历,验证遍历的元素是不是从小到大
if (maxVal < root->val) maxVal = root->val;
else return false;
bool right = isValidBST(root->right);
return left && right;
}
};
就是说我们一直更新maxVal如果左子树的节点小于根节点的值那我就一直赋值如果发现有大于的这在左子树这边就说明不是二叉搜索树了,我们来看一下代码:
class Solution {
public:
long long maxVal = LONG_MIN;
bool isValidBST(TreeNode* root) {
if (root == NULL) return true;
bool left = isValidBST(root -> left);
if (maxVal < root -> val) maxVal = root -> val;
else return false;
bool right = isValidBST(root -> right);
return left && right;
}
};
这个其实还是中序遍历,左根右的顺序,我们先看左子树,只要当前节点不为空就一直递归,先左再右,最后发现到了叶子节点,最后将根节点赋值给maxVal,接着去判断右边,如果最后左右都为true就是二叉搜索树了,这个递归还有点难度,就是我们是如何递归的,先左一直到叶子节点会返回true,随后我们赋值,再去判断右子树,当右子树也到了叶子节点的时候也会返回 true,根节点的右子树是一个意思,最后我们判断是否是true, 我还是建议大家用上面的第一种方法理解,这个递归作为选学,上面的那一种方法必须会。
总结
今天的题目还是挺有意思的,我们也接触了二叉搜索树,最大二叉树是一道不错的题目,我们学会了递归与分割数组,合并二叉树其实还是递归,随后的两道题我们接触了二叉搜索树,大家自己要去消化一下,我们明天再见!