145.Binary Tree Postorder Traversal---二叉树后序非递归遍历
题目大意:后序遍历二叉树。
法一:普通递归,只是这里需要传入一个list来存储遍历结果。代码如下(耗时1ms):
1 public List<Integer> postorderTraversal(TreeNode root) { 2 List<Integer> list = new ArrayList<Integer>(); 3 list = dfs(root, list); 4 return list; 5 } 6 public static List<Integer> dfs(TreeNode root, List<Integer> list) { 7 if(root == null) { 8 return list; 9 } 10 else { 11 list = dfs(root.left, list); 12 list = dfs(root.right, list); 13 list.add(root.val); 14 return list; 15 } 16 }
法二(借鉴):后序遍历顺序是“左右根”,这里将其反过来遍历,也就是“根右左”,然后将遍历结果反转返回即可。这里用到了LinkedList.addFirst()方法,即将值插到链表头部。(addLast()方法与add()方法一样是插到链表尾部)。这里也可以用ArrayList.add(),在最后再调用Collections.reverse(list)方法即可。此方法代码简单,但不是很好想。代码如下(耗时1ms):
1 public List<Integer> postorderTraversal(TreeNode root) { 2 LinkedList<Integer> list = new LinkedList<Integer>(); 3 if(root == null) { 4 return list; 5 } 6 Stack<TreeNode> stack = new Stack<TreeNode>(); 7 stack.push(root); 8 TreeNode tmp = null; 9 while(!stack.isEmpty()) { 10 tmp = stack.pop(); 11 list.addFirst(tmp.val); 12 if(tmp.left != null) { 13 stack.push(tmp.left); 14 } 15 if(tmp.right != null) { 16 stack.push(tmp.right); 17 } 18 } 19 return list; 20 }
法三(借鉴):普通后序非递归遍历,这里用一个辅助栈来标记结点是否已经访问右结点,如果已经访问右结点,则将根值加入list中,否则访问右结点压栈。因为有两个栈要压栈出栈,耗时较长。也可以在TreeNode结点中加入一个标记属性flag来标记是否访问过右结点,这样就不需要辅助栈了,时间应该会快一些。代码如下(耗时2ms):
1 public List<Integer> postorderTraversal(TreeNode root) { 2 List<Integer> list = new ArrayList<Integer>(); 3 if(root == null) { 4 return list; 5 } 6 Stack<TreeNode> stackNode = new Stack<TreeNode>(); 7 //0表示右结点未访问,1表示右结点已访问 8 Stack<Integer> stackFlag = new Stack<Integer>(); 9 stackNode.push(root); 10 stackFlag.push(0); 11 TreeNode tmp = root.left;//已压栈,则访问其左结点 12 while(!stackNode.isEmpty()) { 13 while(tmp != null) { 14 stackNode.push(tmp); 15 stackFlag.push(0); 16 tmp = tmp.left; 17 } 18 if(stackFlag.peek() == 0) { 19 //右结点未访问,则访问右结点 20 tmp = stackNode.peek().right; 21 stackFlag.pop(); 22 stackFlag.push(1);//将访问右结点状态置1 23 } 24 else { 25 //右结点已访问,则将根结点加入list队列,并将根节点弹出 26 list.add(stackNode.pop().val); 27 stackFlag.pop();//弹出根节点状态值 28 } 29 } 30 return list; 31 }
法四(借鉴):保证根结点在左孩子和右孩子访问之后才能访问,因此对于任一结点P,先将其入栈。如果P不存在左孩子和右孩子,则可以直接访问它;或者P存 在左孩子或者右孩子,但是其左孩子和右孩子都已被访问过了,则同样可以直接访问该结点。若非上述两种情况,则将P的右孩子和左孩子依次入栈,这样就保证了 每次取栈顶元素的时候,左孩子在右孩子前面被访问,左孩子和右孩子都在根结点前面被访问。这个比法二还要难理解,特别是要先压right再压left。代码如下(耗时2ms):
1 public List<Integer> postorderTraversal(TreeNode root) { 2 List<Integer> list = new ArrayList<Integer>(); 3 if(root == null) { 4 return list; 5 } 6 Stack<TreeNode> stack = new Stack<TreeNode>(); 7 stack.push(root); 8 TreeNode pre = null, cur = null; 9 while(!stack.isEmpty()) { 10 cur = stack.peek();//判断当前结点情况,所以用peek不用pop 11 if((cur.left == null && cur.right == null) || 12 (pre != null && (pre == cur.left || pre == cur.right))) { 13 //如果当前结点没有左右孩子则直接弹出当前结点 14 //如果当前结点的左右孩子都已经访问完则弹出当前结点 15 list.add(cur.val); 16 pre = cur; 17 stack.pop(); 18 } 19 else { 20 //注意这里一定要先压right再压left,因为栈的先进后出的原则,到时候会先弹出left再弹出right,这样的顺序才正确。 21 if(cur.right != null) { 22 stack.push(cur.right); 23 } 24 if(cur.left != null) { 25 stack.push(cur.left); 26 } 27 } 28 } 29 return list; 30 }
法五(借鉴):最接近先序、中序非递归遍历的方法,先压左结点再判断栈顶元素。代码如下(耗时2ms):
1 public List<Integer> postorderTraversal(TreeNode root) { 2 List<Integer> list = new ArrayList<Integer>(); 3 if(root == null) { 4 return list; 5 } 6 Stack<TreeNode> stack = new Stack<TreeNode>(); 7 stack.push(root); 8 TreeNode pre = null, cur = root.left; 9 while(!stack.isEmpty()) { 10 while(cur != null) { 11 stack.push(cur); 12 cur = cur.left; 13 } 14 //判断栈顶结点 15 cur = stack.peek(); 16 //判断是否访问栈顶结点 17 if(cur.right != null && pre != cur.right) { 18 //如果不是从右孩子返回,即还未访问右孩子,则访问 19 cur = cur.right; 20 } 21 else { 22 //如果没有右孩子或右孩子已经访问过,则直接弹出当前节点进行访问 23 list.add(cur.val); 24 stack.pop(); 25 //记录当前访问的结点 26 pre = cur; 27 //将当前结点赋空 28 cur = null; 29 } 30 } 31 return list; 32 }