算法修炼之路—【字符串】Leetcode 783 二叉搜索树节点最小距离

题目描述

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

示例1:

输入: root = [4, 2, 6, 1, 3, null, null]
输出: 1
解释: 注意,root是树节点对象(TreeNode object),而不是数组。给定的树可表示为下图:
在这里插入图片描述
最小的差值为1,它是节点1和节点2的差值,也是节点3和节点2的差值。

注意:

  1. 二叉树的大小范围在2100
  2. 二叉树总是有效的,每个节点的值都是整数,且不重复;
  3. 本题与530题相同;

思路分析

题目难度为简单 ,二叉搜索树的特点为节点坐边的数值均小于本节点值;右子树的值均大于本节点值,这里需要求解任意两节点的差的最小值,我们易知其最小值必出现在相邻节点,故这里直接思考:

  1. 根节点与左右节点的差值;
  2. 多个差值之间的比较,得出最小值;

这里需要的就是遍历树,一个节点最多存在两个可比较的值:root.val - root.left.val, root.right.val - root.val,题干中给出树的高度有限,故我们这里采用简单的递归解法,简单来说就是:

  1. 判断当前节点是否为空,若为空返回Integer.MAX_VALUE
  2. 判断当前节点的左右节点是否均不存在,若是返回Integer.MAX_VALUE;
  3. 返回 当前节点的左右节点差值较小值和左右子树的差值较小值 中的最小值,即为输出;

根据以上思考,我们给出核心的判断代码:

        if (root == null || (root.left == null && root.right == null)) {
            return Integer.MAX_VALUE;
        }
        
        int i1 = root.left == null ? Integer.MAX_VALUE : root.val - root.left.val;
        int i2 = root.right == null ? Integer.MAX_VALUE : root.right.val - root.val;
        
        int i3 = solutionRecursive(root.left);
        int i4 = solutionRecursive(root.right);
        
        return Math.min(Math.min(i1, i2), Math.min(i3, i4));

上面的写法是为了更好的理解,这里我们进一步简化代码则有:

解题代码

    public static int solutionRecursive(TreeNode root) {
        if (root == null || (root.left == null && root.right == null)) {
            return Integer.MAX_VALUE;
        }
        return Math.min(Math.min((root.left == null ? Integer.MAX_VALUE 
                                    : root.val - root.left.val), 
                                 (root.right == null ? Integer.MAX_VALUE 
                                    : root.right.val - root.val)),
                        Math.min(solutionRecursive(root.left), 
                                 solutionRecursive(root.right))
                        );

    }

复杂度分析

n为树的节点个数,h为树的高度:

时间复杂度: 这里对每个节点进行了一次访问,时间复杂度为O(n);
空间复杂度: 没有借助辅助容器,但是因是递归解法,内存中存在栈空间的使用,故空间复杂度为O(h)

小结

递归解法特点

在本博客类似的算法文章中提过,树这种数据类型特别适合递归求解,在现实业务场景中,树的出现更多的用在检索需求中,这就使得树的高度不会太高;数据库中甚至想要提高检索效率使用如B树、B+树此类的多叉树来减少I/O,从而提升系统效率和检索速度; 这样的也无需求催生下的树结构,不会太高,这样也为递归这样的算法求解方式提供了一定的适用条件和参考价值。

这里简单说一下适合递归的问题具有的特点:

  1. 总问题可以细分为若干个有限的小问题,且小问题之间并没有相干性;
  2. 总问题和小问题的求解方式是相同的;
  3. 存在递归终止条件
  4. 确保递归的函数栈空间不会无限增长或超出题目要求;
    其中,理论层面,保证前三条即可。

递归与迭代

这里举最简单的例子,今天实到多少人?大家收到信号之后赶忙一排站好:

  • 递归解法,首先是正向传话,之后是反向回答:就像是第一名同学问第二名,你后面还有几个人;第二名不知道,同样地需要问第三名… 直到最后一名,他知道自己是最后一名,所以倒数第二名问他的时候,直接回答"我后面没有人";此时会进行第二遍的反向群体传话,倒数第二名高速他前面的同学“我后面只有一个人”;同理,第二名会告诉第一名“我后面有n-2个人”,此时第一位同学知道了包括他在内,总人数为n,故最后直接说出,实到n人;
  • 迭代解法:直接就是从最后一名同学喊1,倒数第二名同学喊2,到了第一名同学喊n

递归解法是直接询问根本结果,但是根本结果依赖于下级信息;迭代是从基础抓起,为上级提供参考。

从代码实现角度,递归实现简单但是需要栈空间且随问题规模增大而增大,可能出现栈空间溢出而无法正常运行的情况;迭代一般需要辅助空间,且代码实现较递归解法更为复杂。当题目可通过这两种解法求解时,应均进行尝试。

Github源码

完整可运行文件请访问GitHub

展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 程序猿惹谁了 设计师: 上身试试
应支付0元
点击重新获取
扫码支付

支付成功即可阅读