我开了一个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到n每一个数都可以作为根结点,所以肯定有一个循环
- 循环中每选择一个i,都会把[1,n]的数组划分成两段,每一段都有自己的若干棵二叉搜索树,把这两个集合做一个笛卡尔积(即两两组合),就能得到最后的答案
晕了吗?没有晕的话就可以直接看代码了;晕了的话,我们再说的详细一点:
- 选出根结点后应该先分别求解该根的左右子树集合,也就是根的左子树有若干种,它们组成左子树集合,根的右子树有若干种,它们组成右子树集合。
- 然后将左右子树相互配对,每一个左子树都与所有右子树匹配,每一个右子树都与所有的左子树匹配。然后将两个子树插在根结点上。
- 最后,把根结点放入链表中
现在大家都明白了这道题的思路,我们来看一下代码:
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循环是整道题的逻辑核心,用来组合左右的二叉搜索树。这一道递归题是不是比较难,至少没有之前那么好想。原因是因为它是自底向上求解的,而自顶向下的思路才是我们大部分人的第一思路。还有一点要注意的是,自底向上的求解,逻辑都是放在递归函数后面的,自顶向下则相反。
最后,各位同学再来想一下这个问题,就针对这一题,如果现在我们不需要给出所有的二叉搜索树,只需要给出二叉搜索树的数量,我们应该怎么做?我们当然可以在这个代码的基础上稍作改动,但是有更简单的方法吗?当然有!下期告诉你!
给个提示:卡塔兰数
关注公众号,更多算法知识点告诉你。