前言
该系列文章为本人刷leetcode的记录,主要旨在分享刷题的思路及算法解析(尽可能的一题多解),另方便自己日后查阅回顾。代码的实现语言是python和go。
想进大厂免不了刷题,一起加油吧,小伙伴们!
题目
offer 第7题
链接: https://leetcode-cn.com/problems/zhong-jian-er-cha-shu-lcof/
题目内容
重建二叉树
输入某二叉树的前序遍历和中序遍历的结果,请构建该二叉树并返回其根节点。
假设输入的前序遍历和中序遍历的结果中都不含重复的数字。
示例 1:
Input: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]
Output: [3,9,20,null,null,15,7]
示例 2:
Input: preorder = [-1], inorder = [-1]
Output: [-1]
限制:
0 <= 节点个数 <= 5000
解题思路
代码实现
python
def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:
#方法1:递归
#看到二叉树先想递归
# def recur(preorder,inorder):
# #递归终止条件
# if not preorder or not inorder: return
# #先找根节点
# root_node = preorder.pop(0)
# #print(root_node)
# #构建树
# root=TreeNode(root_node)
# #划分出左右子树的的前序和中序遍历序列
# left_tree_preorder = preorder[:inorder.index(root_node)]
# #print(left_tree_preorder,inorder.index(root_node))
# left_tree_inorder = inorder[:inorder.index(root_node)]
# right_tree_preorder = preorder[inorder.index(root_node):]
# right_tree_inorder = inorder[inorder.index(root_node)+1:]
# #继续递归构建左右子树
# root.left = recur(left_tree_preorder,left_tree_inorder)
# root.right = recur(right_tree_preorder,right_tree_inorder)
# return root
# return recur(preorder,inorder)
#方法1:递归
# 减少空间复杂度;将上面每一次传给子递归程序的前序、中序序列的切片换为原始切片中的索引
def recur(pre_start,pre_end,in_start,in_end):
#设置迭代终止条件
if pre_start > pre_end or in_start >in_end: return
#创建树
root = TreeNode(preorder[pre_start])
#获取前序遍历的根节点在中序遍历中的索引位置
i = dic[preorder[pre_start]]
#print(i)
#使用左子树的节点数量做为划分的索引计算比较合理
size_tree_L = i - in_start
#进行递归构建左右子树
#在构建的时候注意;在哪个遍历的序列中就用哪个参数;不要混着用;
#(否则在某些没有左/右子树的情况下会出错)
root.left = recur(pre_start+1,pre_start+size_tree_L,in_start,i-1)
root.right = recur(pre_start+size_tree_L+1,pre_end,i+1,in_end)
return root
dic = {}
for i in range(len(inorder)):
dic[inorder[i]] = i
return recur(0,len(preorder)-1,0,len(inorder)-1)
"""
在利用迭代按照前序遍历去遍历二叉树时,遍历的过程先一直遍历所有的左子树的左节点,直到最左节点;
之后将所有左节点依次出栈,并在此过程中将其右节点入栈;最后所有直到根节点出栈;对右子树进行相同的操作.
反过来,在依据前序遍历序列去反构建二叉树时,实际上前序遍历的序列中分为根--左子树--右子树三部分;且左子树中的排列顺序也是按照上面所诉的过程;所以在此过程中,
对于所有的最左节点我们是能看到的;关键在于弄清什么时候开始进行转向,开始遍历左子树中的右节点;
而根据中序遍历:左子树--根--右子树; 其中左子树的排列也是:左节点--根节点--右节点;
所以在中序遍历中很可以看到左子树的右节点在什么时候开始出现;
事实上,前序遍历是从上到左下--左下... 而中序遍历是从最左下一直往上走,这中间如果没有遇见右节点的话,其过程刚好与前序遍历是相反的;
所以进行判断前序和中序遍历中什么时候相反的顺序刚好结束就代表右节点的出现.以此就可以确定前序遍历中的右节点
"""
#方法2:迭代
#根据前序和中序遍历构建二叉树
#这里是仿照官方解答的迭代思路写的;官方的思路实际上就是在带我们利用前序遍历的步骤来还原二叉树
# if not preorder: return None
# #利用栈来实现利用前序遍历的进行反构建二叉树
# #创建二叉树
# root = TreeNode(preorder[0])
# stack = [root]
# index = 0
# for i in range(1,len(preorder)):
# #判断入栈的元素是否已经是最左节点了;注意在前序遍历时在最左节点入栈之前是不需要执行出栈的
# node = stack[-1]
# #先确定最左节点在哪(最左节点对应中序遍历中的第一个节点)
# if node.val != inorder[index]:
# node.left = TreeNode(preorder[i])
# #所有的最左节点依次入栈
# stack.append(node.left)
# #如果最左节点入栈了;就要执行出栈来确定什么时候进行右节点的转弯了
# else:
# while stack and stack[-1].val==inorder[index]:
# #这里是在判断与前序遍历是相反的顺序结束的位置;就是代表右节点是该节点的右孩子;
# node=stack.pop()
# index +=1
# node.right = TreeNode(preorder[i])
# stack.append(node.right)
# return root
go
func bulidTree(preorder,inorder []int) *TreeNode{
方法1:递归
//if preorder == nil || inorder==nil {return nil}
//
//
//
//var recur func(prerder,inorder []int) *TreeNode
//
//recur = func(preorder,inorder []int) *TreeNode{
// //迭代终止条件
// if len(preorder)== 0 || len(inorder) ==0 {return nil}
//
// //使用hashmap构造中序遍历的查询表
// dic := make(map[int]int,len(inorder))
// for i:=0; i<len(inorder); i++{
// dic[inorder[i]]=i
// }
//
// //确定根节点
// root_node := preorder[0]
//
// root := &TreeNode{Val:root_node,Left:new(TreeNode),Right:new(TreeNode)}
//
// rnode_index := dic[root_node]
//
// //进行获取新的左右子树的前序和中序遍历序列
// tree_L_pre := preorder[1:rnode_index+1]
// tree_L_in := inorder[:rnode_index]
//
// tree_R_pre := preorder[rnode_index+1:]
// tree_R_in := inorder[rnode_index+1:]
//
// //进行递归
// root.Left = recur(tree_L_pre,tree_L_in)
// root.Right = recur(tree_R_pre,tree_R_in)
//
// return root
//
//}
//
//return recur(preorder,inorder)
//方法2:对方法1进行优化;之前传入的是[]int,以及hashmap在每次迭代中都会进行更新;会造成额外的空间开销;使用传入索引的方法进行优化
if len(preorder)==0 || len(inorder)==0 {return nil}
dic:=make(map[int]int,len(inorder))
for i:=0;i<len(inorder);i++{
dic[inorder[i]]=i
}
var recur func(pre_start,pre_end,in_start,in_end int) *TreeNode
recur = func(pre_start,pre_end,in_start,in_end int) *TreeNode{
//递归终止条件
if pre_start> pre_end || in_start > in_end{return nil}
root_node := preorder[pre_start]
root := &TreeNode{Val:root_node}
rnode_index := dic[root_node]
//这里还是用左子树节点数目作为参考更加明确;用索引会弄晕
size_tree_L := rnode_index - in_start
root.Left = recur(pre_start+1,pre_start+size_tree_L,in_start,rnode_index-1)
root.Right = recur(pre_start+size_tree_L+1,pre_end,rnode_index+1,in_end)
return root
}
return recur(0,len(preorder)-1,0,len(inorder)-1)
//
方法2:使用非递归的迭代解法
//if len(preorder)==0{return nil}
//
使用栈来模拟二叉树的前序遍历
//stack := list.New()
//root := &TreeNode{Val:preorder[0]}
//stack.PushFront(root)
//index :=0
//
//for i:=1; i<len(preorder); i++{
// node := stack.Front().Value.(*TreeNode)
// node_val := node.Val
//
//
// if node_val != inorder[index]{
// //没找到最左节点之前,一直执行左节点入栈
// node.Left = &TreeNode{Val:preorder[i]}
// stack.PushFront(node.Left)
// }else{
// for stack.Len()!=0 && stack.Front().Value.(*TreeNode).Val == inorder[index]{
// node = stack.Remove(stack.Front()).(*TreeNode)
// index++
// }
// //fmt.Println(root)
// node.Right = &TreeNode{Val:preorder[i]}
// stack.PushFront(node.Right)
//
// }
//
//
//
//
//}
//
//return root
}
总结
在利用二叉树的前序和中序遍历序列构建二叉树时,注意:对于普通二叉树而言,前序和中序都存在可以唯一确定一个二叉树;对二叉搜索树而言,前序和中序任一个存在都可以唯一确定一个二叉树;
在利用go写非递归算法时候,注意使用双端链表list.New()时(模拟栈也可以用切片[]**TreeNode类型,但切片类型在进行出栈入栈操作时候比较麻烦点,内存消耗也更多些),对于栈顶的元素如果想要取出来,需要使用stack.Front().Value.(*TreeNode); 双端队列中的元素Element结构体定义如下:
type Element struct {
// Next and previous pointers in the doubly-linked list of elements.
// To simplify the implementation, internally a list l is implemented
// as a ring, such that &l.root is both the next element of the last
// list element (l.Back()) and the previous element of the first list
// element (l.Front()).
next, prev *Element
// The list to which this element belongs.
list *List
// The value stored with this element.
Value interface{}
}
next,prev表示前后元素的指向; Value表示当前元素的值信息(如果链表中存的元素时结构体,则值就是结构体的所有字段信息); 所以在取出Vaule信息后需要进行类型断言(将Interface接口类型转成原数据的类型);
注意这里也可以用反射reflect()进行取信息;
node := reflect.ValueOf(stack.Front.Value).ELem() ;
node_val := node.FieldByName(“Val”).Int()
这里因为二叉树的是结构体指针;反射取出的是地址ptr类型,加Elem()之后就是结构体了;但是不可以直接调用Val的字段属性;
反射这一块还不熟,这题留个坑,未来有时间再进行继续完善;