力扣题库144+94+145,利用迭代返回二叉树的前中后序遍历。
题目描述
题目都是一句话,主要就是突出使用迭代的方法,而不是我们常用的递归。
解题思路
只要是能用递归解决的问题都是可以用迭代来解决的。
递归说白了就是隐性地声明了一个栈(stack),同一个函数,前面还未处理完的先依次压进栈里面,一直到最后递归退出,开始从后往前回溯的时候,就是函数依次出栈的时候了。
将递归转换为迭代,如果可以从递归的最后往前捋就直接用一个循环就能达到目的,例如斐波那契数列的计算。像我们这里无法从递归退出的位置开始,只能从root着手,就必须要显性声明一个栈来完成上述操作了。
所以不管是前中后如何排序,整个过程都是入栈出栈的交替循环,一直到之后栈空为止。难点就在于在这个循环过程当中什么时候将节点的值放入最后的结果,又应该如何设置条件让循环能顺序进行下去。
下面由浅入深分别来看。
初始答案
前序遍历
先看前序遍历,先节点,再左子节点,最后右子节点。所以遇到一个节点,在入栈之前先将节点的值放入最终结果再说,然后因为右子节点是回溯的时候才会做操作的,所以将右子节点压入栈。同时将root转移到左子结点上,循环下去。一直到左子节点是None,不能再入栈了,开始出栈,将root转移到出栈的第一个元素上,重复前面的操作。一直到出栈的元素为None,则继续出栈。知道最后栈空为止。
class Solution:
def preorderTraversal(self, root: TreeNode) -> List[int]:
temp=[]
result=[]
while root or len(temp)>0:
while root:
result.append(root.val)
temp.append(root.right)
root=root.left
root=temp.pop(-1)
return result
外层的大循环控制着整体的入栈和出栈,内层的循环是入栈操作。当root为None的时候开始出栈,一直到最后temp为空返回结果。
中序遍历
再来看中序遍历,思路也差不多,只不过这里因为要先处理左子节点,所以遇到节点要先将节点入栈,root转移到左子节点,出栈的时候再将节点的值计入最后的的结果,同时root转移到右子节点。
class Solution:
def inorderTraversal(self, root: TreeNode) -> List[int]:
stack = []
result = []
while root or len(stack)>0:
while root:
stack.append(root)
root=root.left
root=stack.pop(-1)
result.append(root.val)
root=root.right
return result
后序遍历
后序遍历算是不太好想到的了,因为节点要最后处理,所以在出栈的时候不能马上处理,只是帮助获取到右子节点后还要再次入栈,等到下次出栈的时候就是用来获取节点的值了。因为一个节点会入栈出栈两次,所以要加一个标志位来区分。
class Solution:
def postorderTraversal(self, root: TreeNode) -> List[int]:
temp=[]
result=[]
while root or len(temp)>0:
while root:
temp.append([root,root.right])
root=root.left
item=temp.pop(-1)
if item[1]:
temp.append([item[0],None])
root=item[1]
else:
result.append(item[0].val)
return result
我这里直接将右子节点做为辅助标志位和节点一起做为单个元素入栈,如果发现标志位不是None,就将root转到标志位节点,否则就直接获取节点的值。
这种除了将真正的节点入栈外,还一起压入辅助元素的操作还是蛮常见的,例如栈的最小值中也可以用类似的方法去构造一个可以返回最小值的栈
class MinStack:
def __init__(self):
"""
initialize your data structure here.
"""
self.stack=[]
def push(self, x: int) -> None:
if self.stack:
if x<=self.stack[-1][1]:
self.stack.append([x,x])
else:
self.stack.append([x,self.stack[-1][1]])
else:
self.stack.append([x,x])
def pop(self) -> None:
self.stack.pop(-1)
def top(self) -> int:
return self.stack[-1][0]
def getMin(self) -> int:
return self.stack[-1][1]
将元素入栈的时候还顺便将当前的最小值也一起入栈,新的元素再入栈的时候和当前的最小元素比较即可。因为栈是FILO,所以关于已经入栈元素的特性都可以用类似的方法来实现。
回到二叉树的遍历,因为基本所有答案思路都一样,时间复杂度也都为O(n),所以处理时间的比较没有啥意义。
改进答案
值得一提的是,对于后续遍历,还有一种很巧妙的方式,就是依然利用前序遍历,不过是先右子节点再左子节点,最后将结果逆序排列即可。
注意事项
最后再将递归的方式也写下来,会很明显发现能用递归还是用递归,更好理解,代码也更简洁
前序
class Solution:
def preorderTraversal(self, root: TreeNode) -> List[int]:
def loop(root):
if not root:
return []
return [root.val]+loop(root.left)+loop(root.right)
return loop(root)
中序
class Solution:
def inorderTraversal(self, root: TreeNode) -> List[int]:
def loop(root):
if not root:
return []
return loop(root.left)+[root.val]+loop(root.right)
return loop(root)
后序
class Solution:
def postorderTraversal(self, root: TreeNode) -> List[int]:
def loop(root):
if not root:
return []
return loop(root.left)+loop(root.right)+[root.val]
return loop(root)
我是T型人小付,一位坚持终身学习的互联网从业者。喜欢我的博客欢迎在csdn上关注我,如果有问题欢迎在底下的评论区交流,谢谢。