二叉树(二)目录
- 8.20日任务:完全二叉树的节点个数
- 学习补充:二叉树的复杂度如何计算?
- 8.21日任务:平衡二叉树
- 8.22日任务:二叉树的所有路径
- 8.22日对于代码随想录中看到的拓展方法的思考
- 8.23 左叶子之和
- 8.23日对于同一思路,自己写法与代码随想录实现方法不一致的情况
- 8.24日任务:找树左下角的值
- 8.24第二道:路径总和
- 8.25日任务:从中序与后序遍历序列构造二叉树
- 8.26日任务:最大二叉树
- 8.26日多做任务:合并二叉树
- 8.27日任务:二叉搜索树中的搜索
- 8.28日任务:验证二叉搜索树
- 8.29日任务:二叉搜索树的最小绝对差
- 8.30日任务:二叉搜索树中的众数
- 8.30思考:最近加一个任务就是:C++函数模板以及操作符重载视频刷一下,目的是什么,目的是让自己能够看懂STL库。主要先尝试看懂基础的vector,sort,借助chatgpt,和csdn学习和做笔记。
- 8.31日任务:二叉树的最近公共祖先
- 9.1日任务:二叉搜索树的最近公共祖先
- 9.1日总结反思:第一遍学习过程中看了思路和参考代码之后,依然要坚持自己复现代码,这时一个加深印象,二次加工理解的过程,必须坚持遵循。
- 9.2日任务:二叉搜索树中的插入操作
- 9.3日任务:删除二叉搜索树中的节点
- 9.4日任务:修剪二叉搜索数
- 9.5日任务:将有序数组转化为二叉搜索树
- 9.5日策略调整:只掌握一种解决问题的方法即可,掌握一个递归就行了,第二次再来看多余的方法,在真正笔试的时候,你只能选择一种方法立刻编写出来。
- 9.6日任务:把二叉搜索树转换为累加树
8.20日任务:完全二叉树的节点个数
完全二叉树的概念:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置,若最底层为第 h 层,则该层包含 1~ 2^(h-1) 个节点。
代码随想录给出的思路:
1.和求二叉树的深度思路一致,提到了递归(求高度的递归)和迭代层序遍历,我自己重点练习一下递归写法。
2.完全二叉树(除了最底层没有布满,其余每层节点都布满了,最下面一层的节点都集中在该层最左边的若干位置)和满二叉树(每一层节点都是布满的,完全二叉树的节点个数为2^h-1)的思路。
3.第二种方法更为精辟的解释:更为精辟的解释:依然使用递归,依然是左右子树节点个数之和+1,只不过用满二叉树作为了终止条件(这句话讲的很精辟)。
注意:2的n次方可以用1<<n来表示;这次又遇到个运算符优先级的问题,<<运算符的优先级小于±号,在这里卡了半天
不用看代码随想录的代码,跟着思路按照自己的逻辑写
第一种:普通二叉树递归求节点数量
class Solution {
public:
int countNodes(TreeNode* root) {
//1.普通二叉树递归方法,求左右子树的数量,然后把左右子树的数量加起来
//1.确定递归函数的输入参数和返回值,输入子树根节点,输出该子树数量,countNodes刚好满足条件
//2.确定终止条件
if (root == nullptr) {
return 0;
}
if (root->left == nullptr && root->right == nullptr) {
return 1;
}
//终止条件部分两条判断语句,第一条是为了应对root为空节点准备的,第二条是为了递归到最底层节点准备的,其实第二条也是可以不用的,第一条已经包含了第二条
//3.确定顺序逻辑
//先算左子树数量
int leftnum = countNodes(root->left);
int rightnum = countNodes(root->right);
return leftnum + rightnum + 1;
}
};
第二种:利用完全二叉树和满二叉树的性质来求节点数量,具体细节代码随想录中解释的挺详细的
重点在于如何定位满二叉树的位置(从完全二叉树的性质来看,只要一个节点左右子树的深度相同,那么该节点就是一个满二叉树的根节点)
更为精辟的解释:依然使用递归,依然是左右子树节点个数之和+1,只不过用满二叉树作为了终止条件(这句话讲的很精辟)
class Solution {
public:
int countNodes(TreeNode* root) {
//结合满二叉树思路,在完全二叉树的前提之下,一个节点倘若它的左右子树深度相同,则该节点为一颗满二叉树的根节点,可以直接根据公式求得节点数量
//上面对于求深度阐述的还不够清晰,我一会画一张图吧
//依然使用递归,依然是左右子树节点个数之和+1,只不过用满二叉树作为了终止条件(这句话讲的很精辟)
//1.确定递归输入参数和返回值,输入根节点,返回该树节点总数
//2.确定终止条件,这个时候终止条件就是满二叉树
if (root == nullptr) { //这个判断语句是针对root为空,或者说程序第一次执行时
return 0;
}
//递归的终点判断主要在下方,判断此时是不是满二叉树
TreeNode* leftNode = root->left;
TreeNode* rightNode = root->right;
int leftDepth{0}, rightDepth{0};
while (leftNode) {
leftNode = leftNode->left;
leftDepth++;
}
while (rightNode) {
rightNode = rightNode->right;
rightDepth++;
}
if (leftDepth == rightDepth) {
return (1 << (leftDepth + 1)) - 1;//满二叉树节点数计算
}
//3.确定顺序逻辑
int leftNum = countNodes(root->left);
int rightNum = countNodes(root->right);
return leftNum + rightNum + 1;
}
};
学习补充:二叉树的复杂度如何计算?
复杂度的东西先放着,等题目刷完之后,再来特别攻坚一下复杂度的问题
8.21日任务:平衡二叉树
核心思路(比较简单):
递归函数的返回值:有两种情况:第一种是如果从下往上发现已经不是平衡二叉树了,直接可以终止递归;第二种如果从下往上发现是平衡二叉树的话返回的应该此树的高度,所以返回值可以设定为一个int类型的变量,>=0则此子树为平衡二叉树,且值为二叉树的高度,值为-1或者INT_MIN则直接退出。
这道题我只掌握了递归,迭代法觉得没有必要,不去管了,当作节省时间了,不差这一两种方法的。
按照自己的思路写,跟之前左右中求高度的思想是一样的。终止标识我用的INT_MIN
class Solution {
public:
int getMaxDepth(TreeNode* root) {
//1.终止条件,到了最底部节点的时候,就终止递归
if (root == nullptr) {
return 0;
}
if (root->left == nullptr && root->right == nullptr) {
return 1;
}
int leftDepth = getMaxDepth(root->left);
if (leftDepth == INT_MIN) {
return INT_MIN;
}
int rightDepth = getMaxDepth(root->right);
if (rightDepth == INT_MIN) {
return INT_MIN;
}
if (abs(leftDepth - rightDepth) > 1) {
return INT_MIN;
}
return max(leftDepth,rightDepth) + 1;
}
bool isBalanced(TreeNode* root) {
//用求高度的方法,递归,由低到高,通过每个结点左右两侧的高度差来判定是不是平衡二叉树
//递归函数的输入参数和返回值,输入参数为根节点,返回值:有两种如果从下往上发现已经不是平衡二叉树了,直接可以终止递归,如果是的话返回的应该此树的高度,所以返回值可以设定为一个int类型的变量,>=0则此子树为平衡二叉树,且值为二叉树的高度,值为-1或者INT_MIN则直接退出。
if (root == nullptr) {
return true;
}
int temp = getMaxDepth(root);
if (temp == INT_MIN) {
return false;
}
return true;
}
};
8.22日任务:二叉树的所有路径
to_string的用法,之前用的不多,很生疏,通过这道题也记起来不少。
对于代码随想录最后给出的迭代法的看法:多一种写法自然有助于开阔思路,但是开阔的这个思路相对脱离正常逻辑下的思路,我目前是在第一遍学习,应该注重的是对于数据结构知识的掌握以及对于比较常用思路代码的掌握,而不应该投入太多时间到这种扩展思路的地方,有点画蛇添足,徒降效率的感觉,就是常规思路大量的题目都会用到,会更熟悉,更高效,对于自己第一遍学习来说也是比较节约时间。这种拓展思路,可以在第二遍刷题的时候看一看,后面遇到这些东西应该做个标记直接跳过,否则太浪费时间了。
首先尝试我的思路:
我的result和tempPath用的是类成员变量,类似于全局变量;代码随想录中用的是递归函数参数设置为引用类型,整体思路保持一致。
这道题我的独立思考及编写时间是1个小时。
class Solution {
public:
vector<string> result;
vector<int> tempPath;//存储当前正在探索的路径
void searchPath(TreeNode* root) {
//1.终止条件,到达叶子结点的时候
if (root->left == nullptr && root->right == nullptr) {
//到达叶子结点的时候,讲道理一条路径已经完成了,可以压入vector数组了
tempPath.push_back(root->val);
string stringPath;
for (int i = 0; i < tempPath.size() - 1; i++) { //变量i在循环结束后会被释放
stringPath += to_string(tempPath[i]);
stringPath += "->";
}
stringPath += to_string(tempPath[tempPath.size() - 1]);
result.push_back(stringPath);
return;
}
tempPath.push_back(root->val);
//2.按照顺序逻辑,顺序逻辑就是前序遍历,一直往下深入就好了
if (root->left) { //左节点存在,那么就把左结点押入到temppath中
searchPath(root->left);
tempPath.pop_back();
//第一步把做左结点的值加入到temppath中
//第二步是把加进去的东西又吐出来,因为要回溯
}
if (root->right) {
//1.把右结点的值加入到temppath中去
//2.把加进去的东西又吐出来,因为要回溯
searchPath(root->right);
tempPath.pop_back();
}
return;
}
vector<string> binaryTreePaths(TreeNode* root) {
//返回值是一个字符串,需要知道怎么添加字符串
//用回溯,写法的话是用递归
//递归函数的参数输入和返回值。输入参数1个根节点,不需要返回值,可以设一个vector<string>类型的类成员变量存储路径
//题目已经提示树中节点的数目>=1所有不需要考虑空姐点
searchPath(root);
return result;
}
};
补充代码是随想录思路(不是全局变量(类成员变量的方式)的方式),自己在自己的原代码上做修改。对引用传参理解可以更深一些。
class Solution {
public:
void searchPath(TreeNode* root, vector<int>& tempPath, vector<string>& result) {
//1.终止条件,到达叶子结点的时候
tempPath.push_back(root->val);
if (root->left == nullptr && root->right == nullptr) {
//到达叶子结点的时候,讲道理一条路径已经完成了,可以压入vector数组了
string stringPath;
for (int i = 0; i < tempPath.size() - 1; i++) { //变量i在循环结束后会被释放
stringPath += to_string(tempPath[i]);
stringPath += "->";
}
stringPath += to_string(tempPath[tempPath.size() - 1]);
result.push_back(stringPath);
return;
}
//2.按照顺序逻辑,顺序逻辑就是前序遍历,一直往下深入就好了
if (root->left) { //左节点存在,那么就把左结点押入到temppath中
searchPath(root->left, tempPath, result);
tempPath.pop_back();
//第一步把做左结点的值加入到temppath中
//第二步是把加进去的东西又吐出来,因为要回溯
}
if (root->right) {
//1.把右结点的值加入到temppath中去
//2.把加进去的东西又吐出来,因为要回溯
searchPath(root->right,tempPath, result);
tempPath.pop_back();
}
return;
}
vector<string> binaryTreePaths(TreeNode* root) {
//返回值是一个字符串,需要知道怎么添加字符串
//用回溯,写法的话是用递归
//递归函数的参数输入和返回值。输入参数1个根节点,不需要返回值,可以设一个vector<string>类型的类成员变量存储路径
//题目已经提示树中节点的数目>=1所有不需要考虑空姐点
vector<string> result;
vector<int> tempPath;
searchPath(root, tempPath, result);
return result;
}
};
继续独立尝试实现迭代法:(没能独立实现,看了代码随想录的思路之后再单独编写)
比较特殊的地方在于需要重新设置一个栈存储路径,这个可以根据代码在纸上画一画模拟一下,但是不好去想
class Solution {
public:
vector<string> binaryTreePaths(TreeNode* root) {
//尝试复习一下前序遍历迭代法
//先把前序遍历统一迭代法的代码写出来
//前序遍历统一迭代法是栈的思路
vector<string> result;
vector<int> temp;
if (root == nullptr) {
return result;
}
stack<TreeNode*> tempstack;
stack<string> tempstring;
tempstack.push(root);
tempstring.push(to_string(root->val));
while (!tempstack.empty()) {
TreeNode* node = tempstack.top(); tempstack.pop();
string path = tempstring.top(); tempstring.pop();
if (node->left == nullptr && node->right == nullptr) {
result.push_back(path);
}
if (node->right) {
tempstack.push(node->right);
tempstring.push(path+"->"+to_string(node->right->val));
}
if (node->left) {
tempstack.push(node->left);
tempstring.push(path+"->"+to_string(node->left->val));
}
}
return result;
}
};
8.22日对于代码随想录中看到的拓展方法的思考
对于代码随想录最后给出的较多拓展方法的看法:多一种写法自然有助于开阔思路,但是开阔的这个思路相对脱离正常逻辑下的思路,我目前是在第一遍学习,应该注重的是对于数据结构知识的掌握以及对于比较常用思路代码的掌握,而不应该投入太多时间到这种扩展思路的地方,有点画蛇添足,徒降效率的感觉,就是常规思路大量的题目都会用到,会更熟悉,更高效,对于自己第一遍学习来说也是比较节约时间。这种拓展思路,可以在第二遍刷题的时候看一看,后面遇到这些东西应该做个标记直接跳过,否则太浪费时间了。
8.23 左叶子之和
首先按照自己的思考逻辑去写,这道题我用的是之前用的从上向下求深度的递归方法。
我的方法和代码随想录的方法是不一样的,算是个新思路。
class Solution {
public:
void getSumOfLeftLeaves (TreeNode* root, int& sum, int flag) {
//1.截止条件
if(root->left == nullptr && root->right == nullptr) {
if (flag == 1) {//满足左叶子节点条件
sum += root->val;
}
return;
}
//2.顺序逻辑
if (root->left) {
flag = 1;
getSumOfLeftLeaves(root->left,sum,flag);
}
if (root->right) {
flag = 0;
getSumOfLeftLeaves(root->right,sum,flag);
}
return;
}
int sumOfLeftLeaves(TreeNode* root) {
//叶子节点的定义:没有子节点的节点称为叶子节点,上一级是下一级的父节点,下一级是上一级的子节点
//左叶子是指1.满足叶子节点条件2.又是上一级节点的左子节点
//按照我的分析,后序遍历不太好处理,首先尝试用求深度的回溯法。
//依然是递归
//确定递归输入参数和返回值,输入一个root节点,输入一个sum引用,求和,输入一个左节点标志flag,不用返回值
int sum{0};
//不考虑节点为空
getSumOfLeftLeaves(root,sum,0);
return sum;
}
};
代码随想录中还提到了后序遍历的递归方法和迭代法,我选择按照后序遍历在练习一下递归。
根据代码随想录的思路自己编写的。
class Solution {
public:
void getSumOfLeftLeaves (TreeNode* root, int& sum) {
//1.截止条件
if(root->left != nullptr && root->left->left == nullptr && root->left->right == nullptr) {
sum += root->left->val;
}
if (root->left == nullptr && root->right == nullptr) {
return;
}
//2.顺序逻辑
if (root->left) {
getSumOfLeftLeaves(root->left,sum);
}
if (root->right) {
getSumOfLeftLeaves(root->right,sum);
}
return;
}
int sumOfLeftLeaves(TreeNode* root) {
//叶子节点的定义:没有子节点的节点称为叶子节点,上一级是下一级的父节点,下一级是上一级的子节点
//左叶子是指1.满足叶子节点条件2.又是上一级节点的左子节点
//代码随想录给出的方法是后序遍历递归,我前面想了这个方法,但是没转过弯来,这里再试一下
//后序遍历的思路应该是:对于左叶子的定义:一个节点的左子节点不为空,且左子节点的两个子结点均为空,这就构成了一个终止条件
//递归的输入参数和返回值,输入根节点,和一个引用值,返回值
int sum{0};
getSumOfLeftLeaves(root, sum);
return sum;
}
};
8.23日对于同一思路,自己写法与代码随想录实现方法不一致的情况
思路相同即可,重要的是思路,代码实现可以有差异,保证思路一致。这样可以保留自己思考一定程度上的独立性,思考的主动给权放在自己手上。
8.24日任务:找树左下角的值
先按照自己的思路,首先想到的是层序遍历,直接敲代码,不到10分钟就拿下了。
class Solution {
public:
int findBottomLeftValue(TreeNode* root) {
//这道题的第一反应是层序遍历,单独用一个数组或者容器记录每一行第一个节点,如果最后确认是最后一行,那最后就是这个节点,如果不是最后一行那就不是这个节点,没问题。
//直接先来层序遍历的代码吧
queue<TreeNode*> usequeue;
//不考虑空节点
usequeue.push(root);
TreeNode* tempnode;
while (!usequeue.empty()) {
int size = usequeue.size();
tempnode = usequeue.front();
for (int i = 0; i < size; i++) {
TreeNode* node = usequeue.front();
usequeue.pop();
if (node->left) {
usequeue.push(node->left);
}
if (node->right) {
usequeue.push(node->right);
}
}
}
return tempnode->val;
}
};
代码随想录用的是递归的方法,我没有看具体思路,先顺着递归的思路去想,其他方法可以不用,但递归还是要多练练。
用的是从顶部向下求深度的递归回溯逻辑。比较简单,没有太复杂,不会了直接看代码就行。
class Solution {
public:
int maxdetph{0};
void mySolution(TreeNode* root, int depth, int& val) {//flag表示这是个左节点
//1.终止条件
if (root->left == nullptr && root->right == nullptr) {
if (depth > maxdetph) {
maxdetph = depth;
val = root->val;
}
return;
}
//2.顺序逻辑
if (root->left) {
depth++;
mySolution(root->left,depth,val);
depth--;
}
if (root->right) {
depth++;
mySolution(root->right,depth,val);
depth--;
}
return;
}
int findBottomLeftValue(TreeNode* root) {
//尝试递归的思路:设置一个外部最深深度的变量maxdepth,然后用一个depth做回溯,用一个变量存储节点,只有depth>maxdetph时才会更新
//1.递归的输入参数和返回值,输入一个引用节点,根节点,深度计数(普通传参,不用引用),不需要返回值
int val{0};
mySolution(root, 1, val);
return val;
}
};
8.24第二道:路径总和
按自己的思路来,依然是自顶向下递归回溯,没有太大难度。10分钟就解决了。
class Solution {
public:
void mySolution(TreeNode* root, const int targetSum,int sum, bool& flag) {
if (flag == true) {
return;
}
//1.终止条件
sum += root->val;
if(root->left == nullptr && root->right == nullptr) {
if (sum == targetSum) {
flag = true;
}
return;
}
//顺序逻辑
if (root->left) {
mySolution(root->left,targetSum,sum,flag);
}
if (root->right) {
mySolution(root->right,targetSum,sum,flag);
}
}
bool hasPathSum(TreeNode* root, int targetSum) {
//依然是用自顶向下的回溯逻辑
//用递归,递归输入参数为根节点,targetsum(常量),以及sum(不用引用)和一个flag引用传参或者公共成员变量
bool flag = false;
if (root == nullptr) {
return false;
}
mySolution(root,targetSum,0,flag);
return flag;
}
};
版本二:在顺序逻辑的判断那里多加了一个判断,和代码随想录中思路接近,保留我的思路独立性
class Solution {
public:
void mySolution(TreeNode* root, const int targetSum,int sum, bool& flag) {
//1.终止条件
sum += root->val;
if(root->left == nullptr && root->right == nullptr) {
if (sum == targetSum) {
flag = true;
}
return;
}
//顺序逻辑
if (root->left && flag == false) {
mySolution(root->left,targetSum,sum,flag);
}
if (root->right && flag == false) {
mySolution(root->right,targetSum,sum,flag);
}
}
bool hasPathSum(TreeNode* root, int targetSum) {
//依然是用自顶向下的回溯逻辑
//用递归,递归输入参数为根节点,targetsum(常量),以及sum(不用引用)和一个flag引用传参或者公共成员变量
bool flag = false;
if (root == nullptr) {
return false;
}
mySolution(root,targetSum,0,flag);
return flag;
}
};
这里还有一道路径总和(二):依然保留自己思考的独立性,搞完去打球
没有太大难度。
class Solution {
public:
vector<vector<int>> result;
void mySolution(TreeNode* root, const int targetSum, int sum, vector<int> tempresult) {
//终止条件
sum += root->val;
tempresult.push_back(root->val);
if (root->left == nullptr && root->right == nullptr) {
if (sum == targetSum) {
result.push_back(tempresult);
}
return;
}
//顺序逻辑
if (root->left) {
mySolution(root->left, targetSum, sum, tempresult);
}
if(root->right) {
mySolution(root->right, targetSum, sum, tempresult);
}
}
vector<vector<int>> pathSum(TreeNode* root, int targetSum) {
//依然是顺序自顶向下递归回溯
//递归函数输入参数和返回值,输入根节点,输入目标值,输入自己的计数值,输入vector<int>引用类型数组,还有一个大数组可以放在公共成员变量,也可以放在引用传参里,不需要返回值
if (root == nullptr) {
return result;
}
int sum{0};
vector<int> tempresult;
mySolution(root, targetSum, sum, tempresult);
return result;
}
};
8.25日任务:从中序与后序遍历序列构造二叉树
开始进入构造二叉树的阶段了,为了方便调试,今天的代码用Visual Studio敲,可以打印日志。今天任务量比较大,预计花费一整个上午2个小时左右。
一个人搞了上午两个小时,没搞定,倒是发现了很多问题:
1.我在写代码时造成了内存泄露问题,当你的函数返回值是一个指针时,这个时候应该用一个指针变量来接收,而不是使用一个指向堆区内存的指针来接收,今天犯了这个错误。
2.后边的代码用visual studio去敲,尝试打日志。学会打日志很重要。或者debug也可以。
3.vector leftinorder(inorder.begin(), inorder.begin() + cutindex);这种方法可以定义数组,左闭右开。
4.关于栈的问题,C++在调用函数时,在栈区创建栈帧空间,函数执行完之后,首先会将返回值保存到寄存器中,之后再释放栈空间,最后从寄存器中获取返回值,总之栈区释放之前,返回值会被保留下来。
按照代码随想录的思路,自己调bug,这是正确的思路,但是代码不够简洁。
class Solution {
public:
TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {//letcode给出的函数模板,inorder是一棵树的中序遍历,postorder是一棵树的后序遍历 //按照代码随想录的思路自己来
//从代码随想录中获取的整体思路:通过后序遍历的末尾val做切割,配合中序遍历就可以切分树的两个子树
//如何构建一棵二叉树呢?代码随想录用的是递归的思路。
//考虑递归的输入参数和返回值,输入参数为局部树的中序和后序遍历数组,返回值则返回该局部树的根节点
//leetcode给出的函数输入输出参数刚好满足条件
//1.终止条件,中序和后序遍历数组都仅有一个值,即这就是一个叶子节点
if (inorder.size() == 1 || inorder.size() == 0) {
if (inorder.size() == 1) {
TreeNode* newNode = new TreeNode{};
newNode->val = inorder[0];
return newNode;
}else {
return nullptr;
}
}
//顺序逻辑
//首先创建局部树的根节点
TreeNode* rootNode = new TreeNode{};
int tempval = postorder.back();
rootNode->val = tempval;
//将中序遍历和后序遍历数组切分成局部树左右子树的数组
vector<int> leftinorder;
vector<int> leftpostorder;
vector<int> rightinorder;
vector<int> rightpostorder;
int cutindex{ 0 };
//获取中序数组
for(int i = 0; i < inorder.size(); i++) {
if (inorder[i] == tempval) {
cutindex = i;
break;
}
leftinorder.push_back(inorder[i]);
}
for (int i = cutindex + 1; i < inorder.size(); i++) {
rightinorder.push_back(inorder[i]);
}
//获取后序数组
//下面两段代码隐含的重要信息就是:一棵树的中序和后序遍历的总节点数必然是相同的,而且在中序遍历数组和后序遍历数组,左右两棵子树是从中间分开的
for (int i = 0; i < leftinorder.size(); i++) {
leftpostorder.push_back(postorder[i]);
}
for (int i = leftinorder.size(); i < postorder.size() - 1; i++) {
rightpostorder.push_back(postorder[i]);
}
//构建树的左右节点
rootNode->left = buildTree(leftinorder, leftpostorder);
rootNode->right = buildTree(rightinorder, rightpostorder);
return rootNode;
}
};
待补充:
1.更精简的代码写法:切分数组使用stl库中的高级切分方式,同时不需要每次重复定义切分数组,使用数组索引的方式集合stl库中的函数进行切分。
2.从前序遍历和中序遍历序列构造二叉树
3.前序和后序遍历序列能不能构造一棵二叉树?不能,只能确定根节点,不能确定左右子树的前序后序遍历序列
子树中序后序数组快速构造方法:这种方法从思路上来说更直接,用索引做标记也可以,没有必须在细节上投入太多。毕竟这个空间复杂度并没有造成多大影响。
class Solution {
public:
TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {//letcode给出的函数模板,inorder是一棵树的中序遍历,postorder是一棵树的后序遍历 //按照代码随想录的思路自己来
//从代码随想录中获取的整体思路:通过后序遍历的末尾val做切割,配合中序遍历就可以切分树的两个子树
//如何构建一棵二叉树呢?代码随想录用的是递归的思路。
//考虑递归的输入参数和返回值,输入参数为局部树的中序和后序遍历数组,返回值则返回该局部树的根节点
//leetcode给出的函数输入输出参数刚好满足条件
//1.终止条件,中序和后序遍历数组都仅有一个值,即这就是一个叶子节点
if (inorder.size() == 1 || inorder.size() == 0) {
if (inorder.size() == 1) {
TreeNode* newNode = new TreeNode{};
newNode->val = inorder[0];
return newNode;
}else {
return nullptr;
}
}
//顺序逻辑
//首先创建局部树的根节点
TreeNode* rootNode = new TreeNode{};
int tempval = postorder.back();
rootNode->val = tempval;
//将中序遍历和后序遍历数组切分成局部树左右子树的数组
int cutindex{ 0 };
//获取中序数组
for(int i = 0; i < inorder.size(); i++) {
if (inorder[i] == tempval) {
cutindex = i;
break;
}
}
//以下定义方式均为左闭右开区间
vector<int> leftinorder(inorder.begin(), inorder.begin() + cutindex);
vector<int> rightinorder(inorder.begin() + cutindex + 1, inorder.end());
vector<int> leftpostorder(postorder.begin(), postorder.begin() + leftinorder.size() + 1);
vector<int> rightpostorder(postorder.begin() + leftinorder.size(),postorder.end() - 1);
//构建树的左右节点
rootNode->left = buildTree(leftinorder, leftpostorder);
rootNode->right = buildTree(rightinorder, rightpostorder);
return rootNode;
}
};
从前序遍历和中序遍历序列构造二叉树,和前面的从中序和后序遍历方式一致,这里内部用到了下标索引方式,坚持左闭右开。
class Solution {
public:
TreeNode* mySolution(vector<int>& preorder, vector<int>& inorder,int preorderstart, int preorderend, int inorderstart, int inorderend) {
//1.终止条件,数组中只有一个节点或者数组中一个节点都没有
if (preorderstart == preorderend) {
return nullptr;
}
if (preorderend - preorderstart == 1) {
TreeNode* newNode = new TreeNode{};
newNode->val = preorder[preorderstart];//只剩下一个元素了
return newNode;
}
//顺序逻辑:构造一棵树
//先构造根节点
TreeNode* rootNode = new TreeNode{};
rootNode->val = preorder[preorderstart];
//构造左右子树的前序和中序遍历数组
//先构建中序
int leftinorderend,rightinorderstart;
int leftpreorderend,rightpreorderstart;
for (int i = inorderstart; i < inorderend; i++) {
if (inorder[i] == preorder[preorderstart]) {
leftinorderend = i;//保证左闭右开
}
}
rightinorderstart = leftinorderend + 1;
//构建前序
leftpreorderend = preorderstart + 1 + leftinorderend - inorderstart;
rightpreorderstart = leftpreorderend;
//构建左右子树
rootNode->left = mySolution(preorder, inorder, preorderstart + 1, leftpreorderend, inorderstart,leftinorderend);
rootNode->right = mySolution(preorder, inorder, rightpreorderstart, preorderend, rightinorderstart, inorderend);
return rootNode;
}
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
//从前序和中序遍历序列构造二叉树,核心思路前序遍历的第一个节点则为树的根节点
//依然用递归,这道题用索引的方式吧,不用创建新数组的方式
TreeNode* rootNode = mySolution(preorder, inorder, 0, preorder.size(), 0, inorder.size());
return rootNode;
}
};
8.26日任务:最大二叉树
关注INT_MIN和INT_MAX的使用。
按照自己的思路,相对比较简单,代码随想录中推荐在递归过程中使用索引,而不是创建数组。之前我觉得创建数组花费不了多大的开销,但是却忽略了这是一个递归过程,如果要构建一个非常大的二叉树时,定义数组就会造成较大的时间和空间开销。所以使用索引是一个比较合理可行高效的方案。使用索引的代码这里就不在缀写,比较简单,我只想节约时间。
class Solution {
public:
TreeNode* constructMaximumBinaryTree(vector<int>& nums) {
//这道题跟昨天的从中序与后序遍历序列构造二叉树非常类似
//直接用递归法
//递归函数的参数和返回值,递归函数的目标就是构造出一棵树来,返回值是树的结点,输入值是给定的不重复的数组,题目中所给的函数是符合这个要求的。
//1.终止条件:数组中仅有一个值或者没有值
if (nums.size() == 1) {
TreeNode* newNode = new TreeNode{};
newNode->val = nums[0];
return newNode;
}
if (nums.size() == 0) {
return nullptr;
}
//顺序逻辑
//1.先找出数组中的最大值(或许可以用stl库函数)
//2.切分左数组;3.切分右数组
int maxValue{INT_MIN};//设置为极限最小值
int maxIndex{0};
for (int i = 0; i < nums.size(); i++) {
if (nums[i]>maxValue) {
maxValue = nums[i];
maxIndex = i;
}
}//拿到了最大值和最大值对应的索引
TreeNode* rootNode = new TreeNode{};
rootNode->val = maxValue;
vector<int> leftnums(nums.begin(), nums.begin() + maxIndex); //左闭右开
vector<int> rightnums(nums.begin()+ maxIndex + 1, nums.end());//左闭右开
rootNode->left = constructMaximumBinaryTree(leftnums);
rootNode->right = constructMaximumBinaryTree(rightnums);
return rootNode;
}
};
8.26日多做任务:合并二叉树
递归法前中后序遍历都可以。
首先是按照代码随想录的思路自行编写代码,写的比较繁琐。
重要注意点:我的这段代码是完全建立了一个新的树,这个树的链表指针与原先的root1,root2没有任何关系。
class Solution {
public:
TreeNode* mergeTrees(TreeNode* root1, TreeNode* root2) {
//代码随想录给出了递归法(改变原树结构以及不改变原树结构两种),迭代法(用队列),野路子(指针的指针),总共四套方法
//首先是递归(前中后序均可)
//前序递归 递归函数返回值和输入参数,输入参数为左树根结点和右树根结点,返回值返回一个新节点(不改变原数结构)
//mergeTrees的输入参数和返回值刚好合适
//1.终止条件
if (root1 == nullptr && root2 == nullptr) {
return nullptr;
}
//2.顺序执行
TreeNode* rootNode = new TreeNode{0};
if (root1 == nullptr && root2 != nullptr) {
rootNode->val = root2->val;
rootNode->left = mergeTrees(nullptr, root2->left);
rootNode->right = mergeTrees(nullptr, root2->right);
}
if (root1 != nullptr && root2 == nullptr) {
rootNode->val = root1->val;
rootNode->left = mergeTrees(root1->left, nullptr);
rootNode->right = mergeTrees(root1->right, nullptr);
}
if (root1 != nullptr && root2 != nullptr) {
rootNode->val = root1->val + root2->val;
rootNode->left = mergeTrees(root1->left, root2->left);
rootNode->right = mergeTrees(root1->right, root2->right);
}
return rootNode;
}
};
下面这段代码是不改变原子树结构的一个链表,需要注意的细节点有两个:
1.两棵子树重叠的部分,代码中创建了申请了新的堆区内存,但是不重叠的部分,并没有申请新的堆区内存,而是直接指向root1,root2的地址。
2.这段代码的终止条件考虑的比较巧妙,完全不用再考虑一棵子树相比另一颗子树在不重叠部分多出任意节点。最后我会画一张图示意这个逻辑。
class Solution {
public:
TreeNode* mergeTrees(TreeNode* root1, TreeNode* root2) {
if (root1 == nullptr) {
return root2;
}
if (root2 == nullptr) {
return root1;
}
//2.顺序执行
TreeNode* rootNode = new TreeNode{0};
rootNode->val = root1->val + root2->val;
rootNode->left = mergeTrees(root1->left, root2->left);
rootNode->right = mergeTrees(root1->right, root2->right);
return rootNode;
}
};
下面这段代码则是在原先的树上直接进行修改
class Solution {
public:
TreeNode* mergeTrees(TreeNode* root1, TreeNode* root2) {
if (root1 == nullptr) {
return root2;
}
if (root2 == nullptr) {
return root1;
}
//2.顺序执行
root1->val = root1->val + root2->val;
root1->left = mergeTrees(root1->left, root2->left);
root1->right = mergeTrees(root1->right, root2->right);
return root1;
}
};
还有迭代法:这个思路不容易思考想到,既然见了还敲了一遍,以后就知道有这种比较巧的方法了。
这个代码中root1与root2的部分节点是交叉连接的。
class Solution {
public:
TreeNode* mergeTrees(TreeNode* root1, TreeNode* root2) {
//根据代码随想录中的迭代法自行编写
//首先考虑特殊情况
if (root1 == nullptr) return root2;
if (root2 == nullptr) return root1;
queue<TreeNode*> tempqueue;
//上面已经把空节点的情况解决了
tempqueue.push(root1);
tempqueue.push(root2);
while (!tempqueue.empty()) {
TreeNode* node1 = tempqueue.front(); tempqueue.pop();
TreeNode* node2 = tempqueue.front(); tempqueue.pop();
node1->val = node1->val + node2->val;
if (node1->left != nullptr && node2->left != nullptr) {
tempqueue.push(node1->left);
tempqueue.push(node2->left);
}
if (node1->right != nullptr && node2->right != nullptr) {
tempqueue.push(node1->right);
tempqueue.push(node2->right);
}
if (node1->left != nullptr && node2->left == nullptr){}//这个不用处理的
if (node1->right != nullptr && node2->right == nullptr) {}//这个也不用处理
if (node1->left == nullptr && node2->left != nullptr) {
node1->left = node2->left;
}
if (node1->right == nullptr && node2->right != nullptr) {
node1->right = node2->right;
}
//如果两个结点都为叶子节点,即两个节点都没有左右子节点,就不用管了
}
return root1;
}
};
最后的指针操作野路子,不用管,没什么难度的。
8.27日任务:二叉搜索树中的搜索
给自己提个要求,每次代码写完之后,主动把中间可以省略的细节省略掉,保存原始版和精简版代码。
二叉搜索树的迭代法非常非常简洁,掌握就好。
按照自己的逻辑写的递归,和代码随想录代码思路逻辑基本一致,就是写的不精简
class Solution {
public:
TreeNode* searchBST(TreeNode* root, int val) {
//自己先思考一轮,二叉搜索树的递归和迭代遍历
//二叉搜索树的定义:1.若二叉搜索树的左节点存在,它的值必然小于根节点的值;2.若二叉搜索树的右节点不为空,则右节点的值必大于根节点的值3.左右子树均为二叉搜索树;
//首先尝试递归,考虑返回值和输入参数,输入值肯定是根节点和数值,返回值返回节点值等于val的节点,一旦找到立马返回,没找到则返回空值
//1.终止条件
//节点值重复的问题,二叉搜索树的节点值不可能重复
if (root == nullptr) {
return nullptr;
}
if (root->val == val) {
return root;
}
else if (root->left == nullptr && root->right ==nullptr) {
return nullptr;
}
//按照顺序逻辑
bool flag = root->val > val; //flag为true,则找左节点,为false,则找右节点
TreeNode * node = nullptr;
if (flag == true && root->left) {
node = searchBST(root->left,val);
}
if (flag == false && root->right) {
node = searchBST(root->right,val);
}
return node;
}
};
迭代法:自己的思路,层序遍历思路,用队列,写的比较繁琐
class Solution {
public:
TreeNode* searchBST(TreeNode* root, int val) {
//自己先思考一轮,二叉搜索树的递归和迭代遍历
//二叉搜索树的定义:1.若二叉搜索树的左节点存在,它的值必然小于根节点的值;2.若二叉搜索树的右节点不为空,则右节点的值必大于根节点的值3.左右子树均为二叉搜索树;
//接下来尝试迭代法
//我的想法:直接层序遍历那套逻辑就好了
queue<TreeNode*> tempqueue;
if (root == nullptr || root->val == val) return root;
tempqueue.push(root);
while (!tempqueue.empty()) {
TreeNode* node = tempqueue.front();tempqueue.pop();
if (node->val == val) return node;
if (node->val > val) {
if (node->left) {
tempqueue.push(node->left);
}
else {
return nullptr;
}
}
if (node->val < val) {
if (node->right) {
tempqueue.push(node->right);
}
else {
return nullptr;
}
}
}
return nullptr;
}
};
代码随想录得迭代法太香了。
class Solution {
public:
TreeNode* searchBST(TreeNode* root, int val) {
//代码随想录的迭代法思路更简洁了
while (root != nullptr) {
if (root->val > val) root = root->left;
else if (root->val < val) root = root->right;
else return root;
}
return nullptr;
}
};
8.28日任务:验证二叉搜索树
二叉搜索树概念再加深理解:
若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
它的左、右子树也分别为二叉搜索树。
重点在“所有节点”这里,我之前理解的是:左子树不为空,相邻的左结点的值小于根节点的值,理解错了,在这里做一下纠正。
又犯了重要错误:还是对于上面的所有结点没有真正理解清楚,一个根结点的值要比左子树最大的值大,比右子树最小的值小。 这个其实也就是代码随想录中提到的陷阱。
关于在递归的过程中检查升序,我目前还没有完全掌握这道题目的思想,刷二轮的时候得特别注意一下这里。
对于二叉树输入的root即为当前节点理解的还不够,还需要题目来反复加深理解。
代码随想录给出的核心思路:二叉搜索树的中序遍历是有序的。
具体实现方式思路:
1.递归,既可以保存到数组中最后来检测是否有序,也可在遍历过程中判断是否有序(肯定是后者方便)
2.迭代法,用栈模拟中序遍历(前中后序遍历统一写法复习一下)
代码不参考代码随想录,直接按自己的想法编写,csdn记录两套代码,自己写的繁琐版+精简版
class Solution {
public:
void midOrderTraversal(TreeNode* root, vector<int>& result) {
//1.终止条件
if (root->left == nullptr && root->right == nullptr) {
result.push_back(root->val);
return;
}
//顺序逻辑
if (root->left) {
midOrderTraversal(root->left,result);
}
result.push_back(root->val);
if (root->right) {
midOrderTraversal(root->right,result);
}
return;
}
bool isValidBST(TreeNode* root) {
//递归:中序遍历:左中右,在遍历过程中进行比较
//开始的代码犯了重要错误:一个根结点的值要比左子树最大的值大,比右子树最小的值小
//先乖乖按照中序遍历放到数组里(中序遍历递归的逻辑又有点忘了,自己先想一会)
if (root == nullptr) return false;
vector<int> result;
//递归的输入参数和返回值,输入根结点和result,不用返回值
midOrderTraversal(root,result);
for (int i = 1; i < result.size(); i++) {
if (result[i] <= result[i - 1]) {
return false;
}
}
return true;
}
};
总结:这段代码写的太繁琐了,这段代码的终止条件是叶子结点,如果终止条件改为空节点的话,代码会简洁很多。
简洁版:
class Solution {
public:
void midOrderTraversal(TreeNode* root, vector<int>& result) {
//1.终止条件
if (root == nullptr) return;
//顺序逻辑
midOrderTraversal(root->left,result);
result.push_back(root->val);
midOrderTraversal(root->right,result);
return;
}
bool isValidBST(TreeNode* root) {
//递归:中序遍历:左中右,在遍历过程中进行比较
//开始的代码犯了重要错误:一个根结点的值要比左子树最大的值大,比右子树最小的值小
//先乖乖按照中序遍历放到数组里(中序遍历递归的逻辑又有点忘了,自己先想一会)
vector<int> result;
//递归的输入参数和返回值,输入根结点和result,不用返回值
midOrderTraversal(root,result);
for (int i = 1; i < result.size(); i++) {
if (result[i] <= result[i - 1]) return false;
}
return true;
}
};
接下来代码随想录给出的在遍历过程中检查顺序的代码比较难理解,先去洗把脸,清醒一下,再试着跟一下代码随想录的代码
在遍历过程中检查升序代码:
说实话,这段代码我没怎么看懂,还需要多磨一磨。整体思路就是:对于二叉搜索树来说,中序遍历所历经的节点值就是按照升序排列的,至于最后的return
class Solution {
public:
long long maxValue = LONG_MIN;
bool isValidBST(TreeNode* root) {
//递归:中序遍历:左中右,在遍历过程中完成比较的事
//这道题如果想在遍历过程中完成顺序检查,这个思路是非常独特的.因为二叉搜索树的中序遍历顺序(沿路经过的结点的值)本身就是升序,
//那么我们只要在这个遍历的过程中不断记录最大值,并使当前最大值小于下一个结点的值,那么这颗树就满足二叉树的特征
//倘若按照上述思路
//递归的输入参数和返回值,输入参数就是当前结点,返回值则是true or false
//1.终止条件
if (root == nullptr) return true;
bool left = isValidBST(root->left);//左
//中
if (maxValue < root->val) maxValue = root->val;
else return false;
bool right = isValidBST(root->right);//右
return left && right;
}
};
下面这段代码使用指针来记录上一节点,不是用maxValue来记录上一节点的值。
class Solution {
public:
TreeNode* lastnode = nullptr;//记录上一个节点,其实是用来比较升序的
bool isValidBST(TreeNode* root) {
//递归:中序遍历:左中右,在遍历过程中完成比较的事
//这道题如果想在遍历过程中完成顺序检查,这个思路是非常独特的.因为二叉搜索树的中序遍历顺序(沿路经过的结点的值)本身就是升序,
//那么我们只要在这个遍历的过程中不断记录最大值,并使当前最大值小于下一个结点的值,那么这颗树就满足二叉树的特征
//倘若按照上述思路
//递归的输入参数和返回值,输入参数就是当前结点,返回值则是true or false
//1.终止条件
if (root == nullptr) return true;
bool left = isValidBST(root->left);
if (lastnode != nullptr && lastnode->val >= root->val) return false;
lastnode = root;
bool right = isValidBST(root->right);
return left && right;
}
};
迭代法:和递归法同一个逻辑思路,代码随想录中写的更简单,我只要掌握基础的统一迭代法就ok.
class Solution {
public:
bool isValidBST(TreeNode* root) {
//尝试迭代法:左中右
if (root == nullptr) return true;
stack<TreeNode*> tempstack;
TreeNode* lastnode = nullptr;
tempstack.push(root);
while (!tempstack.empty()) {
TreeNode* node = tempstack.top();
tempstack.pop();
if (node != nullptr) {
if (node->right) tempstack.push(node->right);
tempstack.push(node);
tempstack.push(nullptr);
if (node->left) tempstack.push(node->left);
}else {
node = tempstack.top();
tempstack.pop();
if (lastnode != nullptr && lastnode->val >= node->val) return false;
lastnode = node;
}
}
return true;
}
};
8.29日任务:二叉搜索树的最小绝对差
用到C++库中abs,min 函数,这道题比较简单,和昨天的一样,没有投入多少时间就解决掉了。
存数组版
class Solution {
public:
vector<int> result;
void traversal(TreeNode* root) {
//终止条件,到达空值
if (root == nullptr) return;
//顺序执行,中序遍历左中右
traversal(root->left);//左
result.push_back(root->val);//中
traversal(root->right);//右
}
int getMinimumDifference(TreeNode* root) {
//和昨天的验证二叉搜索树一个样
//一种存数组的递归,一种在递归过程遍历的递归(我选择用前一个结点做记录的递归),一种迭代法,继续用同一迭代法(全部思想都是左中右中序遍历)
//首先是存数组的递归,递归输入参数为根节点,数组要么是引用传参,要么做类的成员变量,返回值不要,在过程中直接压入数组
traversal(root);
int val = INT_MAX;
for (int i = 1; i < result.size(); i++) {
val = min(val,abs(result[i] - result[i - 1]));
}
return val;
}
};
递归,记录上一节点版
class Solution {
public:
TreeNode* pre = nullptr;
int minVal = INT_MAX;
void traversal(TreeNode* root) {
//终止条件,到达空值
if (root == nullptr) return;
//顺序执行,中序遍历左中右
traversal(root->left);//左
if (pre != nullptr) minVal = min(minVal,abs(pre->val - root->val));
pre = root;
traversal(root->right);//右
}
int getMinimumDifference(TreeNode* root) {
//和昨天的验证二叉搜索树一个样
//一种存数组的递归,一种在递归过程遍历的递归(我选择用前一个结点做记录的递归),一种迭代法,继续用同一迭代法(全部思想都是左中右中序遍历)
//第二是尝试使用上一结点做记录在遍历中完成比较
//递归输入参数:当前根结点,返回值不需要返回值
traversal(root);
return minVal;
}
};
统一迭代法,记录上一节点版
class Solution {
public:
int getMinimumDifference(TreeNode* root) {
//和昨天的验证二叉搜索树一个样
//一种存数组的递归,一种在递归过程遍历的递归(我选择用前一个结点做记录的递归),一种迭代法,继续用同一迭代法(全部思想都是左中右中序遍历)
//第三是用迭代法,复习一下统一迭代法中序遍历
if (root == nullptr) return 0;
stack<TreeNode*> tempstack;
tempstack.push(root);
TreeNode* pre = nullptr;
int minVal = INT_MAX;
while (!tempstack.empty()) {
TreeNode* node = tempstack.top();
tempstack.pop();
if (node != nullptr) {
if (node->right) tempstack.push(node->right);
tempstack.push(node);
tempstack.push(nullptr);
if (node->left) tempstack.push(node->left);
}
else {
node = tempstack.top();
tempstack.pop();
if (pre != nullptr) minVal = min(minVal,abs(node->val - pre->val));
pre = node;
}
}
return minVal;
}
};
8.30日任务:二叉搜索树中的众数
迭代法忽略,和不占用额外空间的递归是一个思路
需要学习的地方:
1.map容器的了解和使用
2.pair数据类型的使用(用于存储一队值)
3.sort函数第三个输入参数(如何排序)了解和使用),可以加入仿函数
按照代码随想录给出的提示来:
方法一:非二叉搜索树
这个是仅仅看了思路,具体实操map,pair,sort的知识我得再补充学习一下
class Solution {
public:
unordered_map<int, int> map;//定义map数据结构
void traversal(TreeNode* root) {
//终止条件:
if (root == nullptr) return;
//顺序执行
map[root->val]++;
traversal(root->left);
traversal(root->right);
return;
}
bool static cmp (const pair<int, int>& a, const pair<int, int>& b) {
return a.second > b.second; //pair数据类型的引用的方式是first,second
}
vector<int> findMode(TreeNode* root) {
//方法一: 当作普通二叉树看待,首先遍历整棵树,将节点的值存储到map中去,再对map进行排序,获取频率出现最高的元素
//递归:用前序遍历
vector<int> result;
if (root == nullptr) return result;
traversal(root);//拿到map;
vector<pair<int, int>> vec(map.begin(), map.end());//先把map类型转化为数组,数组元素为pair类型
sort(vec.begin(), vec.end(), cmp); // 给频率排个序,cmp是排序函数
result.push_back(vec[0].first);
for (int i = 1; i < vec.size(); i++) {
// 取最高的放到result数组中
if (vec[i].second == vec[0].second) result.push_back(vec[i].first);
else break;
}
return result;
}
};
方法二:按照代码随想录思路自己编写,一些计数小细节没有搞好,花费了一些时间。
特殊情况就是:众数最大值为1的时候。
class Solution {
public:
vector<int> result;
TreeNode* pre = nullptr;//记录上一节点
int maxCount = INT_MIN;//设置最大计数值
int count = 1;//灵活计数
void traversal(TreeNode* root) {
//终止条件
if (root == nullptr) return;
//顺序执行,二叉搜索树的有序顺序必须是中序遍历:左中右
//左
traversal(root->left);
//中为当前节点
if (pre != nullptr) {
if (pre->val == root->val) {
count++;
if (count == maxCount) result.push_back(root->val);
else if (count > maxCount) {
maxCount = count;
result.clear();
result.push_back(root->val);
}
}
else {
count = 1;
if (maxCount == 1) result.push_back(root->val);
}
}
else {
maxCount = 1;
result.push_back(root->val);
}
pre = root;
//右
traversal(root->right);
return;
}
vector<int> findMode(TreeNode* root) {
//方法二:不申请额外空间(数组),利用二叉搜索树的特点搜索
//递归,输入参数为当前节点,在过程中设置计数变量,不需要输出参数,直接在递归函数中处理result;
//要考虑众数最大值为1的情况
if (root == nullptr) return result;
traversal(root);
return result;
}
};
下面这个方法(代码随想录的写法),处理当前节点是最简洁的,这个属于思维方式的问题,逻辑越简洁清晰,写的就越简洁,本质上是题目练的还不够多,加油吧
class Solution {
public:
vector<int> result;
TreeNode* pre = nullptr;//记录上一节点
int maxCount = INT_MIN;//设置最大计数值
int count = 1;//灵活计数
void traversal(TreeNode* root) {
//终止条件
if (root == nullptr) return;
//顺序执行,二叉搜索树的有序顺序必须是中序遍历:左中右
//左
traversal(root->left);
//中为当前节点
if (pre == nullptr) count = 1;
else if (pre->val == root->val) count++;
else count = 1;
pre = root;
if (count == maxCount) result.push_back(root->val);
if (count > maxCount) {
maxCount = count;
result.clear();
result.push_back(root->val);
}
//右
traversal(root->right);
return;
}
vector<int> findMode(TreeNode* root) {
//方法二:不申请额外空间(数组),利用二叉搜索树的特点搜索
//递归,输入参数为当前节点,在过程中设置计数变量,不需要输出参数,直接在递归函数中处理result;
//要考虑众数最大值为1的情况
if (root == nullptr) return result;
traversal(root);
return result;
}
};
8.30思考:最近加一个任务就是:C++函数模板以及操作符重载视频刷一下,目的是什么,目的是让自己能够看懂STL库。主要先尝试看懂基础的vector,sort,借助chatgpt,和csdn学习和做笔记。
8.31日任务:二叉树的最近公共祖先
这道题目是有点难度的,二刷需要重视。
首先是跟着代码随想录的思路,没有看代码独立编写,左右中,对于中结点的处理一坨稀饭,浪费了将近1个小时。
我的思路是没有找到公共祖先之前flag = flase,找到了公共祖先之后,flag = true.没有代码随想录中的代码那么清晰。
class Solution {
public:
bool flag = false;
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
//我已经看了代码随想录的思路,代码还没看
//思路:左右中,输入一个子树根节点,倘若左右子树出现P,Q,那么根节点就是公共祖先,如果一侧出现p或q,另一侧没有出现,但是根节点本身就是p,q中的一个
//递归左右中,递归函数的输入参数:树的根节点,以及p,q节点,返回值为nullptr或者最近公共祖先,lowestCommonAncestor符合这个样式
//终止条件
if (root == nullptr) return nullptr;
//题目中提到的树中节点数目范围>=2,所以我的终止条件是合理的
//顺序执行逻辑(左右中)
TreeNode* leftnode = lowestCommonAncestor(root->left, p, q);
TreeNode* rightnode = lowestCommonAncestor(root->right, p, q);
//中
if (flag == false) {
if (leftnode != nullptr && rightnode != nullptr ) {flag = true; return root;}
else if (leftnode == nullptr && rightnode == nullptr) {
if (root == p || root == q) return root;
else return nullptr;
}
else {
if (root == p || root == q) flag = true;
return root;
}
}
else {
if (leftnode != nullptr) return leftnode;
else return rightnode;
}
return nullptr;
}
};
代码随想录中的代码:这段代码是非常简洁的,遍历了整棵树,同时一直在传递的是公共祖先节点。
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
//我已经看了代码随想录的思路,代码还没看
//思路:左右中,输入一个子树根节点,倘若左右子树出现P,Q,那么根节点就是公共祖先,如果一侧出现p或q,另一侧没有出现,但是根节点本身就是p,q中的一个
//递归左右中,递归函数的输入参数:树的根节点,以及p,q节点,返回值为nullptr或者最近公共祖先,lowestCommonAncestor符合这个样式
//终止条件
if (root == nullptr || root == p || root == q) return root; //这里面已经包含了指定节点本身为最近公共祖先的情况
//题目中提到的树中节点数目范围>=2,所以我的终止条件是合理的
//顺序执行逻辑(左右中)
TreeNode* leftnode = lowestCommonAncestor(root->left, p, q);
TreeNode* rightnode = lowestCommonAncestor(root->right, p, q);
//处理中逻辑
if (leftnode != nullptr && rightnode != nullptr) return root;
if (leftnode != nullptr && rightnode == nullptr) return leftnode;//这里也包含了指定节点本身为公共祖先的情况
else if (leftnode == nullptr && rightnode != nullptr) return rightnode;
else return nullptr;
}
};
9.1日任务:二叉搜索树的最近公共祖先
复习回想昨天的思路和代码,这个代码解这道题没有任何问题。但是二叉搜索树有更简单的方法。
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
//今天状态不太好,先思考回想一下普通二叉树的最近公共祖先求法
//普通二叉树的公共祖先
//递归,输入参数为根节点以及两个要找的节点,顺序执行分别找左右子树是否含有这两个节点,如果是左右子树返回这两个节点。当这两个子树都返回节点的时候,则根节点为公共祖先,当只有一个子树返回值不为空,根节点既可以是根节点,也可以不是根节点。
//昨天的思路是,1.一旦发现是要找的子结点,那就返回这个子结点,这个子结点既可以是根节点,也可以不是根节点。使用中序遍历左中右,遍历所有节点。自底向上一直遍历。
//寻找普通二叉树的最近公共祖先的代码
//终止条件
if (root == nullptr || root == p || root == q) return root;
//顺序执行
TreeNode* leftNode = lowestCommonAncestor(root->left, p, q);
TreeNode* rightNode = lowestCommonAncestor(root->right, p, q);
if (leftNode != nullptr && rightNode != nullptr) return root;
else if (leftNode != nullptr && rightNode == nullptr) return leftNode;
else return rightNode; //这个else已经包含了两个子树都没有找到以及只有右子树找到了的情况。
return nullptr;
}
};
根据代码随想录中的思路自行实现:自顶向下第一个节点值位于[p,q]值区间内的节点即为最终的节点,理解了这一点,整个代码编写就会很简单了。这个代码还不够简洁,因为还多遍历了一些节点。
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
//对于二叉搜索树,看了代码随想录的思路,假如树的根节点的值位于[p,q]或者[q,p]的值区间里,那么这个节点就是公共祖先
//用递归,输入根节点,指定节点p,q,返回祖先节点
//终止条件
if (root == nullptr || (root->val >= p->val && root->val <= q->val) || (root->val <= p->val && root->val >= q->val)) return root;
//顺序执行,终止条件已经把中节点遍历过了
TreeNode* leftNode = lowestCommonAncestor(root->left, p, q);//左右
TreeNode* rightNode = lowestCommonAncestor(root->right, p, q);//左右
if (leftNode != nullptr && rightNode == nullptr) return leftNode;
else return rightNode;
}
};
遍历最少节点的写法
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
//对于二叉搜索树,看了代码随想录的思路,假如树的根节点的值位于[p,q]或者[q,p]的值区间里,那么这个节点就是公共祖先
//用递归,输入根节点,指定节点p,q,返回祖先节点
//终止条件
if (root == nullptr || (root->val >= p->val && root->val <= q->val) || (root->val <= p->val && root->val >= q->val)) return root;
//顺序执行,终止条件已经把中节点遍历过了
TreeNode* returnNode;
if (root->val > q->val && root->val > p->val) {
returnNode = lowestCommonAncestor(root->left, p, q);
}
else if (root->val < q->val && root->val < p->val) {
returnNode = lowestCommonAncestor(root->right, p, q);
}
return returnNode;
}
};
简洁写法:
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
//对于二叉搜索树,看了代码随想录的思路,假如树的根节点的值位于[p,q]或者[q,p]的值区间里,那么这个节点就是公共祖先
//用递归,输入根节点,指定节点p,q,返回祖先节点
//终止条件
if (root == nullptr || (root->val >= p->val && root->val <= q->val) || (root->val <= p->val && root->val >= q->val)) return root;
//顺序执行,终止条件已经把中节点遍历过了
TreeNode* returnNode;
if (root->val > q->val && root->val > p->val) {
return lowestCommonAncestor(root->left, p, q);
}
else if (root->val < q->val && root->val < p->val) {
return lowestCommonAncestor(root->right, p, q);
}
return nullptr;
}
};
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
//迭代法就更简单了,这种写法很简洁可以掌握
while(root) { //根节点不为空
if (root->val > p->val && root->val > q->val) {
root = root->left;
}
else if (root->val < p->val && root->val < q->val) {
root = root->right;
}
else return root;//这个else就包括了root的值位于p,q值的区间内
}
return root;
}
};
9.1日总结反思:第一遍学习过程中看了思路和参考代码之后,依然要坚持自己复现代码,这时一个加深印象,二次加工理解的过程,必须坚持遵循。
9.2日任务:二叉搜索树中的插入操作
大致逻辑就是:从上至下,比你大就去你右边,比你小就去你左边,二叉搜索树的规律还是非常强的。
参考代码随想录之后,一定要保证思路在自己的脑子里复现,然后由自己输出代码,保证自己思维的独立性。
按照自己的思路先尝试了迭代法整体比较简单吧
class Solution {
public:
TreeNode* insertIntoBST(TreeNode* root, int val) {
//代码随想录二叉树的插入操作的思路我没怎么看懂,先按照自己的思路来,代码随想录给出了递归和迭代
//首先尝试迭代法
TreeNode* tempRoot = root;
if (tempRoot == nullptr) {
tempRoot = new TreeNode{val};
return tempRoot;
}
TreeNode* lastNode;
while(tempRoot){//根节点不为空
lastNode = tempRoot;
if (tempRoot->val > val) tempRoot = tempRoot->left;
else tempRoot = tempRoot->right;
}
TreeNode* newNode = new TreeNode{val};
if (lastNode->val > val) lastNode->left = newNode;
else lastNode->right = newNode;
return root;
}
};
迭代法类似一种循环深入的节奏,递归类似于把这种循环深入用递归的形式表示。
递归法一:递归函数不加返回值
class Solution {
public:
void mySolution(TreeNode* root, int val) {
//终止条件
if (val > root->val && root->right == nullptr) {
TreeNode * newNode = new TreeNode{val};
root->right = newNode;
return;
}
if (val < root->val && root->left == nullptr) {
TreeNode * newNode = new TreeNode{val};
root->left = newNode;
return;
}
//顺序执行
if (val > root->val) mySolution(root->right, val);
else mySolution(root->left, val);
}
TreeNode* insertIntoBST(TreeNode* root, int val) {
//代码随想录二叉树的插入操作的思路我没怎么看懂,先按照自己的思路来,代码随想录给出了递归和迭代
//接下来尝试递归,递归的思路:自上向下递归,终止条件就是发现:val大于节点值且节点右侧为空。返回值,可以有,也可以没有,先写没有的
if (root == nullptr) {
root = new TreeNode{val};
return root;
}
mySolution(root, val);
return root;
}
};
递归法二:递归函数加返回值
class Solution {
public:
TreeNode* insertIntoBST(TreeNode* root, int val) {
//代码随想录二叉树的插入操作的思路我没怎么看懂,先按照自己的思路来,代码随想录给出了递归和迭代
//接下来尝试递归,递归的思路:自上向下递归,终止条件就是发现:val大于节点值且节点右侧为空。返回值,可以有,也可以没有,先写没有的
//现在写有返回值的递归,返回值就直接返回自己
//终止条件
if (root == nullptr) {
TreeNode* newNode = new TreeNode{val};
return newNode;
}
//顺序执行
TreeNode* node;
if (val > root->val) {
node = insertIntoBST(root->right, val);
if (node != root) root->right = node;
}
else {
node = insertIntoBST(root->left, val);
if (node != root) root->left = node;
}
return root;
}
};
写的再精简一点:这个就很精简了,第一次学,精不精简精简无所谓
class Solution {
public:
TreeNode* insertIntoBST(TreeNode* root, int val) {
//代码随想录二叉树的插入操作的思路我没怎么看懂,先按照自己的思路来,代码随想录给出了递归和迭代
//接下来尝试递归,递归的思路:自上向下递归,终止条件就是发现:val大于节点值且节点右侧为空。返回值,可以有,也可以没有,先写没有的
//现在写有返回值的递归,返回值就直接返回自己
//终止条件
if (root == nullptr) {
TreeNode* newNode = new TreeNode{val};
return newNode;
}
//顺序执行
if (val > root->val) root->right = insertIntoBST(root->right, val);
else root->left = insertIntoBST(root->left, val);
return root;
}
};
9.3日任务:删除二叉搜索树中的节点
只掌握了第一种递归法,另外简略版普通二叉树的递归和迭代法太绕,不实用,直接跳过。
全部按照自己的思路独立编写,刚开始尝试迭代法写,写着写着一环套一环,发现还是递归更方便简单。
class Solution {
public:
TreeNode* deleteNode(TreeNode* root, int key) {
//主要思路:先找这个节点,找到节点之后,两种选择,把右侧值最小的值移动过来,二是把左侧最大的值移动过来。
//开始尝试了迭代法,发现不行太麻烦了,还是得用递归法
//递归法输入参数,节点和key值,返回值为改造后的根节点
//终止条件:根节点为要找的值的情况
if (root == nullptr) return root;//空值情况
if (root->val == key && root->left == nullptr && root->right == nullptr) {
delete root;
root = nullptr;
return nullptr;
}
if (root->val == key) { //顶点就是要删除的点
TreeNode* tempNode = root;
//此时要删的是顶点,1.找右侧最小值2.找左侧最大值
if (tempNode->right) { //去右侧找最小值
tempNode = tempNode->right;
TreeNode* lastNode = tempNode;
while (tempNode->left) {
lastNode = tempNode;
tempNode = tempNode->left;
}
//此时的tempNode就是要提上去做头节点的点
if (lastNode == tempNode) tempNode->left = root->left;
else {
if (tempNode->right) lastNode->left = tempNode->right;
else lastNode->left = nullptr;
tempNode->left = root->left;
tempNode->right = root->right;
}
}
else{
//去左边找最大值
tempNode = tempNode->left;
TreeNode* lastNode = tempNode;
while (tempNode->right) {
lastNode = tempNode;
tempNode = tempNode->right;
}
//此时的tempNode就是要提上去做头节点的点
if (lastNode == tempNode) tempNode->right = root->right;
else {
if (tempNode->left) lastNode->right = tempNode->left;
else lastNode->right = nullptr;
tempNode->left = root->left;
tempNode->right = root->right;
}
}
delete root;
root = nullptr;
return tempNode;
}
//顺序执行
TreeNode* tempNode = root;
TreeNode* lastNode = nullptr;
bool flag =true;//true表示左边
while(tempNode){
if (key > tempNode->val) {
flag = true;
lastNode = tempNode;
tempNode = tempNode->right;
}
else if (key < tempNode->val) {
flag = false;
lastNode = tempNode;
tempNode = tempNode->left;
}
else {
if (flag) lastNode->right = deleteNode(tempNode, key);
else lastNode->left = deleteNode(tempNode, key);
break;
}
}
return root;
}
};
代码随想录的思路和方法:对于终止条件的分类要比我处理的好。
我的思路是找右侧最小值或者左侧最大值去更换顶点;代码随想的思路是把整个左子树放在右子树最小值底下,这个方案爆杀我的方案
按照代码随想录的思路编写代码
class Solution {
public:
TreeNode* deleteNode(TreeNode* root, int key) {
//用代码随想录的思路
//终止条件
//第一种:空节点情况
if (root == nullptr) return root;
if (root->val == key) {
//第二种:两侧都为空
if (root->left == nullptr && root->right == nullptr) {delete root; root = nullptr; return nullptr;}
//第三种:左侧为空,右侧不为空
else if (root->left == nullptr && root->right != nullptr) {
TreeNode* rootNode = root->right;
delete root;
root = nullptr;
return rootNode;
}
//第四种:左侧不为空,右侧为空
else if (root->left != nullptr && root->right == nullptr) {
TreeNode* rootNode = root->left;
delete root;
root = nullptr;
return rootNode;
}
//都不为空
else {
TreeNode* tempNode = root;
TreeNode* returnNode = root->right;
tempNode = tempNode->right;
while(tempNode->left) {
tempNode = tempNode->left;
}
tempNode->left = root->left;
delete root;
root = nullptr;
return returnNode;
}
}
//顺序执行
//key == root->val的情况已经包含了
//下面这段代码其实很能够体现对于递归的理解
if (key > root->val) root->right = deleteNode(root->right, key);
else if (key < root->val) root->left = deleteNode(root->left, key);
return root;
}
};
思考:代码中对于中止条件处理部分体现的是编写程序的思维方式;而顺序执行则体现的是对于递归是否有更深刻的理解。
下面两个代码段对于递归的理解还是非常有区别的,还是得多刷题学习。
代码段二已经写成迭代法的样子了,给递归增加了很多不必要的步骤。
代码段一:
if (key > root->val) root->right = deleteNode(root->right, key);
else if (key < root->val) root->left = deleteNode(root->left, key);
代码段二:
TreeNode* tempNode = root;
TreeNode* lastNode = nullptr;
bool flag =true;//true表示左边
while(tempNode){
if (key > tempNode->val) {
flag = true;
lastNode = tempNode;
tempNode = tempNode->right;
}
else if (key < tempNode->val) {
flag = false;
lastNode = tempNode;
tempNode = tempNode->left;
}
else {
if (flag) lastNode->right = deleteNode(tempNode, key);
else lastNode->left = deleteNode(tempNode, key);
break;
}
}
9.4日任务:修剪二叉搜索数
注意点:递归的截至条件中必须要有return,没有return会把判断迁移到下方的顺序执行部分,会造成一些问题。
我自己的想法构思和编写和调试花了1个半小时,保留了自己的思维独立性,发现自己对于递归的理解确实非常不足。
继续加油。代码随想录的想法明天来再搞。
完全按照自己的想法,思路上没什么问题,就是相比代码随想录的思路复杂了很多,看来对于递归还是理解不足。细节方面的问题还是挺多的。
刷第二遍时可以刻意注意一下几个break和return上的细节考虑。
class Solution {
public:
TreeNode* trimBST(TreeNode* root, int low, int high) {
//自行思路梳理:
//比较明显的思路:自顶向下,1.找到最小值low,左子树全清空 2.找到最大值high,右子树全清空
//特殊的情况:没遍历一个根节点,比较根节点和low,high,三种情况:low,high都在一侧(继续向下移动)
//,low,high在两侧,一个在顶点一个在一侧(若在右侧,则直接删除右子树即可)
//不太好想,先按照顺序的思路想:
//1.一个在顶点,这种情况最简单,顶点为最小值,左子树清空,右子树用双指针法,尾部指针只有当当前值比high小时才移动
//2.在一侧,只管走到一个顶点的情况或者在两侧的情况,所以一个为顶点的情况是终止条件,
//3.对于在两侧的情况,左侧小,删掉更小的值,和顶点在一侧的情况差不多
//用递归:
//1.终止条件
//1.一个是顶点的情况
if (root == nullptr) return nullptr;
if (root->val == low && low == high) {
root->left = nullptr;
root->right = nullptr;
return root;
}
if (root->val == low) {
root->left = nullptr;
TreeNode* cur = root->right;
TreeNode* last = root;
while (cur) {
if (cur->val == high) {
last->right = cur;
cur->right = nullptr;
return root;
}
else if (cur->val < high) {
last->right = cur;
last = cur;
cur = cur->right;
}
else { //大于的情况
cur = cur->left;
}
}
//截至条件里,肯定要有return的
return root;
}
else if (root->val == high) {
root->right = nullptr;
TreeNode* cur = root->left;
TreeNode* last = root;
while (cur) {
if (cur->val == low) {
last->left = cur;
cur->left = nullptr;
return root;
}
else if (cur->val > low) {
last->left = cur;
last = cur;
cur = cur->left;
}
else { //小于的情况
cur = cur->right;
}
}
return root;
}
else if (root->val > low && root->val <high) {
//先去右边
TreeNode* cur = root->right;
TreeNode* last = root;
while (cur) {
if (cur->val == high) {
last->right = cur;
cur->right = nullptr;
break;
}
else if (cur->val < high) {
last->right = cur;
last = cur;
cur = cur->right;
}
else { //大于的情况
cur = cur->left;
}
}
//再去左边
cur = root->left;
last = root;
while (cur) {
if (cur->val == low) {
last->left = cur;
cur->left = nullptr;
break;
}
else if (cur->val > low) {
last->left = cur;
last = cur;
cur = cur->left;
}
else { //小于的情况
cur = cur->right;
}
}
//终止条件中是一定要加return的
return root;
}
//剩下就是去一侧的情况
if (root->val > high) root = trimBST(root->left, low, high);
else root = trimBST(root->right, low, high);
return root;
}
};
代码随想录的想法:
我的评价,思路好理解,但是代码的话我无法独立写出,只能通过阅读代码推理出背后的逻辑。
我的反思:递归这一块还是要尽量保证自己思维的独立性,只有这样才能不断有更深的理解,纯粹按着代码随想录的思路并不是一个很好的选择。
class Solution {
public:
TreeNode* trimBST(TreeNode* root, int low, int high) {
//其实就是三种情况:第一种:两个边界点都在一侧;第二种:两个边界点分别在两侧;第三种:一个边界点在顶点,另一个在一侧。
//我的思路是把第一种作为顺序执行,第二种和第三种作为终止条件来进行编写代码
//代码随想录给出的思路相对来说比较绕,需要对递归有更高的认识。
//下面给出代码随想录的代码:
if (root == nullptr) return nullptr;
//第一种情况,这种情况初始根节点已经不是最终要的根节点了
//两个边界点都在同一侧,向同一侧不断递归就可以到达第二三种情况,第二三种情况的返回值就是最终修剪后的根节点
if (root->val < low) {
TreeNode* right = trimBST(root->right, low, high);
return right;
}
if (root->val > high) {
TreeNode* left = trimBST(root->left, low, high);
return left;
}
//第二三种情况合在一起,这两种情况,初始根节点依然是初始根节点
//第二种情况递归之后又会变成第一种情况
root->left = trimBST(root->left, low, high);
root->right = trimBST(root->right, low ,high);
return root;
}
};
迭代法:其实并不太好理解的,二次刷题可以再试一下这个思路
class Solution {
public:
TreeNode* trimBST(TreeNode* root, int low, int high) {
//迭代法
//第一种:两个边界点都在一侧;第二种:两个边界点分别在两侧;第三种:一个边界点在顶点,另一个在一侧。
//先将所有的情况修改成第二三种
if (root == nullptr) return root;
while (root != nullptr && (root->val < low || root->val > high)) {
if (root->val < low) root = root->right;
else root = root->left;
}
//此时既可以进入第二三种情况,也可以进入空节点的情况
//进入第二三种情况,其实可以看作是同一种情况
//此时的root就是要返回的根节点,只要去左右两侧做修建即可
//先去左侧修剪
//设立一个遍历节点
TreeNode* cur = root;
while (cur != nullptr) {
while (cur->left && cur->left->val < low){
cur->left = cur->left->right;
}
//这个值大于low,则继续向左移
cur = cur->left;
}
//去右侧修剪
cur = root;
while (cur != nullptr) {
while (cur->right && cur->right->val > high) {
cur->right = cur->right->left;
}
cur = cur->right;
}
return root;
}
};
第二天再次尝试:
class Solution {
public:
TreeNode* trimBST(TreeNode* root, int low, int high) {
//迭代法
//第一种:两个边界点都在一侧;第二种:两个边界点分别在两侧;第三种:一个边界点在顶点,另一个在一侧。
//再试一下递归
//第一种递归法:将第二三种情况作为终点条件
if (root == nullptr) return nullptr;
if (root->val >= low && root->val <= high) {
TreeNode* cur = root;
while (cur != nullptr) {
while (cur->left && cur->left->val < low){
cur->left = cur->left->right;
}
//这个值大于low,则继续向左移
cur = cur->left;
}
//去右侧修剪
cur = root;
while (cur != nullptr) {
while (cur->right && cur->right->val > high) {
cur->right = cur->right->left;
}
cur = cur->right;
}
return root;
}
//顺序执行
if (root->val < low) root = trimBST(root->right, low, high);
else root = trimBST(root->left, low, high);
return root;
}
};
底下这种并不好想,上面的那种更好想。
class Solution {
public:
TreeNode* trimBST(TreeNode* root, int low, int high) {
//迭代法
//第一种:两个边界点都在一侧;第二种:两个边界点分别在两侧;第三种:一个边界点在顶点,另一个在一侧。
//再试一下递归
//第二种递归,返回值是要返回的根节点
//终止条件
if (root == nullptr) return nullptr;
//第一种情况
if (root->val < low) {
TreeNode* right = trimBST(root->right, low, high);
return right;
}
if (root->val > high) {
TreeNode* left = trimBST(root->left, low, high);
return left;
}
//第二三种情况
root->left = trimBST(root->left, low, high);
root->right = trimBST(root->right, low, high);
return root;
}
};
9.5日任务:将有序数组转化为二叉搜索树
9.5日策略调整:只掌握一种解决问题的方法即可,掌握一个递归就行了,第二次再来看多余的方法,在真正笔试的时候,你只能选择一种方法立刻编写出来。
直接按照自己的思路一遍过,没有什么压力,只试一种方案就可以了,第二遍回来再尝试其他方案
class Solution {
public:
TreeNode* mySolution(vector<int>& nums, int start, int end) {
//1.终止条件,start,end为开始和结束点对应的索引
//只有一个节点
if (start == end) {
TreeNode* newNode = new TreeNode{nums[start]};
return newNode;
}
if (start > end) {
return nullptr;
}
//顺序执行
int rootIndex = (start + end) / 2;//c++中这里是取整
TreeNode* root = new TreeNode{nums[rootIndex]};
root->left = mySolution(nums, start, rootIndex -1);
root->right = mySolution(nums, rootIndex + 1, end);
return root;
}
TreeNode* sortedArrayToBST(vector<int>& nums) {
//宗旨:第一次学,主要是对知识的掌握,只要用一种方法就可以了,其他方法第二遍刷的时候可以来尝试
//整数数组已经按照升序排列,请转为一颗平衡二叉搜索树
//平衡二叉树的概念:左右子树高度差不超过1
//构造思路:从中间位置切分成两个子树,然后分别构造子树
//用递归法,自上向下递归
//递归输入参数:数组,要构造的子树的索引,返回值,构造的子树根节点
TreeNode* root = mySolution(nums, 0, nums.size() - 1);
return root;
}
};
9.6日任务:把二叉搜索树转换为累加树
其实就是一个右左中遍历不断累加,还是得不断练,看似写出来的是一样的代码,但在思维方法上差距还是很大的,只尝试迭代法即可,第二遍再尝试其他方法。
按照自己的思路一遍过,不过写法上可以再简便一些,总之就是右左中遍历累加就好了
class Solution {
public:
int mySolution(TreeNode* root, int preValue) {
//终止条件
if (root == nullptr) return preValue;
//顺序执行,右中左
//右
if (root->right) {
preValue = mySolution(root->right, preValue);
}
//中
root->val = preValue + root->val;
preValue = root->val;
//左
if (root -> left) {
preValue = mySolution(root->left,preValue);
}
return preValue;
}
TreeNode* convertBST(TreeNode* root) {
//二叉搜索树的左中右遍历为升序,右中左遍历为降序
//这道题本身就是个降序叠加的思路
//思路就是右中左
//递归输入参数,树的根节点,返回值应为遍历过节点的总和
mySolution(root,0);
return root;
}
};
下面这个也可以
class Solution {
public:
int sumValue{0};
void mySolution(TreeNode* root) {
//终止条件
if (root == nullptr) return;
//顺序执行,右中左
//右
if (root->right) mySolution(root->right);
//中
root->val = sumValue + root->val;
sumValue = root->val;
//左
if (root->left) mySolution(root->left);
return;
}
TreeNode* convertBST(TreeNode* root) {
//二叉搜索树的左中右遍历为升序,右中左遍历为降序
//这道题本身就是个降序叠加的思路
//思路就是右中左
//递归输入参数,树的根节点,返回值应为遍历过节点的总和
mySolution(root);
return root;
}
};
更简便的是这种:
class Solution {
public:
int sumValue{0};
void mySolution(TreeNode* root) {
//终止条件
if (root == nullptr) return;
//顺序执行,右中左
//右
mySolution(root->right);
//中
root->val += sumValue;
sumValue = root->val;
//左
mySolution(root->left);
return;
}
TreeNode* convertBST(TreeNode* root) {
//二叉搜索树的左中右遍历为升序,右中左遍历为降序
//这道题本身就是个降序叠加的思路
//思路就是右中左
//递归输入参数,树的根节点,返回值应为遍历过节点的总和
mySolution(root);
return root;
}
};