代码随想录算法训练营第二十一天|530.二叉搜索树的最小绝对差、501.二叉搜索树中的众数、236. 二叉树的最近公共祖先

文章介绍了如何在二叉搜索树中找到任意两节点间的最小绝对差值,通过中序遍历和preNode辅助计算;同时讲述了如何在含重复值的二叉搜索树中找到众数,并提出一种只遍历一次就能找出所有众数的方法;最后讲解了二叉树最近公共祖先问题的解决方案,利用后序遍历回溯实现。
摘要由CSDN通过智能技术生成

530.二叉搜索树的最小绝对差

参考材料(代码随想录)

题目

力扣题目链接

给你一个二叉搜索树的根节点 root ,返回 树中任意两不同节点值之间的最小差值 。

差值是一个正数,其数值等于两值之差的绝对值。

思路

题目中要求在二叉搜索树上任意两节点的差的绝对值的最小值。

注意是二叉搜索树,二叉搜索树可是有序的。

遇到在二叉搜索树上求什么最值啊,差值之类的,就把它想成在一个有序数组上求最值,求差值,这样就简单多了。

递归

那么二叉搜索树采用中序遍历,其实就是一个有序数组。

在一个有序数组上求两个数最小差值,这是不是就是一道送分题了。

最直观的想法,就是把二叉搜索树转换成有序数组,然后遍历一遍数组,就统计出来最小差值了。其实在二叉搜素树中序遍历的过程中,用一个pre节点记录一下cur节点的前一个节点,我们就可以直接计算了。

如图:

代码如下(JavaScript)

var getMinimumDifference = function(root) {
  let res=Infinity
  let preNode=null
  //中序遍历
  const inorder=(curNode)=>{
    if(!curNode) return null
    inorder(curNode.left)   //左
     // 更新res
    if(preNode) res=Math.min(res,curNode.val-preNode.val)   //中
     // 记录前一个节点         
    preNode=curNode
    inorder(curNode.right)   //右
  }
  inorder(root)
  return res
};

   501.二叉搜索树中的众数

参考材料(代码随想录)

题目

力扣题目链接

给你一个含重复值的二叉搜索树(BST)的根节点 root ,找出并返回 BST 中的所有 众数(即,出现频率最高的元素)。

如果树中有不止一个众数,可以按 任意顺序 返回。

假定 BST 满足如下定义:

  • 结点左子树中所含节点的值 小于等于 当前节点的值
  • 结点右子树中所含节点的值 大于等于 当前节点的值
  • 左子树和右子树都是二叉搜索树

 

思路

二叉搜索树,既然是搜索树,它中序遍历就是有序的。

如图:

遍历有序数组的元素出现频率,从头遍历,那么一定是相邻两个元素作比较,然后就把出现频率最高的元素输出就可以了。

关键是在有序数组上的话,好搞,在树上怎么搞呢?

这就考察对树的操作了。

530.二叉搜索树的最小绝对差中我们就使用了pre指针和cur指针的技巧,这次又用上了。

弄一个指针指向前一个节点,这样每次cur(当前节点)才能和pre(前一个节点)作比较。

而且初始化的时候pre = NULL,这样当pre为NULL时候,我们就知道这是比较的第一个元素。

    if(preNode===null) count=1
    else if(preNode.val===curNode.val) count++
    else count=1
    preNode=curNode

此时又有问题了,因为要求最大频率的元素集合(注意是集合,不是一个元素,可以有多个众数),如果是数组上大家一般怎么办?

应该是先遍历一遍数组,找出最大频率(maxCount),然后再重新遍历一遍数组把出现频率为maxCount的元素放进集合。(因为众数有多个)

这种方式遍历了两遍数组。

那么我们遍历两遍二叉搜索树,把众数集合算出来也是可以的。

但这里其实只需要遍历一次就可以找到所有的众数。

那么如何只遍历一遍呢?

如果 频率count 等于 maxCount(最大频率),当然要把这个元素加入到结果集中(以下代码为result数组),代码如下:

 if(count===maxCount) res.push(curNode.val)

是不是感觉这里有问题,result怎么能轻易就把元素放进去了呢,万一,这个maxCount此时还不是真正最大频率呢。

所以下面要做如下操作:

频率count 大于 maxCount的时候,不仅要更新maxCount,而且要清空结果集(以下代码为result数组),因为结果集之前的元素都失效了。

if(count>maxCount){
      res=[]
      maxCount=count
      res.push(curNode.val)
}

代码如下(JavaScript)

var findMode = function(root) {
  // 不使用额外空间,使用中序遍历,设置出现最大次数初始值为0
  let count=0,maxCount=0
  let preNode=null
  let res=[]
  // 1.确定递归函数及函数参数
  const travelTree =(curNode)=>{
    // 2. 确定递归终止条件
    if(curNode===null) return
    travelTree(curNode.left)
    // 3. 单层递归逻辑
    if(preNode===null) count=1
    else if(preNode.val===curNode.val) count++
    else count=1
    preNode=curNode
    if(count===maxCount) res.push(curNode.val)
    if(count>maxCount){
      res=[]
      maxCount=count
      res.push(curNode.val)
    }
    travelTree(curNode.right)
  }
  travelTree(root)
  return res
};

 236. 二叉树的最近公共祖先

 参考材料(代码随想录)

题目

力扣题目链接

给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。

百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”

思路

遇到这个题目首先想的是要是能自底向上查找就好了,这样就可以找到公共祖先了。

那么二叉树如何可以自底向上查找呢?

回溯啊,二叉树回溯的过程就是从低到上。

后序遍历(左右中)就是天然的回溯过程,可以根据左右子树的返回值,来处理中节点的逻辑。

接下来就看如何判断一个节点是节点q和节点p的公共祖先呢。

首先最容易想到的一个情况:如果找到一个节点,发现左子树出现结点p,右子树出现节点q,或者 左子树出现结点q,右子树出现节点p,那么该节点就是节点p和q的最近公共祖先。 即情况一:

 

 

判断逻辑是 如果递归遍历遇到q,就将q返回,遇到p 就将p返回,那么如果 左右子树的返回值都不为空,说明此时的中节点,一定是q 和p 的最近祖先。

那么有录友可能疑惑,会不会左子树 遇到q 返回,右子树也遇到q返回,这样并没有找到 q 和p的最近祖先。

这么想的录友,要审题了,题目强调:二叉树节点数值是不重复的,而且一定存在 q 和 p

但是很多人容易忽略一个情况,就是节点本身p(q),它拥有一个子孙节点q(p)。 情况二:

递归三部曲 

  • 确定递归函数返回值以及参数

如果遇到p或者q,我们要返回最近公共节点,返回值不为空,就说明找到了q或者p。

  • 确定终止条件

遇到空的话,因为树都是空了,所以返回空。

  • 确定单层递归逻辑

值得注意的是 本题函数有返回值,是因为回溯的过程需要递归函数的返回值做判断,但本题我们依然要遍历树的所有节点。

代码如下(JavaScript)

var lowestCommonAncestor = function (root, p, q) {
  //使用递归方法
  //因为需要从底向上查找,所以后序遍历
  //1.确定递归函数的参数和返回值
  const travelTree = (root, p, q) => {
    //2.确定终止条件
    if (root === null || root === p || root === q) {
      return root
    }
    //3.确定单层递归逻辑
    let left = travelTree(root.left, p, q)
    let right = travelTree(root.right, p, q)
    if (left !== null && right !== null)return root
    if (left == null ) return right
    return left
  }
  return travelTree(root, p, q)
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值