LeetCode高频题:二叉树(三)

我开了一个LeetCode会员,选择了一些高频率题目。这个系列是希望帮助大家每天花30分钟的题目了解面试高频题,让大家面试更加游刃有余。

本期我们讲解两道的中等题。上期我们说到有一题是将有序数组转换为二叉搜索树,不知道大家有没有印象,忘记了的同学可以看我们的上期推送。那道题说白了就是中序遍历的变种,其实二叉搜索树都或多或少跟中序遍历有关。今天我们的题也与二叉搜索树和中序遍历有关。本期所用的树的数据结构均如下:

class TreeNode {
    int val;
    TreeNode left; 
    TreeNode right;
    TreeNode(int x) { val = x; }
}

94. 二叉搜索树的中序遍历

题意:给定一棵树,返回它的中序遍历,如下图
在这里插入图片描述

这道题的递归形式是非常简单的,相信有前面两期的基础,各位同学都能一眼看出来,中序遍历就是把add的操作放在两个递归中间。而同样的,前序遍历就是把add的操作放在两个递归之前;后序遍历就是把add的操作放在两个递归之后。完整代码如下:

List<Integer> list=new ArrayList<>();
public List<Integer> inorderTraversal(TreeNode root) {
    if(root==null)
        return new ArrayList<>();
    inorderTraversal(root.left);
    list.add(root.val);
    inorderTraversal(root.right);
    return list;
}

但这样的难度是一道中等题该有的难度吗?NONONO!题目最下面还有一行小字:

Follow up: Recursive solution is trivial, could you do it iteratively?

翻译一下就是,递归太简单了,我们要用迭代做。那么如何用迭代来做呢?那我们当然要借助一个数据结构了。中序遍历的顺序是:左->中->右。所以内层的while循环找到的是最左边的结点,把沿路的结点都放进stack中。然后再依次pop出来,一个一个做处理。做什么样的处理呢?先把它放进list中,然后用同样的方法处理它的右子树。完整代码如下:

public List<Integer> inorderTraversal2(TreeNode root) {
    List<Integer> list = new ArrayList<>();
    Stack<TreeNode> stack = new Stack<>();
    TreeNode cur = root;
    while(cur!=null || !stack.empty()){
        while(cur!=null){
            stack.add(cur);
            cur = cur.left;
        }
        cur = stack.pop();
        list.add(cur.val);
        cur = cur.right;
    }
    return list;
}

看到这里我们不禁会想,为什么递归那么简单还要用迭代呢?那是因为迭代能用笔在纸上演算,按照算法的逻辑一步一步来,很容易找到bug。而递归很难演算,递归关系很容易搞混。还有一些情况下,迭代是比递归更容易的,所以两种方法我们都要掌握。这些迭代比递归更简单的题,我们会在下期告诉大家。

95. 不同的二叉搜索树

题意:给出一个整数n,返回从1到n构建的所有不同的二叉搜索树,如下图
在这里插入图片描述

这道题可以说是第一道我们介绍的中等难度的题,大家是不是感觉难度一下子上来了,不要着急,我们慢慢讲解。

首先想强调的一点,题意是二叉搜索树而不是二叉树,看到了这一点,这道题的题意也就了解了。其次我们要知道一棵二叉搜索树的子树依旧是二叉搜索树,明白这一点,我们就能把问题分解为子问题了。解这道题的思路大致如下:

  1. 从1到n每一个数都可以作为根结点,所以肯定有一个循环
  2. 循环中每选择一个i,都会把[1,n]的数组划分成两段,每一段都有自己的若干棵二叉搜索树,把这两个集合做一个笛卡尔积(即两两组合),就能得到最后的答案

晕了吗?没有晕的话就可以直接看代码了;晕了的话,我们再说的详细一点:

  1. 选出根结点后应该先分别求解该根的左右子树集合,也就是根的左子树有若干种,它们组成左子树集合,根的右子树有若干种,它们组成右子树集合。
  2. 然后将左右子树相互配对,每一个左子树都与所有右子树匹配,每一个右子树都与所有的左子树匹配。然后将两个子树插在根结点上。
  3. 最后,把根结点放入链表中

现在大家都明白了这道题的思路,我们来看一下代码:

public List<TreeNode> generateTrees(int n) {
    if(n==0)
        return new ArrayList<>();
    return help(1,n);
}
List<TreeNode> help(int start,int end){
    List<TreeNode> list=new ArrayList<>();
    if(start>end){
        list.add(null);//这个容易忘记,不加的话下面会空指针异常
        return list;
    }
    for(int i=start;i<=end;i++){
        List<TreeNode> left=help(start,i-1);
        List<TreeNode> right=help(i+1,end);
        for(TreeNode l:left){
            for(TreeNode r:right){
                TreeNode root=new TreeNode(i);
                root.left=l;
                root.right=r;
                list.add(root);
            }
        }
    }
    return list;
}

代码最后两个for循环是整道题的逻辑核心,用来组合左右的二叉搜索树。这一道递归题是不是比较难,至少没有之前那么好想。原因是因为它是自底向上求解的,而自顶向下的思路才是我们大部分人的第一思路。还有一点要注意的是,自底向上的求解,逻辑都是放在递归函数后面的,自顶向下则相反。

最后,各位同学再来想一下这个问题,就针对这一题,如果现在我们不需要给出所有的二叉搜索树,只需要给出二叉搜索树的数量,我们应该怎么做?我们当然可以在这个代码的基础上稍作改动,但是有更简单的方法吗?当然有!下期告诉你!

给个提示:卡塔兰数

关注公众号,更多算法知识点告诉你。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值