目录
一、做题心得
今天是代码随想录打卡的第18天,来到了二叉树章节的part6。今天的题目,依旧是同递归练习很大。果然,递归作为通往二叉树世界的钥匙,当真作用巨大。说说几天做题的感受吧,前两道和之前打卡的题有些许关联,再次应用到了二叉搜索树的性质--中序遍历的有序性,难度整体来说不大,第三题的话,个人感觉还是比较新的,没做出来,但看了题解之后还是蛮清晰的,运用到的思想我们其实也知道,但是不太容易用上。
好了,话不多说,直接开始今天的题目。
二、题目与题解
题目一:530.二叉搜索树的最小绝对差
题目链接
530. 二叉搜索树的最小绝对差 - 力扣(LeetCode)
给你一个二叉搜索树的根节点
root
,返回 树中任意两不同节点值之间的最小差值 。差值是一个正数,其数值等于两值之差的绝对值。
示例 1:
输入:root = [4,2,6,1,3] 输出:1示例 2:
输入:root = [1,0,48,null,null,12,49] 输出:1提示:
- 树中节点的数目范围是
[2, 104]
0 <= Node.val <= 105
题解:中序遍历(DFS)
这个题和昨天打卡的 98. 验证二叉搜索树 - 力扣(LeetCode)有一些相似,都是考察二叉搜索树的性质--通过中序遍历实现从小到大的排序,这个昨天已经有提到了,因为中序遍历按照左根右的顺序对节点进行遍历,而二叉搜索树节点的值恰好满足左节点<根节点<右节点。
这个题要求我们得到最小的绝对差,而绝对差的最小值肯定是在排完序后的相邻节点中出现,这时我们只需要不断比较记录下最小的值即可。实现比较相邻节点的关键就是先驱节点pre的设置。
代码如下:
class Solution {
public:
int ans = INT_MAX; //初始化结果为最大int型值(尽可能大)
TreeNode* pre = nullptr; //初始化pre节点为空,存储当前节点的前一个节点
void dfs(TreeNode* cur) { //中序遍历(递归实现),cur为当前节点
if (cur == nullptr) return;
dfs(cur->left); //左
if (pre != nullptr) { //中
ans = min(ans, cur->val - pre->val); //更新最小差值
}
pre = cur; //更新pre位置,往后移动一位(即移到了当前位置)
dfs(cur->right); //右
}
int getMinimumDifference(TreeNode* root) {
dfs(root);
return ans;
}
};
题目二:501.二叉搜索树中的众数
题目链接
给你一个含重复值的二叉搜索树(BST)的根节点
root
,找出并返回 BST 中的所有 众数(即,出现频率最高的元素)。如果树中有不止一个众数,可以按 任意顺序 返回。
假定 BST 满足如下定义:
- 结点左子树中所含节点的值 小于等于 当前节点的值
- 结点右子树中所含节点的值 大于等于 当前节点的值
- 左子树和右子树都是二叉搜索树
示例 1:
输入:root = [1,null,2,2] 输出:[2]示例 2:
输入:root = [0] 输出:[0]提示:
- 树中节点的数目在范围
[1, 104]
内-105 <= Node.val <= 105
题解1:层序遍历(BFS) + 哈希
这里是作用于所有二叉树的通解--其实哪种遍历方式都行,只是我更习惯层序遍历。
思路很简单,就是挨个遍历每个节点,然后用哈希表存储每个节点的值以及该值出现的次数。出现次数最多的值自然就是题目要求的众数,这时候只需要用一个数组存储结果即可。
代码如下:
class Solution {
public:
vector<int> findMode(TreeNode* root) {
unordered_map<int,int> hash; //哈希表:key存储节点的值(first),value存储为该节点值的节点的个数(second)
vector<int> ans; //存储结果
if (root == nullptr) return ans;
queue<TreeNode*> q;
q.push(root);
while (!q.empty()) { //层序遍历(BFS)
TreeNode* node = q.front();
q.pop();
hash[node->val]++;
if (node->left) q.push(node->left);
if (node->right) q.push(node->right);
}
int Max = 0; //记录众数(出现频率最高的节点值)出现的次数,初始化为0
for (auto i : hash) { //注意是这里用auto,因为hash的元素的特殊性
Max = max(Max,i.second);
}
for (auto i : hash) {
if(i.second == Max) //和众数出现的次数相同
ans.push_back(i.first); //添加众数到结果
}
return ans;
}
};
题解2:中序遍历(DFS)+ 哈希
由于这个题已经要求是二叉搜索树了,所以可以用上述中序遍历的解法。这样排序之后,重复的数字就会集中在一起,就不需要用哈希表来单独存储,效率更高。每次出现更大众数的时候,之前的众数清空。
代码如下:
class Solution {
public:
int max_cnt = 0; // 最大频次
int cnt = 0; // 当前元素出现频次
TreeNode* pre = nullptr; // 前驱节点,初始化为空(前驱节点是后续进行相邻元素大小判断的关键)
vector<int> ans; // 保存众数
void dfs(TreeNode* root) { // 中序遍历--作用相当于排序:从小到大排序
if (root == nullptr) return;
dfs(root->left); // 左
if (pre && pre->val == root->val)
cnt++; // 与前驱相同,频次++
else
cnt = 1; // 是没前驱的首个节点,或非重复元素,频次初始化
if (cnt == max_cnt) // 成为了众数之一 // 中
ans.push_back(root->val);
if (cnt > max_cnt) // 频次突破新高,成为众数
{
ans.clear(); // 关键:之前的众数无效
max_cnt = cnt;
ans.push_back(root->val);
}
pre = root; // 更新前驱
dfs(root->right); // 右
}
vector<int> findMode(TreeNode* root)
{
dfs(root);
return ans;
}
};
题目三:236. 二叉树的最近公共祖先
题目链接
236. 二叉树的最近公共祖先 - 力扣(LeetCode)
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
示例 1:
输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1 输出:3 解释:节点5
和节点1
的最近公共祖先是节点3 。
示例 2:
输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4 输出:5 解释:节点5
和节点4
的最近公共祖先是节点5 。
因为根据定义最近公共祖先节点可以为节点本身。示例 3:
输入:root = [1,2], p = 1, q = 2 输出:1提示:
- 树中节点数目在范围
[2, 105]
内。-109 <= Node.val <= 109
- 所有
Node.val
互不相同
。p != q
p
和q
均存在于给定的二叉树中。
题解:递归(后序遍历思想)
这道题其实不太好想到,由于我们想要找到最近公共祖先,自然就希望能够自底向上查找,这里我们就可以想到后序遍历的思想--以左右根的遍历顺序进行遍历。这样,我们就可以通过递归,实现子节点向父节点传递信息。
具体分三步走:
1.基本情况处理:
如果当前节点root是p或q,那么直接返回root,因为root本身就是最近公共祖先(或者就是目标节点之一)。
如果root是nullptr,说明已经遍历到了叶子节点的下一层,没有找到p和q,按照二叉树的性质,此时应该返回nullptr。
2.递归调用:
递归地在左子树中查找p和q的最近公共祖先,将结果保存在left中。
递归地在右子树中查找p和q的最近公共祖先,将结果保存在right中。
3.结果判断:
如果left和right都不为空,说明p和q分别位于当前节点root的左右两侧,因此root就是它们的最近公共祖先,返回root。
如果left为空而right不为空,说明p和q都位于右子树中(或者p/q之一就是right),因此整个树的最近公共祖先就是right,返回right。
如果left不为空而right为空,说明p和q都位于左子树中(或者p/q之一就是left),因此整个树的最近公共祖先就是left,返回left。
代码如下:
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if (root == p || root == q || root == nullptr) return root; //p,q有一个为根节点或者根节点为空时,最近公共祖先就是root
//递归:分别在左右子树中查找p和q的最近公共祖先,并将结果存放在left,right中
TreeNode* left = lowestCommonAncestor(root->left, p, q);
TreeNode* right = lowestCommonAncestor(root->right, p, q);
if (left != nullptr && right != nullptr) return root; //p,q一个在左子树,一个在右子树,此时最近公共祖先就是root
if (left == nullptr) return right; //p,q都在右子树中,这时最近公共祖先就是右子树的最近公共祖先
return left; //p,q都在左子树中
}
};
三、小结
今天的打卡到此也就结束了,又是不断递归的一天。后边还得多花点时间总结一下。最后,我是算法小白,但也希望终有所获。