资料来源:代码随想录
21.合并二叉树 617
有两种迭代法:一种是直接修改二叉树1,把2合并到1上去,合并之后的1就是要求的二叉树;另一种是重新定义一棵二叉树。
直接修改二叉树1:内存消耗31.4M,用时32ms
class Solution {
public:
TreeNode* mergeTrees(TreeNode* root1, TreeNode* root2) {
//函数的参数及返回值:传入要合并的两棵二叉树的根节点,返回合并后的二叉树的根节点
//终止条件:以下两种情况已经包含了1和2都为空的情况
if(root1==NULL) return root2; //如果1为空,合并后的结果应该为2,所以直接返回2
if(root2==NULL) return root1; //如果2为空,合并后的结果应该为1,所以直接返回1
//单层递归逻辑:把2合并到1上
root1->val=root1->val+root2->val; //中
//递归
root1->left=mergeTrees(root1->left,root2->left); //t1 的左子树是:合并 t1左子树 t2左子树之后的左子树
root1->right=mergeTrees(root1->right,root2->right); //t1 的右子树:是 合并 t1右子树 t2右子树之后的右子树
//最终t1就是合并之后的根节点
return root1;
}
};
重新定义二叉树:内存消耗32.1M,用时16ms
class Solution {
public:
TreeNode* mergeTrees(TreeNode* root1, TreeNode* root2) {
//函数的参数及返回值:传入要合并的两棵二叉树的根节点,返回合并后的二叉树的根节点
//终止条件:以下两种情况已经包含了1和2都为空的情况
if(root1==NULL) return root2; //如果1为空,合并后的结果应该为2,所以直接返回2
if(root2==NULL) return root1; //如果2为空,合并后的结果应该为1,所以直接返回1
//单层递归逻辑
TreeNode* root=new TreeNode(0);
root->val=root1->val+root2->val; //中
//递归
root->left=mergeTrees(root1->left,root2->left);
root->right=mergeTrees(root1->right,root2->right);
return root;
}
};
22.二叉搜索树中的搜索 700
二叉搜索树是一个有序树:
- 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
- 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
- 它的左、右子树也分别为二叉搜索树
这就决定了,二叉搜索树,递归遍历和迭代遍历和普通二叉树都不一样。
二叉搜索树的特性帮助我们决定了递归时的遍历顺序。
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;
}
};
23.验证二叉搜索树 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();
traversal(root);
for(int i=1; i<vec.size(); i++)
{
if(vec[i]<=vec[i-1]) return false; //后一个值小于等于前一个值,说明数组内不是递增的,则不是二叉搜索树
}
return true;
}
};
方法二:设置一个最大值,每次更新最大值为节点的值,相当于最大值记录的一直是当前遍历节点前一个节点的值。那么一旦出现当前节点的值小于等于最大值,说明不是递增的,则不是二叉搜索树。
注意最大值初始值的设置,本题的测试用例里有int的最小值,所以如果初始最大值设置为int的最小值的话,可能在中间就错误返回了。所以把最大值设置为long long的最小值。
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) return false; //前一个节点的值大于等于当前节点的值
else
{
maxVal=root->val; //否则更新最大值
}
//右:
bool right=isValidBST(root->right);
return left && right; //看起来只判断了两边,但实际上在“左中右”这个过程中就把根节点已经包含进去了
}
};
方法三:那如果测试用例里还包含long long最小值呢?那就不设置最大值了,直接用前后两个节点进行比较。
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; //前一个节点的值大于等于当前节点的值
else {
pre=root; //前一个节点的指针向后移动
}
bool right=isValidBST(root->right);
return left && right;
}
};
23.二叉搜索树中的最小绝对差 530
有的题目里,需要判断二叉树是否符合某一特性,或者要搜索特定的某一条路径或某一节点,那么要在搜索到了之后立刻返回,就需要返回值。本题是要遍历整棵二叉树,而结果值存进一个全局变量里就可以,所以不需要返回值。
class Solution {
private:
//先定义两个全局变量
int result=INT_MAX;
TreeNode* pre=NULL;
void traversal(TreeNode* cur)
{
if(cur==NULL) return;
traversal(cur->left); //递归的过程中是有回溯的,遍历左节点之后要回溯才能退回到中间节点
//中的处理逻辑
if(pre!=NULL)
{
result=min(result,cur->val-pre->val); //保证result始终是最小的差值
}
pre=cur; //cur在递归的过程中一步一步移动,pre跟着它
traversal(cur->right);
}
public:
int getMinimumDifference(TreeNode* root) {
traversal(root);
return result;
}
};
34.二叉搜索树中的众数 501
如果没有说是二叉搜索树的话,可以遍历整棵树,寻找出现频率最高的即可。
步骤:
1.遍历二叉树,用map来记录元素的值和各个值出现的频率,因为是要遍历整棵二叉树,所以任何一种遍历方式都可以;
2.定义一个排序函数;
3.map里无法针对value直接排序,只能针对key排序(unordered的话key也不可以),所以需要把map放进数组里面进行排序;
4.排好序后,取出现频率最高的元素放进result数组里。
class Solution {
private:
//递归遍历整棵二叉树
void traversal(TreeNode* cur, unordered_map<int,int>& map) //用map来存放遍历结果,key存放数值,value存放出现的次数
{
if(cur==NULL) return;
//采用前序遍历
map[cur->val]++; //中:统计出现次数
traversal(cur->left,map);
traversal(cur->right,map);
return;
}
//定义一个在数组中排序的函数,这里没有懂为什么传入的是静态变量
bool static compare(const pair<int,int>& a, const pair<int,int>& b)
{
return a.second>b.second; //按照pair里value的值进行降序排列
}
public:
vector<int> findMode(TreeNode* root) {
unordered_map<int,int> map; //定义一个map用于存放遍历结果
vector<int> result; //存放最后的数值结果
if(root==NULL) return result;
traversal(root,map);
vector<pair<int,int>> vec(map.begin(),map.end()); //把map里的数据放进数组里以便排序,数据形式还是pair
sort(vec.begin(),vec.end(),compare); //按照出现的次数进行降序排列
result.push_back(vec[0].first); //排好序后,vec数组里第一个元素就是出现频率最高的元素,把它存进结果数组里
for(int i=1; i<vec.size(); i++) //因为允许有相同元素,所以再找一下还有没有出现频率和最高频率相同的,也要存起来
{
if(vec[i].second==vec[0].second) result.push_back(vec[i].first);
}
return result;
}
};
如果确定是二叉搜索树,就可以利用二叉搜索树遍历的性质,即中序遍历是有序的。具体方法是使用两个指针,比较前后元素是否相同(根据题目中给出的性质,相同的元素一定是相邻的),如果相同,就count+1,不同的话count=1。
class Solution {
private:
int maxCount=0; //记录最大频率
int count=0; //记录频率
TreeNode* pre=NULL; //定义前一个指针
vector<int> result; //存放结果
void traversal(TreeNode* cur)
{
if(cur==NULL) return;
traversal(cur->left); //左
//中:处理逻辑
if(pre==NULL) //第一个节点
{
count=1;
}
else if(pre->val==cur->val) //当前节点的值与前一个节点的值相等
{
count++;
}
else
{
count=1; //不相等的话,该元素值出现次数重新等于1
}
pre=cur; //更新节点
if(count==maxCount)
{
result.push_back(cur->val); //碰到最大次数的话,把值存起来
}
if(count>maxCount) //出现次数大于最大次数的话
{
maxCount=count; //更新最大次数
result.clear(); //result里存放的值已经不是出现频率最高的了,已经失效,要清空
result.push_back(cur->val); //存入新的
}
traversal(cur->right); //右
return;
}
public:
vector<int> findMode(TreeNode* root) {
count=0;
maxCount=0;
TreeNode* pre=NULL; //这三行不要也可以,因为全局变量已经在前面定义了,但加上会快一点
result.clear();
traversal(root);
return result;
}
};
26.二叉树的最近公共祖先 236
需要自底向上查找,如何实现?利用回溯。
后序遍历里包含天然的回溯过程:将左右子树处理后的结果返回给中间节点。
情况1:如果递归遍历遇到q,就将q返回,遇到p 就将p返回,那么如果 左右子树的返回值都不为空,说明此时的中节点,一定是q 和p 的最近祖先。注意题目要求二叉树中的元素没有重复,所以不会出现两个p或q。
情况2:节点本身p(q),它拥有一个子孙节点q(p)。
实现情况1的过程包含了情况2,因为“遇到p或q就返回”,也包含了p或q本身就是公共祖先的情况。
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
//返回值和传入的参数:要找最近公共祖先节点,所以返回treenode类型;要传入的是给定二叉树的根节点和两个指定节点
//终止条件
//1.空节点
//2.对应情况二:p或q就是根节点,那它本身就是一个公共祖先,则可以直接返回root
if(root==NULL) return NULL;
if(root==q || root==p) return root;
//情况一:
//递归:后序
TreeNode* left=lowestCommonAncestor(root->left,p,q); //左子树进行递归,返回在左子树找到的结果
TreeNode* right=lowestCommonAncestor(root->right,p,q); //右子树进行递归,返回右子树找到的结果
//中:处理逻辑
if(left!=NULL && right!=NULL)
{
return root; //左子树和右子树的返回结果均不为空,说明分别找到了p和q,那么最近公共祖先就应该是root
}
else if(left==NULL && right!=NULL)
{
return right; //左子树返回结果为空而右子树不为空,说明右子树里有符合要求的,那么最近公共祖先就应该是right,则返回right
}
else if(left!=NULL && right==NULL)
{
return left; //左子树返回结果不为空,说明左子树里有符合要求的
}
else //左右返回结果均为空
{
return NULL;
}
}
};
28.二叉搜索树的最近公共祖先 235
本题是二叉搜索树,二叉搜索树是有序的,那得好好利用一下这个特点。
在有序树里,如果判断一个节点的左子树里有p,右子树里有q呢?
因为是有序树,所以如果中间节点是 q 和 p 的公共祖先,那么中节点的数一定是在 [p, q]区间的。即 中节点 > p && 中节点 < q 或者 中节点 > q && 中节点 < p。
那么只要从上到下去遍历,遇到 cur节点是数值在[p, q]区间中则一定可以说明该节点cur就是q 和 p的公共祖先。 那问题来了,一定是最近公共祖先吗?当我们从上向下去递归遍历,第一次遇到 cur节点是数值在[p, q]区间中,那么cur就是 p和q的最近公共祖先。
所以思路:比较cur->val和p->val、q->val的大小关系。通过判断当前节点是否在区间中,来判断出当前节点是否是最近公共祖先。
class Solution {
private:
//先写一个递归函数
TreeNode* traversal(TreeNode* cur, TreeNode* p, TreeNode* q)
{
//终止条件:遇到空则返回
if(cur==NULL) return NULL;
//单层递归逻辑:遍历二叉搜索树,寻找[p,q]或[q,p],注意是左闭右闭
//若cur->val比p、q都大,说明cur太靠右了,目标区间在左子树上,需要向左边递归
//如果比P和Q都小,说明cur太靠左了,目标区间在右子树上,需要向右递归
//现在是不知道p和q谁大谁小的,但是除了都大或者都小这两种情况之外,cur一定会在p和q的区间[p,q]或[q,p]里,不论谁大谁小
if(cur->val>p->val && cur->val>q->val) //不知道p q谁大谁小,所以两个都要判断
{
TreeNode* left=traversal(cur->left,p,q);
if(left!=NULL) //返回的left不为空,说明找到了
{
return left;
}
}
if(cur->val<p->val && cur->val<q->val)
{
TreeNode* right=traversal(cur->right,p,q);
if(right!=NULL)
{
return right;
}
}
//当cur在p和q的区间[p,q]或[q,p]里,cur本身就是最近公共祖先
return cur;
}
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
return traversal(root,p,q);
}
};
这里有一个“搜索边”还是“搜索整棵树”的区别。
如果是搜索边的话,当递归函数返回值符合条件,就会立即返回,比如本题中left!=NULL,就直接返回left;
if (递归函数(root->left)) return ;
if (递归函数(root->right)) return ;
如果是搜索整棵树,就会把搜索到的节点先存起来,后续可能会有处理逻辑。
left = 递归函数(root->left);
right = 递归函数(root->right);
left与right的逻辑处理;
29.二叉搜索树中的插入操作 701
虽然说有很多种插入方式,但我们只把新节点插到叶子节点就行。所以关键点在于,如何在遍历到叶子节点之后,在新节点上建立起父子节点关系:通过递归函数返回值完成了新加入节点的父子关系赋值操作,下一层将加入节点返回,本层用root->left或者root->right将其接住。
同样利用二叉搜索树的有序性质。
class Solution {
public:
TreeNode* insertIntoBST(TreeNode* root, int val) {
//终止条件:
if(root==NULL) //如果节点为空,说明遍历到叶子节点了,就可以用val建立新节点了
{
TreeNode* node= new TreeNode(val);
return node; //把新建立的节点返回上一层
}
//单层递归逻辑
if(root->val > val) //说明应该把新节点插入左子树
{
root->left=insertIntoBST(root->left,val); //用root->left接住下一层返回的要插入的新节点,就在root和新节点之间建立了父子关系
}
if(root->val < val) //应该把新节点插入右子树
{
root->right=insertIntoBST(root->right,val);
}
return root;
}
};
30.删除二叉搜索树中的节点 450
采用递归法。
终止条件:找到要删除的节点就可以终止了,那么还有删除节点的操作,所以删除节点也在终止条件里。
有五种情况:
没遍历到要删除的节点:
1.返回空;
遍历到要删除的节点:
2.要删除的节点是叶子节点,左右孩子均为空,直接删除即可,然后返回NULL给上一层,因为要删除的本身就是叶子节点,删了之后上一层就变成新的叶子节点,所以用root->left或root->right接收NULL是正确的;
3.要删除的节点左孩子为空,右孩子不为空,则右孩子补位,返回右孩子给上一层;
4.要删除的节点左孩子不为空,右孩子为空,则左孩子补位,返回左孩子给上一层;
5.要删除的节点左右孩子均不为空,可以把左子树并到右子树上,也可以把右子树并到左子树上,是一样的,选一种即可。如果选把左子树并到右子树上:考虑二叉搜索树的性质,左子树的所有节点值必然小于右子树,所以要在右子树上找比根节点数值大的最小值,把左子树挂在这个节点的左孩子,也就是把左子树放在右子树的最左下节点的左孩子位置。然后返回右子树的根节点来补位。
2345返回上一层的都是删除节点之后补位的新节点。
class Solution {
public:
TreeNode* deleteNode(TreeNode* root, int key) {
//终止条件:
//1.没找到要删除的节点,则返回空
if(root==NULL) return NULL;
//找到要删除的节点了
if(root->val==key)
{
if(root->left==NULL && root->right==NULL) //2.要删除的节点是叶子节点,直接删除即可,然后返回空给上一层,因为这个删除之后上一层会是新的叶子节点,满足左右均为空
{
delete root; //释放内存
return NULL;
}
else if(root->left!=NULL && root->right==NULL) //3.要删除的节点左孩子不为空右孩子为空,则需要左孩子补位,所以把左孩子返回给上一层
{
TreeNode* node=root->left;
delete root;
return node;
}
else if(root->left==NULL && root->right!=NULL) //4.要删除的节点左孩子为空右孩子不为空,则需要右孩子补位,把右孩子返回给上一层
{
TreeNode* node=root->right;
delete root;
return node;
}
else //5.左右孩子均不为空,则把左子树并到右子树去
{
TreeNode* cur=root->right;
while(cur->left!=NULL)
{
cur=cur->left; //找右子树的最左节点
}
cur->left=root->left; //把左子树赋给右子树最左节点的左孩子,然后就可以删除原来的节点了
TreeNode* tmp=root;
root=root->right; //右子树的根节点此时成为新的根节点
delete tmp; //释放内存
return root;
}
}
//递归:开始接收下一层返回的节点
if(root->val > key) root->left=deleteNode(root->left,key); //要删除的节点在左子树,所以用左孩子去接收下面删除节点之后返回来的补位的新节点
if(root->val < key) root->right=deleteNode(root->right,key); //要删除的节点在右子树,所以用右孩子去接收
return root;
}
};
31.修建二叉搜索树 669
class Solution {
public:
TreeNode* trimBST(TreeNode* root, int low, int high) {
//终止条件:
if(root==nullptr) return nullptr;
//以下两个if用来处理当根节点也不在区间内的情况
if(root->val < low) //根据二叉搜索树的性质,此时右子树可能有在区间内的节点,所以对右子树进行修剪,并返回修剪过的右子树,这样连不满足要求的根节点也就一起删了
{
return trimBST(root->right,low,high);
}
if(root->val > high) //此时左子树可能有在区间内的节点,所以对左子树进行修剪,并返回修剪过的左子树
{
return trimBST(root->left,low,high);
}
//以下来处理根节点在区间内的情况:根节点在区间内,去修剪左右子树即可
root->left=trimBST(root->left,low,high); //修剪左子树,并返回修剪后的新的根节点作为左子树的根节点
root->right=trimBST(root->right,low,high); //修剪右子树,并返回修剪后的新的根节点作为右子树的根节点
return root; //返回处理完的新二叉树的根节点
}
};
32.将有序数组转化为二叉搜索树 108
有不同的构造方法。注意区间是左闭右闭。
class Solution {
private:
//先用一个递归函数来构建二叉树
TreeNode* traversal(vector<int>& nums, int left, int right) //要返回构建的二叉树的根节点;传入的是数组和数组的左右两个下标
{
//终止条件:递归到left>right就说明数组元素已经完了
if(left>right) return nullptr;
//mid=(left+right)/2 这种方法也可以,但容易因为数据过大而越界,本题不会越界,但要注意这个可能
int mid=left+((right-left)/2); //计算中间值,这种方法意味着如果数组元素有偶数个的话,选择最中间两个左边的那个作为中间值,就是较小的那个作为中间值
TreeNode* root=new TreeNode(nums[mid]); //用中间值构建根节点
root->left=traversal(nums,left,mid-1); //用区间[left,mid-1]来构建左子树,并把构建好的左子树的根节点返回给root->left
root->right=traversal(nums,mid+1,right); //用区间[mid+1,right]来构建右子树
//注意上面这两个括号里面必须是left而不能是0,必须是right而不能是size-1,因为在递归过程中需要用到的数组是在不断变化的
return root; //都构建好后,返回根节点
}
public:
TreeNode* sortedArrayToBST(vector<int>& nums) {
TreeNode* root= traversal(nums,0,nums.size()-1); //调用递归函数来构建二叉树,要传入两个下标以提供初始数组
return root;
}
};
33.把二叉搜索树转换为累加树 538
因为累加是从大到小累加起来,从树中可以看出累加的顺序是右中左,所以我们需要反中序遍历这个二叉树,然后顺序累加就可以了。
class Solution {
private:
int pre=0; //定义一个变量,用来存放当前节点的再前一个节点的数值
void traversal(TreeNode* cur) //因为不需要返回什么,只是要遍历整棵二叉树并更新节点值,所以返回值类型为void
{
//终止条件:
if(cur==NULL) return;
traversal(cur->right);
//中:
cur->val=cur->val+pre;
pre=cur->val; //这样pre才能保证始终是当前节点的前一个节点值
traversal(cur->left);
}
public:
TreeNode* convertBST(TreeNode* root) {
pre=0;
traversal(root); //把需要遍历更新的二叉树的根节点传入递归函数
return root; //返回递归结束后新二叉树的根节点
}
};
二叉树部分,完结撒花~~~~~