目录
一、做题心得
今天是代码随想录打卡的第17天,来到了二叉树章节的part5。今天主要运用到的思想依旧是递归,事实证明,递归才是打开二叉树大门的钥匙。个人感觉今天的题总体难度不大,有了前些天的基础,相信都能有自己的思考和想法。不过今天也接触到了一点比较新的东西,就是二叉树搜索树的一些性质,等下会慢慢说到。
好了,直接开始今天的内容。
二、题目与题解
题目一:654.最大二叉树
题目链接
给定一个不重复的整数数组
nums
。 最大二叉树 可以用下面的算法从nums
递归地构建:
- 创建一个根节点,其值为
nums
中的最大值。- 递归地在最大值 左边 的 子数组前缀上 构建左子树。
- 递归地在最大值 右边 的 子数组后缀上 构建右子树。
返回
nums
构建的 最大二叉树 。示例 1:
输入:nums = [3,2,1,6,0,5] 输出:[6,3,5,null,2,0,null,null,1] 解释:递归调用如下所示: - [3,2,1,6,0,5] 中的最大值是 6 ,左边部分是 [3,2,1] ,右边部分是 [0,5] 。 - [3,2,1] 中的最大值是 3 ,左边部分是 [] ,右边部分是 [2,1] 。 - 空数组,无子节点。 - [2,1] 中的最大值是 2 ,左边部分是 [] ,右边部分是 [1] 。 - 空数组,无子节点。 - 只有一个元素,所以子节点是一个值为 1 的节点。 - [0,5] 中的最大值是 5 ,左边部分是 [0] ,右边部分是 [] 。 - 只有一个元素,所以子节点是一个值为 0 的节点。 - 空数组,无子节点。示例 2:
输入:nums = [3,2,1] 输出:[3,null,2,null,1]提示:
1 <= nums.length <= 1000
0 <= nums[i] <= 1000
nums
中的所有整数 互不相同
题解:递归
这个题其实跟昨天打卡的最后一道题:
106. 从中序与后序遍历序列构造二叉树 有些相似,都是要求我们构建二叉树。
构建二叉树一般我们选择前序遍历递归的做法,先构建根节点,再递归构建左右子树。这里题目要求以最大值作为根节点,我们找到最大值在数组中的位置索引index,每次递归的时候,我们都采用分割区间的办法来确定左右子树节点的位置范围。
这里我们自己定义一个函数build(),实现树的建立。(注意必须新构建一个函数,这样才能利于分割区间以及递归的实现--左右子树区间的边界作为函数参数)
代码如下:
class Solution {
public:
TreeNode* build(vector<int>& nums,int left,int right) {
//两个特殊情况(递归终止条件)
if(left > right) return nullptr; //当前子数组为空时
if(left == right) return new TreeNode(nums[left]); //当前子数组只有一个元素
int index = left; //记录最大值下标,初始化为左边界
for (int i = left + 1; i <= right; i++) { //遍历子数组,找到最大值的下标(最大值作为根节点)
if (nums[i] > nums[index]) {
index = i;
}
}
TreeNode* node = new TreeNode(nums[index]); //创建树(根节点的值为nums[index])
//递归构建左右子树
node->left = build(nums,left,index - 1); //左区间
node->right = build(nums,index + 1,right); //右区间
return node;
}
TreeNode* constructMaximumBinaryTree(vector<int>& nums) {
return build(nums,0,nums.size() - 1);
}
};
题目二:617.合并二叉树
题目链接
给你两棵二叉树:
root1
和root2
。想象一下,当你将其中一棵覆盖到另一棵之上时,两棵树上的一些节点将会重叠(而另一些不会)。你需要将这两棵树合并成一棵新二叉树。合并的规则是:如果两个节点重叠,那么将这两个节点的值相加作为合并后节点的新值;否则,不为 null 的节点将直接作为新二叉树的节点。
返回合并后的二叉树。
注意: 合并过程必须从两个树的根节点开始。
示例 1:
输入:root1 = [1,3,2,5], root2 = [2,1,3,null,4,null,7] 输出:[3,4,5,5,4,null,7]示例 2:
输入:root1 = [1], root2 = [1,2] 输出:[2,2]提示:
- 两棵树中的节点数目在范围
[0, 2000]
内-104 <= Node.val <= 104
题解:递归(前序遍历)
这个题个人感觉挺简单的,就是分别把对应节点的值加起来存放在一棵新树里边。这里我们很容易想到递归的方式,按照前序遍历递归添加元素。
class Solution {
public:
TreeNode* mergeTrees(TreeNode* root1, TreeNode* root2) {
if (root1 == nullptr) return root2;
if (root2 == nullptr) return root1;
TreeNode* node = new TreeNode(root1->val + root2->val);
//递归构建左右子树
node->left = mergeTrees(root1->left,root2->left);
node->right = mergeTrees(root1->right,root2->right);
return node;
}
};
题目三:617.合并二叉树
题目链接
给定二叉搜索树(BST)的根节点
root
和一个整数值val
。你需要在 BST 中找到节点值等于
val
的节点。 返回以该节点为根的子树。 如果节点不存在,则返回null
。示例 1:
输入:root = [4,2,7,1,3], val = 2 输出:[2,1,3]示例 2:
输入:root = [4,2,7,1,3], val = 5 输出:[]提示:
- 树中节点数在
[1, 5000]
范围内1 <= Node.val <= 107
root
是二叉搜索树1 <= val <= 107
题解:BFS层序遍历
这个题也是可以直接套BFS层序遍历模板,没什么难度,题目都只有一棵最大子树满足要求,所以找到目标节点返回对应的子树即可。
这里要注意的就是:TreeNode* node既可以表示节点,也可以表示一棵树(题中直接return node时表示以其为根节点的树)
代码如下:
class Solution {
public:
TreeNode* searchBST(TreeNode* root, int val) {
if(root == nullptr) return nullptr;
queue<TreeNode*> q;
q.push(root);
while (!q.empty()) {
TreeNode* node = q.front();
q.pop();
if (node->val == val) { //层序遍历找到目标节点
return node;
}
if(node->left) q.push(node->left);
if(node->right) q.push(node->right);
}
return nullptr;
}
};
题目四:98.验证二叉搜索树
题目链接
给你一个二叉树的根节点
root
,判断其是否是一个有效的二叉搜索树。有效 二叉搜索树定义如下:
- 节点的左
子树
只包含 小于 当前节点的数。- 节点的右子树只包含 大于 当前节点的数。
- 所有左子树和右子树自身必须也是二叉搜索树。
示例 1:
输入:root = [2,1,3] 输出:true示例 2:
输入:root = [5,1,4,null,null,3,6] 输出:false 解释:根节点的值是 5 ,但是右子节点的值是 4 。提示:
- 树中节点数目范围在
[1, 104]
内-231 <= Node.val <= 231 - 1
题解:递归(中序遍历)
这个题是我感觉今天做的题中最有收获的一道。
可能很多人一开始就想到判断当前节点是否比它的左子结点大以及是否比它的右子节点小,然后在对于该节点的左右节点分别递归,然后,很快就做出来了。但是,结果却出了错。
这里我们得知道二叉搜索树的定义:
1.节点的左子树只包含小于当前节点的数。
2.节点的右子树只包含大于当前节点的数。
3.所有左子树和右子树自身必须也是二叉搜索树。
这下其实仔细一想就知道,刚刚我们只检查了该节点与直接子节点的关系,没有考虑整个树的结构。换句话说,相隔较远的节点它就无法判断与根节点的大小关系。
所以这里我们引入一个新的思路:中序遍历递归。我们知道中序遍历是:左右根的顺序遍历节点,而二叉搜索树刚好就是左根右的大小排序(左<根<右)。然后用pre指针记录当前节点的前一个节点,与当前节点进行比较,判断是否满足逐渐递增即可。
好了,相信这时大家就有思路了,这里直接看代码(附详细注释):
class Solution {
public:
//这里注意:不能将TreeNode* pre = nullptr;放在isValidBST函数内部,否则每次递归pre都会重新变成nullptr
TreeNode* pre = nullptr; //用来记录当前节点的前一个节点
bool isValidBST(TreeNode* root) {
if (root == nullptr) return true; //注意:空树被认为是有效的BST
bool left = isValidBST(root->left); //递归地检查左子树是否是有效的BST(左)
if (pre != nullptr && pre->val >= root->val)
return false;
pre = root; //更新pre的位置:往后移动一个节点(根)
bool right = isValidBST(root->right); //递归地检查右子树是否是有效的BST(右)
return left && right; //left,right分别存储了递归判断左右子树是否为有效BST的结果,这里要求都满足的时候
}
};
三、小结
今天的打卡到此也就结束了,递归的应用也进一步加深,希望后边继续加油。最后,我是算法小白,但也希望终有所获。