!!!!!二叉搜索树一定要想到中序遍历!!!!!
530、二叉搜索树的最小绝对差
初始思路:
根据二叉搜索树的特性,考虑数中任意两不同节点值之间的最小差值一定发生在相邻的两元素间,所以考虑将二叉搜索树装入数组中,然后遍历数组中的相邻元素之差,找到最小的。其中装入数组时一定要采取中序,这样才能保证数组是一个有序数组!
class Solution {
private int min = Integer.MAX_VALUE;
public int getMinimumDifference(TreeNode root) {
List<Integer> res = new ArrayList<>();
getMinimum(root,res);
for(int i = 1;i<res.size();i++){
if(res.get(i)-res.get(i-1)<min){min=res.get(i)-res.get(i-1);}
}
return min;
}
public void getMinimum(TreeNode root,List<Integer> list){
if(root==null){return;}
getMinimum(root.left,list);
list.add(root.val);
getMinimum(root.right,list);
}
}
此处写的比较随意,getMinimum就是一个中序遍历。
题解复盘 :
居然还可以用双指针!!!
主要思路就是在中序遍历时完成最小值的计算
class Solution {
TreeNode pre;// 记录上一个遍历的结点
int result = Integer.MAX_VALUE;
public int getMinimumDifference(TreeNode root) {
if(root==null)return 0;
traversal(root);
return result;
}
public void traversal(TreeNode root){
if(root==null)return;
//左
traversal(root.left);
//中
if(pre!=null){
result = Math.min(result,root.val-pre.val);
}
pre = root;
//右
traversal(root.right);
}
}
定义一个全局变量pre记录前一个指针,定义一个全局变量计算最小的result;
在中序遍历中只需要注意的点是如何保证pre一直指向cur的前一个点只需要每次更新时让pre=cur即可。
501、二叉搜索树的众数
初始思路:
暴力一定是把这个树都遍历了,用map统计频率,用优先级队列把频率排个序,最后取前面高频的元素的集合,再转换为数组
没有写出来,有点太麻烦了,/(ㄒoㄒ)/~~
题解复盘:
这一段代码随想录一步步的讲的太精彩了
直接粘贴:
中序遍历代码如下:
void searchBST(TreeNode* cur) {
if (cur == NULL) return ;
searchBST(cur->left); // 左
(处理节点) // 中
searchBST(cur->right); // 右
return ;
}
遍历有序数组的元素出现频率,从头遍历,那么一定是相邻两个元素作比较,然后就把出现频率最高的元素输出就可以了。
弄一个指针指向前一个节点,这样每次cur(当前节点)才能和pre(前一个节点)作比较。
而且初始化的时候pre = NULL,这样当pre为NULL时候,我们就知道这是比较的第一个元素。
代码如下:
if (pre == NULL) { // 第一个节点
count = 1; // 频率为1
} else if (pre->val == cur->val) { // 与前一个节点数值相同
count++;
} else { // 与前一个节点数值不同
count = 1;
}
pre = cur; // 更新上一个节点
此时又有问题了,因为要求最大频率的元素集合(注意是集合,不是一个元素,可以有多个众数),如果是数组上大家一般怎么办?
应该是先遍历一遍数组,找出最大频率(maxCount),然后再重新遍历一遍数组把出现频率为maxCount的元素放进集合。(因为众数有多个)
这种方式遍历了两遍数组。
那么我们遍历两遍二叉搜索树,把众数集合算出来也是可以的。
但这里其实只需要遍历一次就可以找到所有的众数。
那么如何只遍历一遍呢?
如果 频率count 等于 maxCount(最大频率),当然要把这个元素加入到结果集中(以下代码为result数组),代码如下:
if (count == maxCount) { // 如果和最大值相同,放进result中
result.push_back(cur->val);
}
是不是感觉这里有问题,result怎么能轻易就把元素放进去了呢,万一,这个maxCount此时还不是真正最大频率呢。
所以下面要做如下操作:
频率count 大于 maxCount的时候,不仅要更新maxCount,而且要清空结果集(以下代码为result数组),因为结果集之前的元素都失效了。
if (count > maxCount) { // 如果计数大于最大值
maxCount = count; // 更新最大频率
result.clear(); // 很关键的一步,不要忘记清空result,之前result里的元素都失效了
result.push_back(cur->val);
}
双指针+智能更新数组
class Solution {
ArrayList<Integer> resList;
int maxCount;
int count;
TreeNode pre;
public int[] findMode(TreeNode root) {
resList = new ArrayList<>();
maxCount = 0;
count = 0;
pre = null;
findMode1(root);
int[] res = new int[resList.size()];
for (int i = 0; i < resList.size(); i++) {
res[i] = resList.get(i);
}
return res;
}
public void findMode1(TreeNode root) {
if (root == null) {
return;
}
findMode1(root.left);
int rootValue = root.val;
// 计数
if (pre == null || rootValue != pre.val) {
count = 1;
} else {
count++;
}
// 更新结果以及maxCount
if (count > maxCount) {
resList.clear();
resList.add(rootValue);
maxCount = count;
} else if (count == maxCount) {
resList.add(rootValue);
}
pre = root;
findMode1(root.right);
}
}
236、二叉树的最近公共祖先(涉及回溯所以后序!!!)
推荐先看视频我就先看视频了
题解复盘:
关键思想在于回溯
如果找到了那个节点就将该节点一直返回
递归三部曲:
- 确定递归函数返回值以及参数
输入当前节点和要查找的pq节点
如果找到p或q就把当前节点返回
- 确定终止条件
遇到空的话,因为树都是空了,所以返回空。
那么我们来说一说,如果 root == q,或者 root == p,说明找到 q p ,则将其返回,这个返回值,后面在中节点的处理过程中会用到,那么中节点的处理逻辑,下面讲解。
- 确定单层递归逻辑
搜索左,搜索右,
如果左为空右不为空返回右
如果左不为空右为空返回左
如果左右都不为空,返回根节点root;
值得注意的是 本题函数有返回值,是因为回溯的过程需要递归函数的返回值做判断,但本题我们依然要遍历树的所有节点。
我们在二叉树:递归函数究竟什么时候需要返回值,什么时候不要返回值? (opens new window)中说了 递归函数有返回值就是要遍历某一条边,但有返回值也要看如何处理返回值!
如果递归函数有返回值,如何区分要搜索一条边,还是搜索整个树呢?
搜索一条边的写法:
if (递归函数(root->left)) return ;
if (递归函数(root->right)) return ;
搜索整个树写法:
left = 递归函数(root->left); // 左
right = 递归函数(root->right); // 右
left与right的逻辑处理; // 中
在递归函数有返回值的情况下:如果要搜索一条边,递归函数返回值不为空的时候,立刻返回,如果搜索整个树,直接用一个变量left、right接住返回值,这个left、right后序还有逻辑处理的需要,也就是后序遍历中处理中间节点的逻辑(也是回溯)。
-
求最小公共祖先,需要从底向上遍历,那么二叉树,只能通过后序遍历(即:回溯)实现从底向上的遍历方式。
-
在回溯的过程中,必然要遍历整棵二叉树(注意遍历整颗二叉树和遍历单边的区别),即使已经找到结果了,依然要把其他节点遍历完,因为要使用递归函数的返回值(也就是代码中的left和right)做逻辑判断。
-
要理解如果返回值left为空,right不为空为什么要返回right,为什么可以用返回right传给上一层结果
class Solution { public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { if(root==null){return root;} if(root==p||root==q){return root;} TreeNode left = lowestCommonAncestor(root.left,p,q); TreeNode right = lowestCommonAncestor(root.right,p,q); if(left!=null&&right!=null){return root;} if(left==null&&right!=null){return right;} if(right==null&&left!=null){return left;} return null; } }
我个人需要注意的点
1)首先考虑到回溯问题所以需要使用后续
2)注意递归的结束条件
3)注意遍历整棵树和单边树的区别,遍历整颗树需要用值接住root.left和root.right的遍历结果
4) 在之前讲的中如果遍历整棵树是不需要返回值的,此处也是遍历了整棵树,但是因为后续需要对返回值进行操作所以此处有返回值
5)注意返回逻辑(看网站上的图会更好理解!)
要理解如果返回值left为空,right不为空为什么要返回right,为什么可以用返回right传给上一层结果
搜索左,搜索右,
如果左为空右不为空返回右
如果左不为空右为空返回左
如果左右都不为空,返回根节点root;