代码随想录训练营Day21 | Leetcode 530、501、236
一、530 二叉搜索树的最小绝对差
题目链接:530 二叉搜索树的最小绝对差
核心:通常二叉搜索树都利用中序遍历得到有序数组,对该有序数组进行操作是较为简单且易于理解的;但是构造遍历的有序数组需要额外的空间,因此为了提高效率,在遍历每个节点时就进行相关操作,待遍历结束即可得到最终求解值。
1、法1:构造有序数组
利用中序遍历得到有序数组,计算有序数组的最小绝对差:由于数组从小到大排列,且不存在重复元素,因此只需计算相邻节点元素的差值,逐个比较得到最小差值即可。
vector<int> vec; //存储中序遍历的数组
void traversal(TreeNode* node)
{//中序遍历:左中右
if(!node)
return;
traversal(node->left);
vec.push_back(node->val);
traversal(node->right);
}
int getMinimumDifference(TreeNode* root) {
int res=INT_MAX; //初始化一定是最大值,而不是初始化为0,因为求解的是min
if(!root)
return 0;
traversal(root); //获取中序遍历的数组
for(int i=1;i<vec.size();++i)
{//中序遍历数组元素一定是从小到大,且不存在重复
int diff=abs(vec[i-1]-vec[i]); //当前节点与前一个节点的差的绝对值
res=res<diff?res:diff;
}
return res;
}
2、无需构造有序数组,遍历时完成相关操作
在中序遍历过程中就进行相邻节点元素的比较,中序遍历的顺序是左中右,具体而言就是在对【中】处理时进行相邻元素的比较等操作,保留最小差值。
注意:主函数需返回的最小绝对差值声明为全局变量,这样在调用递归函数时实现对主函数返回值的操作;此外前一个节点也需声明为全局变量(该变量为递归函数的局部变量是否可行?)
具体实现有中序遍历的递归法和借助栈实现的中序遍历迭代法,其中对【中】的处理操作都是相同的!
int res=INT_MAX; //记录最终返回的最小绝对差值
TreeNode* pre=nullptr; //记录前一个节点
void traversal(TreeNode* node)
{//中序遍历:左中右,从小到大
if(!node)
return;
traversal(node->left); //左
if(pre)
{//pre不为空则比较当前节点与前一个节点的差值,并保留最小差值
int diff=node->val-pre->val;
res=res<diff ? res:diff;
}
pre=node; //pre为空则赋值为当前节点node
traversal(node->right); //右
}
int getMinimumDifference(TreeNode* root) {
//中序遍历的迭代法:借助栈
stack<TreeNode*> stk;
int res=INT_MAX;
TreeNode* pre=nullptr; //记录前一个节点;
TreeNode* cur=root; //记录当前遍历节点
while(cur || !stk.empty())
{
if(cur)
{//cur不为空,进行访问,即进行入栈操作,顺序是左中右,先遍历左
stk.push(cur);
cur=cur->left; //左
}
else
{//cur为空,表示【左】遍历结束,需取出栈顶元素进行比较操作,之后再遍历右
cur=stk.top();
stk.pop();
if(pre)
{//【中】
int diff=cur->val - pre->val;
res=res<diff ? res: diff;
}
pre=cur; //pre为空表示前一个节点无元素,需更新为当前节点
cur=cur->right; //右
}
}
return res;
/*
//递归法:中序遍历,无需存储中序遍历数组,只需在遍历过程中进行比较
traversal(root); //获取中序遍历的数组
return res;
*/
}
二、501 二叉搜索树中的众数
题目链接:501 二叉搜索树中的众数
核心:其一是含重复值的二叉搜索树如何统计各节点值的频数,并保留最大频数的节点;其二是含重复值的二叉搜索树可能存在多个众数,即多个最大频数相同的节点,如何处理这些相同频数的节点。
1. 递归法:
参考530的法2实现,即在中序遍历无需构造有序数组,直接在遍历过程中比较相邻元素实现频数的统计,相邻元素不等则重新开始统计,相等则累加频数;
得到统计的频数后,若与当前的最大频数相同,则保留相应节点,解决多个众数的问题;另外若大于当前的最大频数,则更新最大频数,并重新记录最大频数对应的节点。
int maxCnt=0; //最大频数
int count=0; //当前节点的频数
TreeNode* pre=nullptr; //记录前一个节点
vector<int> res; //记录最大频数的对应节点值
void searchBST(TreeNode* node)
{//中序遍历:左中右
if(!node) return; //空节点则无需递归遍历,直接return
searchBST(node->left); //左
//【中】处理操作
if(!pre)
count=1; //第一个节点
else if(pre->val == node->val)
count++; //当前节点与前一个节点值相同
else
count=1; //当前节点与前一个节点值不同
pre =node; //遍历一个节点后需更新前一个节点
if(count==maxCnt)
res.push_back(node->val); //相同频数的节点不止一个,都需要记录
if(count>maxCnt)
{//更新最大频数,及相应节点值
maxCnt=count;
res.clear(); //更新节点值之前一定将之前的节点删除,即清空
res.push_back(node->val);
}
searchBST(node->right); //右
}
vector<int> findMode(TreeNode* root) {
count=0;
maxCnt=0;
TreeNode* pre=nullptr;
res.clear();
searchBST(root);
return res;
}
2. 迭代法:
借助栈实现中序遍历,中序遍历是左中右,因此先遍历左子树,直到遍历完所有左子树的节点,然后对【中】进行处理,处理逻辑与递归法相同,最后遍历右子树所有节点。
vector<int> findMode(TreeNode* root) {
//中序遍历的迭代法:借助栈
stack<TreeNode*> stk;
TreeNode* pre=nullptr;
TreeNode* cur=root;
int maxCnt=0; //记录最大频数
int count=0; //统计当前节点的频数
vector<int> res;
while(cur || !stk.empty())
{
if(cur)
{//cur不为空,则继续访问树,直到最底层(左)
stk.push(cur);
cur=cur->left; //左
}
else
{//cur为空表示已遍历完所有【左】节点,可以对【中】进行处理
cur=stk.top();
stk.pop();
//前一个节点与当前节点的三种情况:
if(!pre)
count=1; //pre为空说明为第一个节点
else if(pre->val == cur->val)
count++;
else
count=1;
//统计完当前节点的频数后需要与maxCnt比较,两种情况:
//1.当前节点频数也为最大频数,即满足众数条件,需记录
if(count==maxCnt)
res.push_back(cur->val);
//2. 当前节点频数大于最大频数,需更新最大频数,且更新最大频数对应的节点
if(count>maxCnt)
{
maxCnt=count;
res.clear(); //注意:一定要清空!
res.push_back(cur->val);
}
pre=cur; //每处理完一个节点需要更新前一个节点
cur=cur->right; //右
}
}
return res;
}
三、236 二叉树的最近公共祖先
题目链接:236 二叉树的最近公共祖先
核心:回溯思想(理解不够深入),利用递归的后序遍历实现回溯。
从左、右子树中分别寻找两个节点p、q,根据遍历结果再对【中】处理,这是需要回溯的,也是典型的后序遍历的使用;显然,该题需要对整棵树进行遍历,而不是只对左子树或右子树进行遍历,因此需要根据左子树和右子树的遍历结果判断是否存在最近公共祖先,以及最近公共祖先是哪个节点?
对【中】处理时存在以下情况:
- left和right都不为空:返回当前root即可,因为此时返回的root已经是回溯后的节点(理解不深);
- left为空,right不为空:说明找到的最近公共祖先节点在右子树,即返回right;
- left不为空,right为空:说明找到的最近公共祖先节点在左子树,即返回left;
- left和right都为空:说明未找到最近公共祖先节点,返回left或right均可,两者都是空。
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
//使用回溯,后序遍历是典型的回溯方法,左右中,即先遍历左和右分别返回,再对中处理
if(!root || root == p || root == q)
return root; //root为空,或root与p/q相等,都直接返回root即可
TreeNode* left=lowestCommonAncestor(root->left,p,q); //左
TreeNode* right=lowestCommonAncestor(root->right,p,q); //右
//对【中】处理
if(left && right)
return root; //left和right都不为空时说明已在左、右子树找到p、q,因此返回当前root
else if(!left && right)
return right;
else if(left && !right)
return left;
else
return left; //left和right都是空,实际返回空
}