Day 18 二叉树part06
530. 二叉搜索树的最小绝对差
二叉搜索树经常要用到其中序遍历是递增的这一性质,因此,这里经常会用到这种套路,即中序遍历,然后使用一个pre记录前一个访问的节点,pre初值设置为null即可。
对于这道题,使用dif存储已经遍历到的位置中最小的差值,当遇到更小的差值再去更新。总体上就是中序遍历,因此使用递归或迭代都是没有问题的。
class Solution {
int dif = Integer.MAX_VALUE;
TreeNode pre = null;
public int getMinimumDifference(TreeNode root) {
getD(root);
return dif;
}
public void getD(TreeNode root){
if(root.left != null){
getD(root.left); //左
}
int x = root.val; //中
if(pre != null){
int t_dif = x - pre.val >= 0 ? x - pre.val : pre.val - x;
dif = Math.min(t_dif, dif);
}
pre = root;
if(root.right != null){getD(root.right);} //右
}
}
501. 二叉搜索树中的众数
这道题尽管我自己实现出来了,但有点太麻烦了。先整体叙述下我的思路,受启发于239. 滑动窗口最大值,我考虑使用单调队列来存储已经遍历的部分中,频率最高的元素(元素值和频次)。
因为是搜索树,因此还是使用中序遍历。如何统计频率呢,使用tmp数组(tmp[0]代表数字,tmp[1]代表频次),每遍历一个节点,若其与前一个节点值相同,则频次加一;若与前一个节点不同,则tmp更新为新的数字的频次。每次访问完便向单调队列中插入tmp,单调队列可以保证只保留频率最高的元素。
最终只需要将单调队列中的数据转成int数组即可。
class Solution {
Integer[] tmp = new Integer[2];
TreeNode pre = null;
MyQueue queue;
public int[] findMode(TreeNode root) {
queue = new MyQueue();
find(root);
return queue.toArray();
}
public void find(TreeNode root){
if(root == null) return;
if(root.left != null) find(root.left);
if(pre == null || pre.val != root.val){
tmp[0] = root.val;
tmp[1] = 1;
}else if(root.val == pre.val){
tmp[1] += 1;
}
queue.push(new Integer[]{tmp[0], tmp[1]});
queue.print();
pre = root;
if(root.right!=null) find(root.right);
}
}
class MyQueue{
Queue<Integer[]> queue;
public MyQueue(){
this.queue = new LinkedList();
}
public int peek(){
if(this.queue.isEmpty()) return 0;
return this.queue.peek()[1];
}
public void push(Integer[] tmp){ //该数组第一个元素代表节点值,第二哥元素代表频率
if(tmp[1] > this.peek()){
queue.clear();
queue.offer(tmp);
}else if(tmp[1] == this.peek()){
queue.offer(tmp);
}
}
public int[] toArray(){
int[] res = new int[this.queue.size()];
int i = 0;
for(Integer[] tmp : this.queue){
res[i++] = tmp[0];
}
return res;
}
}
其实这道题官解的思路与我的思路并没有什么不同,只是我的实现方法过于复杂了,并不需要为此就设计一个单调队列。使用maxCount记录当前最大的频次即可,之后当出现count大于maxCount就把结果集合清空,再添加当前元素。(与我的有限队列中push的部分含义相同。
class Solution {
int count, num, maxCount = -1;
TreeNode pre = null;
ArrayList<Integer> ans = new ArrayList();
public int[] findMode(TreeNode root) {
find(root);
int[] ans_array = new int[ans.size()];
int i = 0;
for(Integer num: ans) ans_array[i++] = num;
return ans_array;
}
public void find(TreeNode root){
if(root == null) return;
if(root.left != null) find(root.left);
if(pre == null || pre.val != root.val){
num = root.val;
count = 1;
}else if(root.val == pre.val){
count++;
}
if(count == maxCount){
ans.add(num);
}else if(count > maxCount){
maxCount = count;
ans.clear();
ans.add(num);
}
pre = root;
if(root.right!=null) find(root.right);
}
}
236. 二叉树的最近公共祖先
这道题我自己写的这个方法,很容易理解,但是时间复杂度太高,先放在这说一下自己的思路吧。使用层序遍历,当每次遍历一个节点,判断他是不是pq的公共祖先,是则更新res,由于是层序遍历,一定可以保证最后一个遍历到的公共祖先是最近公共祖先。但这样反复去判断是否是祖先的过程复杂度太高,这段代码复杂度应该是O(n**2);
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
TreeNode res = root;
Queue<TreeNode> queue = new LinkedList();
queue.offer(root);
while(!queue.isEmpty()){
TreeNode tmp = queue.poll();
if(isAncestor(tmp, p) && isAncestor(tmp, q)) res = tmp;
if(tmp.left != null) queue.offer(tmp.left);
if(tmp.right != null) queue.offer(tmp.right);
}
return res;
}
public boolean isAncestor(TreeNode root, TreeNode p){
if(root == p) return true;
if(root == null) return false;
return isAncestor(root.left, p) || isAncestor(root.right, p);
}
}
题解中的递归写法真的是很难懂的,因为他和递归的模板并不完全相同,不能按照理解其他递归的方式去理解这道题的解法。
对于一般的递归,我们肯定认为递归去解决的子问题和原问题是相同的,只是更小了,是一个子问题。对于这个题解,如果我们还这么理解是行不通的。具体而言,我们要解决的问题是找到公共祖先,那么该函数返回的一定是pq的公共祖先才对,那为什么会出现left和right都不为null的情况,即公共祖先既出现在左子树又出现在右子树,显然这么理解是不正确的。
理解大佬们的代码,需要先把lowestCommonAncestor()当成是一个纯粹的二叉树后序遍历找节点函数(先忘记它是一个查找最近公共祖先的函数),目的是找到p或者q节点,然后根据返回结果来判断最近公共祖先。这样才能解释left和right都不为空的情况。(这段话引自题解下面的评论中)
但其实这样下来我还是无法想到怎么把他运用到其他的递归问题中去,只能当作一个特例先记住。
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if(root == null) return null;
if(root == p || root == q) return root;
TreeNode left = lowestCommonAncestor(root.left, p, q);
TreeNode right = lowestCommonAncestor(root.right, p, q);
if(left == null) return right;
if(right == null) return left;
return root;
}
}
还有一种思路比较好,但我写的代码实在太丑陋。遍历获取从root到p和从root到q的路径,取路径最后一个相同的节点就是最近公共祖先。我写的太拉了,今天也太晚了不想改了,就这样吧,累了,睡。
class Solution {
List<TreeNode> path = new ArrayList();
List<TreeNode> path_p, path_q;
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
getPath(root, q);
path_q = path_p;
path_p = null;
path = new ArrayList();
getPath(root, p);
System.out.println(path_q);
TreeNode ans = null;
for(int i = 0; i < Math.min(path_q.size(), path_p.size()); i++){
if(path_p.get(i) == path_q.get(i))
ans = path_p.get(i);
}
return ans;
}
public void getPath(TreeNode root, TreeNode p){
if(root == null) return;
path.add(root);
if(root == p) {path_p = new ArrayList(path); return; }
if(root.left != null){
getPath(root.left, p);
path.remove(path.size()-1);
}
if(root.right != null){
getPath(root.right, p);
path.remove(path.size()-1);
}
}
}

浙公网安备 33010602011771号