树,最重要的就是对 递归 的使用!
注意return的使用
递归可以返回一个值
617. 合并二叉树
func mergeTrees(root1 *TreeNode, root2 *TreeNode) *TreeNode {
if root1 == nil {
return root2
}
if root2 == nil {
return root1
}
root := &TreeNode{Val:root1.Val + root2.Val}
root.Left = mergeTrees(root1.Left,root2.Left)
root.Right = mergeTrees(root1.Right,root2.Right)
return root
}
230.二叉搜索树中第k小的元素
法一.中序递归
时间复杂度:每个节点访问两次,是节点总数n的常数级,O(2n)= O(n)
空间复杂度:递归栈O(n)
var cnt int
var res int
func kthSmallest(root *TreeNode,k int) int {
cnt = 0
inorder(root,k)
return res
}
func inorder(root *TreeNode, k int) {
if root == nil {
return //函数无返回值,可直接return
}
inorder(root.Left,k)
cnt++
if cnt == k {
res = root.Val
}
inorder(root.Right,k)
}
法二.Morris
时间复杂度:O(n)
空间复杂度:O(1)
func kthSmallest(root *TreeNode,k int) int {
cnt := 0
node := root
for node != nil {
if node.Left == nil {
cnt++
if cnt == k {
return node.Val
}
node = node.Right
}else {
pre := node.Left
for pre.Right != nil && pre.Right != node {
pre = pre.Right
}
if pre.Right == nil {
pre.Right = node
node = node.Left
}else{ //pre.Right == node
cnt++
if cnt == k {
return node.Val
}
pre.Right = nil
node = node.Right
}
}
}
return -1
}
法san.二分查找
func kthSmallest(root *TreeNode,k int) int {
cnt := numofRoot(root.Left)
if cnt+1 < k {
return kthSmallest(root.Right,k-cnt-1) //注意此处的return
}else if cnt+1 > k {
return kthSmallest(root.Left,k)
}
return root.Val
}
func numofRoot(root *TreeNode) int {
if root == nil {
return 0
}
return numofRoot(root.Left) + numofRoot(root.Right) + 1
}
2.深度优先搜索——递归=栈
108. 将有序数组转换为二叉搜索树
func sortedArrayToBST(nums []int) *TreeNode {
root := creat(nums,0,len(nums)-1)
return root
}
//自下而上递归
//可以以升序序列中的任一个元素作为根节点,以该元素左边的升序序列构建左子树,以该元素右边的升序序列构建右子树,这样得到的树就是一棵二叉搜索树
//因为本题要求高度平衡,因此需要选择升序序列的中间元素作为根节点
func creat(nums []int,l,r int) *TreeNode {
if r < l {
return nil
}
mid := l + (r - l) >> 1
node := &TreeNode{Val:nums[mid]}
node.Left = creat(nums,l,mid-1)
node.Right = creat(nums,mid+1,r)
return node
}
- 有序链表转换二叉搜索树
法一.找链表中间点,用我的如下方法,需要注意对边界条件单独处理:
head=nil,head.Next=nil
func sortedListToBST(head *ListNode) *TreeNode {
if head == nil {
return nil
}
if head.Next == nil {
return &TreeNode{Val:head.Val}
}
return build(head,nil)
}
func build(l,r *ListNode) *TreeNode {
if l == r {
return nil
}
mid := getMedian(l,r)
root := &TreeNode{Val:mid.Val}
root.Left = build(l,mid)
root.Right = build(mid.Next,r)
return root
}
func getMedian(l,r *ListNode) *ListNode {
slow,fast := l,l
若有偶数个节点,选择偏左的作为根节点
for fast.Next != r && fast.Next.Next != r {
slow = slow.Next
fast = fast.Next.Next
}
return slow
}
法二找链表中间点,无需对边界情况作处理
func sortedListToBST(head *ListNode) *TreeNode {
return build(head,nil)
}
func build(l,r *ListNode) *TreeNode {
if l == r {
return nil
}
mid := getMedian(l,r)
root := &TreeNode{Val:mid.Val}
root.Left = build(l,mid)
root.Right = build(mid.Next,r)
return root
}
func getMedian(l,r *ListNode) *ListNode {
slow,fast := l,l
//若有偶数个节点,选择偏右的作为根节点
for fast != r && fast.Next != r {
slow = slow.Next
fast = fast.Next.Next
}
return slow
}
105.从前序与中序遍历序列构造树
func buildTree(preorder []int, inorder []int) *TreeNode {
if len(preorder) == 0 {
return nil
}
i := 0
for ; i < len(inorder); i++ {
if inorder[i] == preorder[0] {
break
}
}
root := &TreeNode{Val:preorder[0]}
root.Left = buildTree( preorder[1:i+1] , inorder[:i] )
root.Right = buildTree( preorder[i+1:] , inorder[i+1:] )
return root
}
- 二叉树的最近公共祖先
树:考虑递归,别复杂化,考虑简单形状!
func lowestCommonAncestor(root, p, q *TreeNode) *TreeNode {
//p q 在不同子树中,公共祖先必为root
//p q 在相同子树中,公共祖先为p或q
if root == nil {
return nil
}
if root.Val == p.Val || root.Val == q.Val {
return root
}
left := lowestCommonAncestor(root.Left,p,q) //找root的左子树中有无节点p/q
right := lowestCommonAncestor(root.Right,p,q) //找root的右子树中有无节点
if left != nil && right != nil {
return root
}
if right == nil {
return left
}
return right
}
1382.将二叉搜索树变平衡
3.广度优先搜索——队列
offer55-1. 二叉树的深度
func maxDepth(root *TreeNode) int {
if root == nil {
return 0
}
queue := []*TreeNode{}
queue = append(queue,root)
res := 0
for len(queue) != 0 {
tmp := []*TreeNode{}
for _,node := range queue {
if node.Right != nil {
tmp = append(tmp,node.Right)
}
if node.Left != nil {
tmp = append(tmp,node.Left)
}
}
res++
queue = tmp
}
return res
}
4.遍历
1)递归:
每个结点只需遍历一次,故时间复杂度为O(n)。n为节点数
最差情况下单只树高度=n,故递归调用栈的深度为O(n),所以空间复杂度为O(n)
平均空间复杂度=树的高度=O(logN)
2)非递归 栈:
时间复杂度为O(n)
空间复杂度为O(n)
3)莫里斯Morris:
时间复杂度为O(n)
空间复杂度为O(1)
O(1)空间进行遍历困难之处在于在遍历的子结点的时候如何重新返回其父节点?
在Morris遍历算法中,通过修改叶子结点的左右空指针来使其指向其前驱或者后继结点来实现的。
中序遍历:
1.某节点的左孩子为空,直接输出该节点,接着该进入右孩子遍历。
2.某节点的左孩子不为空,则该节点中序遍历的前驱节点=该节点左孩子的最右节点
//eg.
//树=[3,1,4,nil,2,nil,nil,5]
void inOrder3(TreeNode *root) {
if(root == NULL)
return;
TreeNode *pNode = root;
while(pNode != NULL) {
if(pNode->left == NULL) {
cout << pNode->val << endl;
pNode = pNode->right;
}
else {
TreeNode *pPre = pNode->left;
while(pPre->right != NULL && pPre->right != pNode) {
//在找前驱
pPre = pPre->right;
}
if(pPre->right == NULL) { //改叶子节点空指针指向前驱
pPre->right = pNode;
pNode = pNode->left;
}
else { //当第二次返回节点3时,输出3,把前驱结点的右孩子设置为空(恢复树的结构),将当前结点更新为当前结点的右孩子
pPre->right = NULL;
cout << pNode->val << endl;
pNode = pNode->right;
}
}
}
}
将前中后递归改为Morris见:https://blog.csdn.net/u013309870/article/details/61207136