654.最大二叉树
题目链接:
难度指数:😀😐😕
给我们一个数组,用这个数组来构造一棵二叉树。
规则:在数组中选取一个最大的元素作为根节点,然后最大元素的左区间再去继续构造左子树,仍然是选取一个最大的元素作为左子树的根节点;右区间同理。
在这个规则下构造出来的最大二叉树,
思考用什么样的遍历方式来构造这棵二叉树?
A:凡是构造二叉树类的题目,我们都要使用前序遍历!
前序遍历是中左右,即一定要先构造中。(要把根节点先构造出来,再去构造左子树,右子树;在左子树中依然先构造中,再构造左、右……)
动画:
代码思路:
确定递归函数的参数和返回值:
传入递归函数的是一个数组nums,最终要返回以这个数组构造的最大二叉树的根节点,因此返回类型是TreeNode*。
TreeNode* contruct(nums) {
//终止条件
if (nums.size() == 1) { //说明到叶子节点
return new TreeNode(nums[0]); //return数组里唯一一个元素
}
//确定单层递归的逻辑:找到数组中的最大值,还要找到最大值所在的下标
//中
int maxValue = 0; //最大值
int index = 0; //最大值所在的下标
//遍历数组
for (int i = 0; i < nums.size(); i++) {
if (nums[i] > maxValue) { //找到比maxValue还大的值
maxValue = nums[i];
index = i; //记录下标
}
}
node = new TreeNode(maxValue); //求完最大值之后,定义了一个新节点
//构造节点的左子树和右子树:
//左 (构造左子树,需要分割数组,需要保证左区间的元素至少有1个)
if (index > 0) { //说明左区间至少有一个元素 (index是这个数组最大值的下标)
//分割数组
newVec(0, index); //[ ) 左闭右开 (不包含index)
node->left = contruct(newVec);
}
//右
if (index < (nums.size() - 1)) {
newVec(index + 1, nums.size()); //[ ) 左闭右开 (不包含nums.size()这个下标)
node->right = contruct(newVec);
}
return node;
}
TreeNode* contruct(nums) { if (nums.size() == 1) { //说明到叶子节点 return new TreeNode(nums[0]); } }
如果传进来的数组大小是1的话,说明我们要构造的根节点,它就是叶子节点。
那么,就return数组里唯一一个元素
同学疑惑:这里面为什么没有对数组为空的情况进行判断?
因为力扣上本题的题目要求就是传入递归函数的数组nums,其大小是
>=1 (因此我们不用对数组为空的情况进行考虑)
到叶子节点,及时返回,不用考虑数组为空的情况。
单层递归的逻辑:
int maxValue = 0; //最大值
为什么要求这个最大值? 因为构造中节点的时候要用最大值来构造。
为什么一开始要赋成0 ? 因为数组里面的所有元素都是正整数。 (题目要求)
为什么要有下标index? 因为接下来分割数组的时候要根据下标来分割。
求完最大值之后,定义了一个新节点
node = new TreeNode(maxValue);
在构造节点的左子树和右子树时,
构造左子树,需要分割数组;同时,需要保证左区间的元素至少有1个,为什么?
if (nums.size() == 1) { //说明到叶子节点 return new TreeNode(nums[0]); }
A:因为这里要求至少只有一个元素的时候,才进行返回。
分割数组:
在分割数组的时候,一定要注意对区间的定义,要么就坚持左闭右开,要么就坚持左闭右闭。
在nums里面定义一个新数组newVec,它取nums里面的下标范围是:0~index,取的是左闭右开区间。
有了新数组,就可以继续向左递归:node->left = contruct(newVec); ,传入新构造的数组newVec
也就是说左区间的数组再传进来,自己调用自己,传进递归函数之后,用这个新数组构造的二叉树就是这个node的左子树 (node->left)
右区间,需要保证数组的大小是大于等于1,
if (index < nums.size() - 1) 说明分割点的右区间,这个数组的大小一定是大于等于1
梳理一下:中构造中节点,然后去递归构造左子树(在构造过程中进行分割数组的操作,左闭右开);递归构造右子树(同理)。
这个节点构造完了,左子树、右子树构造完了,最后将这个节点返回,就是根据这棵二叉树所构造的最大二叉树的根节点的下标。
return node; 就是根据这个数组所构造的最大二叉树的根节点的指针。
代码比较冗余,效率低。
AC代码: (核心代码模式)
class Solution {
public:
TreeNode* constructMaximumBinaryTree(vector<int>& nums) {
TreeNode* node = new TreeNode(0);
if (nums.size() == 1) {
return new TreeNode(nums[0]); //return数组里唯一一个元素
}
//中
int maxValue = 0; //最大值
int index = 0; //最大值所在的下标
//遍历数组
for (int i = 0; i < nums.size(); i++) {
if (nums[i] > maxValue) {
maxValue = nums[i];
index = i; //记录下标
}
}
node = new TreeNode(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;
}
}
617.合并二叉树
题目链接:
难度指数:😀😐
本题是考查一起操作2棵二叉树的能力。我们前面操作的基本都是一棵二叉树,遇到这个可能有点懵。
使用递归法就会涉及到前中后序遍历。本题,前序是最容易理解的。(中左右符合将两棵树合并成一棵树的正常逻辑)
本题也可以使用迭代法:
递归解法:(前序)
代码思路:
1️⃣ 要合并两个数,就把2个数的根节点传进来
2️⃣ 在遍历t1和t2的时候,是同步进行遍历的,不是异步。
if (t1 == NULL) { return t2; } if (t2 == NULL) { return t1; }
这里面已经包含了t1和t2同时为空的情况,比如:t1为空,就返回t2,若t2也为空,其实返回的就是null
3️⃣ tree1所遍历节点和tree2所遍历节点不为空 t1->val += t2->val; //值相加
合并之后的这个二叉树的左子树,需要把旧的tree1的左子树和旧的tree2的右子树作为参数传进来。
TreeNode* mergeTree(TreeNode* t1, TreeNode* t2) {
//终止条件
if (t1 == NULL) {
return t2;
}
if (t2 == NULL) {
return t1;
}
//确定单层递归的逻辑
//tree1所遍历节点和tree2所遍历节点不为空
t1->val += t2->val; //值相加 中
t1->left = mergeTree(t1->left, t2->right); //左
t1->right = mergeTree(t1->right, t2->right); //右
return t1;
}
中序、后序也是可以的,只不过我们正常思路还是倾向于前序的。
上面代码其实是重复用了tree1的结构,也就是说把题目中传进来的数给改了,而没有重新定义一个新的二叉树。
重新定义一个新的二叉树,也不复杂:
TreeNode* mergeTree(TreeNode* t1, TreeNode* t2) {
//终止条件
if (t1 == NULL) {
return t2;
}
if (t2 == NULL) {
return t1;
}
TreeNode* root = new TreeNode(0);
root->val = t1->val + t2->val; //值相加 中
root->left = mergeTree(t1->left, t2->right); //左
root->right = mergeTree(t1->right, t2->right); //右
return root;
}
空间复杂度:O(n) tree1和tree2重新开辟了一个空间。
AC代码: (核心代码模式)
class Solution {
public:
TreeNode* mergeTrees(TreeNode* root1, TreeNode* root2) {
if (root1 == NULL) {
return root2;
}
if (root2 == NULL) {
return root1;
}
//确定单层递归的逻辑
//tree1所遍历的节点和tree2所遍历的节点不为空
root1->val += root2->val; //值相加 中
root1->left = mergeTrees(root1->left, root2->left); //左
root1->right = mergeTrees(root1->right, root2->right); //右
return root1;
}
};
最后的总结建议重新看下视频
迭代解法:
AC代码: (核心代码模式)
700.二叉搜索树中的搜索
题目链接:
难度指数:😀🙂
递归解法:
代码思路:
2️⃣ 如果二叉树中没有我们要搜的目标值的节点,就return null;
或者说root == val (即root这个数值等于我们想要搜索的数值),那么就要把这个节点返回。
TreeNode* search(TreeNode* root, int val) {
if (root == NULL || root->val == val) {
return root;
}
//确定单层递归的逻辑
TreeNode* result = NULL;
if (val < root->val) { //如果要搜索的这个值 < root->val
//我们应该去左子树遍历
result = search(root->left, val); //用result接收递归返回的变量
}
if (val > root->val) {
result = search(root->right, val);
}
return result; //没有搜到结果的话,result的默认值就是NULL
}
用递归的方式实现在二叉搜索树中,搜索我们想要查询的这个数值的节点。
而且,也没有涉及到前中后序遍历,因为二叉搜索树的特性,已经帮我们确定了遍历顺序,
AC代码: (核心代码模式)
class Solution {
public:
TreeNode* searchBST(TreeNode* root, int val) {
if (root == NULL || root->val == val) {
return root;
}
TreeNode* result = NULL;
if (val < root->val) {
result = searchBST(root->left, val);
}
if (val > root->val) {
result = searchBST(root->right, val);
}
return result; //没有搜索到结果的话,result的默认值就是NULL
}
};
迭代解法:
本题的迭代法出乎意外的简单
AC代码: (核心代码模式)
class Solution {
public:
TreeNode* searchBST(TreeNode* root, int val) {
//遍历root,如果root不为空,就一直往下遍历
while (root != NULL) {
if (val < root->val) {
root = root->left;
}
else if (val > root->val) {
root = root->right;
}
else { //相等
return root;
}
}
return NULL;
}
};
98.验证二叉搜索树
题目链接:
难度指数:😀😐😕
给我们一棵树,判断这棵树是不是二叉搜索树。
正是因为二叉搜索树的特性,在递归遍历的时候,如果是中序遍历,那么这些元素都是有序的。
验证二叉搜索树:就是验证这棵树在中序遍历的时候是不是单调递增,如果是,那么它就是一棵二叉搜索树。
本题的一个直观想法:中序遍历这棵二叉树,把所有的元素都保存到一个数组中,判断这个数组是不是单调递增,即可得知这棵树是不是二叉搜索树。
递归解法:(中序)
代码思路:
返回值是这棵二叉树是不是二叉搜索树,bool类型;传入的参数就是这个二叉树的根节点。
PS:一棵空的二叉树,它也是一棵二叉搜索树,也是完全二叉树,也是满二叉树……
bool isvalid(TreeNode* root) {
if (root == NULL) { //若这棵二叉树是空
return true;
}
isvalid(root->left); //左
vec.push_back(root->val); //中
isvalid(root->right); //右
}
AC代码: (核心代码模式)
/**
* 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 {
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();
traversal(root);
for (int i = 1; i < vec.size(); i++) {
//这里注意要 <=,搜索树中不能有相同的元素
if (vec[i] <= vec[i - 1]) {
return false;
}
}
return true;
}
};
这段代码的for循环还是值得一看的,要弄懂为什么这么写。
这并不是最优的方法 ↑↑,
我们没有必要把二叉树转变成一个数组,直接在遍历过程中就可以判断是否有序:
不将二叉树转变成数组:
递归法是左中右,关键是如何进行比较,
long long maxVal = LONG_MIN;
bool isvalid(TreeNode* root) {
if (root == NULL) {
return true;
}
bool left = isvalid(root->left); //左
//中
if (root->val > maxVal) {
maxVal = root->val;
}
else { //就不是二叉搜索树
return false;
}
//右
bool right = isvalid(root->right);
return left && right; //左子树和右子树都符合条件,就return true
}
maxVal记录的是当前节点的前一个节点的数值。
每次root->val始终比前一个结点大,始终比前一个节点大,以至于每次都可以去更新max_value。
同学疑惑:return left && right; 这里判断左子树、右子树符合条件,貌似中是否符合条件还没有判断呀
因为力扣的后台测试数据有int的最小值,
使用long long maxVal = LONG_MIN;
如果你输入的数据中有int的最小值,就把这个maxVal定义成一个更小的,
这样才能保证在遍历第一个节点的时候,root->val 一定 > maxVal
不过,这种方法其实也不太好。
AC代码: (核心代码模式)
class Solution {
public:
long long maxVal = LONG_MIN; //因为后台测试数据中有int最小值
bool isValidBST(TreeNode* root) {
if (root == NULL) {
return true;
}
bool left = isValidBST(root->left); //左
//中
if (root->val > maxVal) {
maxVal = root->val;
}
else { //就不是二叉搜索树
return false;
}
//右
bool right = isValidBST(root->right);
return left && right; //若左子树和右子树都符合,就return true
}
};
这种写法不是很好
可以选择不额外定义一个变量,而是在遍历二叉树的时候,前一个结点和后一个结点进行比较就可以了。
↓↓↓
双指针优化:
定义一个pre节点,初始化为NULL:TreeNode pre = NULL;*
pre记录当前遍历的前一个节点,root就可以和pre进行比较。
AC代码: (核心代码模式)
/**
* 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* pre = NULL; //pre记录当前遍历的前一个节点
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 = root; //记录前一个结点
这一行不是很懂,得琢磨一下
迭代解法:
是一个模板类的题目,即中序遍历的模板,稍稍改一下就可以了。
AC代码: (核心代码模式)