这篇文章主要是讲前序遍历的递归和迭代写法,以及一点思考过程。更多的作为自己的学习记录。
原题描述
144. 二叉树的前序遍历
给定一个二叉树,返回它的 前序 遍历。
示例:
输入: [1,null,2,3]
1
2
/
3
输出: [1,2,3]
递归解法
递归方式可以解决大部分树相关的问题。而写不出递归方式是因为没有彻底掌握其模板。
我们把递归的过程可以理解为一个while循环的过程,就很好理解了。
- 确定递归函数的参数和返回值
确定哪些参数是递归的过程中需要处理的,那么就在递归函数里加上这个参数,并且还有明确每次递归的返回值是什么,进而确定递归函数的返回类型。可以理解为每次执行while循环语句中需要进行变量值的更新。
- 确定终止条件
递归可以理解为一个不断循环的过程,因此就需要终止的条件,如果没有,那么最终操作栈就会溢出。就像while循环一样,需要有一个条件来终止循环。
- 确定单层递归的逻辑
确定每一层递归需要处理的信息,也就是重复调用来实现递归的过程。可以理解为while循环中不断执行的语句。这一层做的操作可以理解为赋值和递归调用方法,有的时候这两个操作可以合成为一个,只需要递归调用方法。
这一步骤的逻辑语句的推理往往可以先从左右子树开始分析,看能否得出推理结论,再运用同样的结论到其各自的左右子树,如果如此递归的方式能够推理成立,那么说明我们就得到了这个正确的结论。
以先序遍历为例:
1.确定递归函数的参数和返回值:这道题中我们可以用题目中自带的返回值类型,即List<Integer>,通过定义一个全局变量来作为返回值。递归参数里就需要传入节点用来在数组中存放节点的数值。
List<Integer> list = new ArrayList();
public List<Integer> preorderTraversal(TreeNode root) {
return list;
}
2.确定中止条件:遍历到节点为空的时候,递归就要结束了
3.确定单层的递归逻辑:先序遍历的顺序为根节点,左孩子,右孩子。
在单个递归层级内,我们只关注单个节点的问题。
list.add(root.val);
preorderTraversal(root.left);
preorderTraversal(root.right);
迭代写法
递归的时候系统隐式的帮我们维护了一个栈。所以迭代的写法就是用栈来实现递归的过程。
这道题中我用的是LinkedList,因为它的增删的效率更高,其底层是用链表来实现的;而Stack底层是用数组来实现的。
首先,不管是哪种写法,我们要牢记先序遍历的特点是:Top->Bottom,Left->Right.因此借助栈的时候。即以深度作为优先级,从根节点一直到达某个确定的叶子,然后再返回到跟到达另一个分支。
为了保证Top->Bottom的顺序,在迭代过程从根节点开始,我们每次只取一个节点,然后将其孩子节点入栈,下次迭代的时候将左孩子出栈,这样可以保证Top->Bottom的顺序。而因为每次入栈的顺序是插入到尾部,并且为了保证出栈的时候是先取出左孩子的特点,需要先入栈右孩子,再入栈左孩子。
class Solution {
List<Integer> result = new ArrayList();
LinkedList<TreeNode> list = new LinkedList();
public List<Integer> preorderTraversal(TreeNode root) {
if(root == null){
return new ArrayList();
}
list.add(root);
while(!list.isEmpty()){
TreeNode node = list.pollLast();
result.add(node.val);
if(node.right != null){
list.add(node.right);
}
if(node.left != null){
list.add(node.left);
}
}
return result;
}
}
小结
我们刷题本质上是提取共通的思路,这一点在很多题解中都会有总结,这是结构化的思维。另一方面我觉得是养成图形化思维能力,将树的问题的求解过程转变利用栈或者迭代过程去实现的图形化的思维。像这些题目在写的时候都可以用画图的形式将过程表现出来有助于我们形成图形化思维的能力。