數(shù)據(jù)結構基礎--二叉樹

本文主要作為自己的學習筆記官份,并不具備過多的指導意義妹萨。

目錄

  • 基本概念
  • 二叉樹的重點
  • 二叉樹的遍歷
  • 實現(xiàn)先序遍歷
  • 實現(xiàn)中序遍歷
  • 實現(xiàn)后序遍歷
  • 以每層換行的方式進行廣度遍歷
  • 二叉樹的序列化和反序列化
  • 前序遍歷的歸檔&&解歸檔
  • 廣度遍歷歸檔&&解歸檔
  • 二叉樹的子樹
  • 平衡二叉樹(AVL樹)
  • 搜索二叉樹
  • 滿二叉樹
  • 完全二叉樹
  • 后序節(jié)點與前驅(qū)節(jié)點
  • 二叉樹中兩節(jié)點間的距離
  • 參考資料

基本概念

  • 基本結構

本節(jié)點的值,左子節(jié)點朗伶,右子節(jié)點。(以及一個初始化賦值)

public class TreeNode {
  public var val: Int
  public var left: TreeNode?
  public var right: TreeNode?
  public init(_val: Int) {
    self.val = val
  }
}

二叉樹的重點

  • 能夠結合隊列艇潭,棧回还,鏈表裆泳,字符串等很多數(shù)據(jù)結構出題。
  • 基本遍歷方式:比如BFS(廣度)柠硕,DFS(深度)工禾。
  • 遞歸的使用

二叉樹的遍歷

先序,中序蝗柔,后序遍歷為最常見的樹的三種遍歷方式闻葵。這三種寫法相似,無非是遞歸的順序略有不同癣丧。

  • 先序遍歷

先序遍歷先從二叉樹的根開始槽畔,然后到左子樹,再到右子樹胁编。

遍歷的結果是:ABDCEF

  • 中序遍歷

中序遍歷先從左子樹開始厢钧,然后到根,再到右子樹嬉橙。


遍歷的結果是:DBAECF

  • 后續(xù)遍歷

后序遍歷先從左子樹開始早直,然后到右子樹,再到根憎夷。

遍歷的結果是:DBEFCA


實現(xiàn)先序遍歷

  • 遞歸

打印自己,然后先遍歷左節(jié)點再遍歷右節(jié)點

/// 先序遍歷--遞歸
///
/// - Parameter node: 遍歷節(jié)點
func preorderRecur(node: TreeNode?) {
    if node == nil {
        return
    }
    
    print(node!.val)//打印當前節(jié)點
    preorderRecur(node: node!.left)//遍歷左節(jié)點
    preorderRecur(node: node!.right)//遍歷右節(jié)點
}
  • 非遞歸

先嘗試將左元素入棧昧旨,若棧頂元素為空則將棧頂推出然后嘗試遍歷右節(jié)點拾给。直到棧為空則遍歷結束。

這里的棧用處是為了保存二叉樹的結構兔沃,以彌補二叉樹無法獲取父節(jié)點的結構特性蒋得。

/// 先序遍歷--while
///
/// - Parameter root: 根節(jié)點
/// - Returns: 遍歷結果數(shù)組
func preorderTraversals(root: TreeNode?) -> [Int] {
    var res = [Int]()
    var stack = [TreeNode]() //遍歷用的棧
    var node = root//遍歷的根節(jié)點
    
    while !stack.isEmpty || node != nil {
        if node != nil {
            res.append(node!.val)  //將當前節(jié)點的值記錄
            stack.append(node!) //將當前節(jié)點加入棧中
            node = node!.left //嘗試遍歷當前節(jié)點的左節(jié)點
        } else {
            let parentsNode = stack.removeLast() //取出當前節(jié)點的父節(jié)點
            node = parentsNode.right  //將棧頂節(jié)點推出,并嘗試遍歷其父元素的右節(jié)點乒疏。
        }
    }
    
    return res
}

  • 還有一種方式

這種方式純粹的利用棧的性質(zhì)额衙,每次彈出棧頂元素,并嘗試將其左右孩子入棧怕吴。

不過需要注意的是后入棧的為左孩子窍侧,以保證優(yōu)先遍歷左側。

func preorderTraversal(root: TreeNode?) -> [Int] {
    var res = [Int]()
    var stack = [TreeNode]() //遍歷用的棧
    var node = root//遍歷的根節(jié)點
    stack.append(root!)
    
    while !stack.isEmpty{

        res.append(stack.last!.val)
        node = stack.removeLast()
        if node!.right != nil {
            stack.append(node!.right!)
        }
        
        if node!.left != nil {
             stack.append(node!.left!)
        }
    }
    
    return res
}

實現(xiàn)中序遍歷

  • 遞歸
/// 中序遍歷--遞歸
///
/// - Parameter node: 遍歷節(jié)點
func inorderRecur(node: TreeNode?) {
    if node == nil {
        return
    }
    
    inorderRecur(node: node!.left)//遍歷左節(jié)
    print(node!.val)//打印當前節(jié)點
    inorderRecur(node: node!.right)//遍歷右節(jié)點
}
  • 非遞歸

與前序遍歷相同转绷,只是記錄的時間不一樣了

func inorderTraversal(root: TreeNode?) -> [Int] {
    var res = [Int]()
    var stack = [TreeNode]()
    var node = root
    
    while !stack.isEmpty || node != nil {
        if node != nil {
            stack.append(node!) //將當前節(jié)點依次入棧
            node = node!.left //嘗試遍歷左節(jié)點
        } else {
            let parentsNode = stack.removeLast() //取出當前節(jié)點的父節(jié)點
            res.append(parentsNode.val) //打印父節(jié)點
            node = parentsNode.right //嘗試遍歷右節(jié)點
        }
    }
    
    return res
}
  • 先序遍歷與中序遍歷的非遞歸實現(xiàn)都是嘗試分解左邊界的過程

實現(xiàn)后序遍歷

  • 遞歸
/// 后序遍歷--遞歸
///
/// - Parameter node: 遍歷節(jié)點
func posorderRecur(node: TreeNode?) {
    if node == nil {
        return
    }
    
    posorderRecur(node: node!.left)//嘗試遍歷左節(jié)
    posorderRecur(node: node!.right)//嘗試遍歷右節(jié)點
    print(node!.val)//打印當前節(jié)點
}
  • 非遞歸

用兩個棧來實現(xiàn)伟件。

第一個棧的處理順序為,自上而下议经,自右而左斧账。經(jīng)過第二個棧的逆序谴返,就變成了自下而上,自左而右咧织。

  • 另一種非遞歸

與之前兩種遍歷方式不同嗓袱,我們需要引入一個新的變量lastPrint來記錄最后一次打印的節(jié)點。以此判斷左习绢,右節(jié)點是否已經(jīng)被打印渠抹。

func posorderTraversal(root: TreeNode?) -> [Int] {
    if root == nil {
        return []
    }
    var res = [Int]()
    var stack = [TreeNode]()
    var node = root
    var lastPrint : TreeNode? //最后一次打印的節(jié)點
    stack.append(node!)


    while !stack.isEmpty{
        node = stack.last
        if node?.left != nil && node?.left != lastPrint && node?.right != lastPrint{
            stack.append((node?.left)!) //node的左子樹一定沒有打印完畢
        }else if node?.right != nil && node?.right != lastPrint {
            stack.append((node?.right)!)  //node的右子樹一定沒有打印完畢
        }else {
            //node的左右子樹全部打印完畢,尋找其父節(jié)點
            res.append(stack.last!.val)
            lastPrint = stack.removeLast()
        }
    }
    
    return res
}

以每層換行的方式進行廣度遍歷

層數(shù)變換的記錄毯炮,需要兩個變量逼肯。當前行最右節(jié)點(last)以及下一行最右(nlast)

  • 具體操作上

每次將新節(jié)點加入隊列時桃煎,將nlast更新成新節(jié)點篮幢。
當當前打印的節(jié)點等于last,執(zhí)行換行并將last更新到下一行nlast为迈。

  • 代碼實現(xiàn)
func BFSTraversal(root: TreeNode?) -> String {
    
    if root == nil {
        return ""
    }
    
    var res = ""
    var queue = [TreeNode]()
    var last = root
    var nlast = root
    queue.append(root!)
    
    while !queue.isEmpty {
        let node = queue.removeFirst() //將隊首節(jié)點出隊
        res += node.val.description + " " //打印隊首節(jié)點
        
        if node.left != nil { //嘗試將左節(jié)點入隊
            queue.append(node.left!)
            nlast = node.left!
        }
        
        if node.right != nil { //嘗試將右節(jié)點入隊
            queue.append(node.right!)
            nlast = node.right!
        }
        
        if node == last { //換行
            last = nlast
            res += "\n"
        }
    }
    
    return res
}

二叉樹的序列化和反序列化

  • 序列化方式
  1. 先序遍歷序列化
  2. 中序遍歷序列化
  3. 后序遍歷序列化
  4. 按層遍歷序列化
  • 一棵樹序列化的結果和反序列化生成的二叉樹都是唯一的
  • 序列化和遍歷二叉樹的區(qū)別
  1. 序列化時需要轉(zhuǎn)化成字符串三椿,所以每個節(jié)點之間需要用符號進行分割
  2. 序列化時需要記錄空節(jié)點,需要特殊符號進行記錄

舉個例子(用!分割葫辐,用#表空):

//序列化
5!12!20!#!#!22!#!#!17!21!#!#!23!#!33!40!#!#!
//遍歷
[5, 12, 20, 22, 17, 21, 23, 33, 40]
  • 反序列化

將序列化字符串轉(zhuǎn)化成數(shù)組(比如這里通過!分割)

//字符串
5!12!20!#!#!22!#!#!17!21!#!#!23!#!33!40!#!#!
//數(shù)組
["5", "12", "20", "#", "#", "22", "#", "#", "17", "21", "#", "#", "23", "#", "33", "40", "#", "#"]

前序遍歷的歸檔&&解歸檔

  • 歸檔
/// 先序遍歷歸檔--遞歸
///
/// - Parameter node: 遍歷節(jié)點
func preorderRecurArchive(node: TreeNode?) -> String {
    if node == nil {
        return "#!"
    }
    
    var res = (node?.val.description)! + "!"
    res += preorderRecurArchive(node: node!.left)//遍歷左節(jié)點
    res += preorderRecurArchive(node: node!.right)//遍歷右節(jié)點
    
    return res
}


/// 先序遍歷格式化--while
///
/// - Parameter root: 根節(jié)點
/// - Returns: 序列化字符串
func preorderArchive(root: TreeNode?) -> String {
    var res = ""
    var stack = [TreeNode]() //遍歷用的棧
    var node = root//遍歷的根節(jié)點
    
    while !stack.isEmpty || node != nil {
        if node != nil {
            res += node!.val.description + "!" //將當前節(jié)點的值記錄
            stack.append(node!) //將當前節(jié)點加入棧中
            node = node!.left //嘗試遍歷當前節(jié)點的左節(jié)點
        } else {
            let parentsNode = stack.removeLast() //取出當前節(jié)點的父節(jié)點
            node = parentsNode.right  //將棧頂節(jié)點推出搜锰,并嘗試遍歷其父元素的右節(jié)點。
            res += "#!" //記錄空節(jié)點
        }
    }
    res += "#!" //記錄空節(jié)點
    return res
}

  • 解歸檔
遞歸
/// 前序遍歷解歸檔--遞歸
///
/// - Parameter str: 歸檔字符串
/// - Returns: 頭節(jié)點
func preorderRecurRearchive(str: String?) -> TreeNode? {
    var treeQueue = (str?.components(separatedBy: "!"))!
    treeQueue.removeLast() //這里切割完畢之后最后數(shù)組的最后一位為""
    
    return preorderRecurRearchiveProcess(treeQueue: &treeQueue)
    
}


/// 根據(jù)前序隊列進行二叉樹重構
///
/// - Parameter treeQueue: 節(jié)點隊列
/// - Returns: 頭節(jié)點
func preorderRecurRearchiveProcess(treeQueue : inout [String]) -> TreeNode? {
    let value = treeQueue.removeFirst()
    if value == "#" { //頭節(jié)點為空
        return nil
    }
    
    let root = TreeNode.init(_val: Int(value)!) //設置根節(jié)點
    root.left = preorderRecurRearchiveProcess(treeQueue: &treeQueue) //設置左節(jié)點
    root.right = preorderRecurRearchiveProcess(treeQueue: &treeQueue) //設置右節(jié)點
    
    return root
}
非遞歸

與遍歷時不同耿战,我們無法通過節(jié)點是否為nil判斷該構建哪一個子節(jié)點蛋叼。

所以我們需要引入一個變量setleft來確定下一次需要構建的節(jié)點方向。

需要注意的是:

每次構建新節(jié)點之后剂陡,下一次都會嘗試構建其左側節(jié)點狈涮。
而每次遇到空節(jié)點后,都會將頂元素推出鸭栖,并嘗試構建其的右側節(jié)點歌馍。

/// 前序遍歷解歸檔
///
/// - Parameter str: 歸檔字符串
/// - Returns: 頭節(jié)點
func preorderRearchive(str: String?) -> TreeNode? {
    var treeQueue = (str?.components(separatedBy: "!"))!
    treeQueue.removeLast() //這里切割完畢之后最后數(shù)組的最后一位為""

    var stack = [TreeNode]() //遍歷用的棧
    var node : TreeNode //當前操作的節(jié)點
    
    if treeQueue.isEmpty || treeQueue.first == "#" { //頭節(jié)點為空
        return nil
    }

    let root = TreeNode.init(_val: Int(treeQueue.removeFirst())!) //設置root節(jié)點
    node = root//將頭節(jié)點記錄為當前操作的節(jié)點
    stack.append(root) //將頭節(jié)點記錄
    var setleft = true //記錄當前需要構建的節(jié)點方向
    
    while !treeQueue.isEmpty {
        let value = treeQueue.removeFirst() //將隊列首元素推出
        if value != "#" { //若當前節(jié)點不為空
            let newNode = TreeNode.init(_val: Int(value)!) //獲得新的節(jié)點
            //與當前節(jié)點相連
            if setleft {
                node.left = newNode
            }else {
                node.right = newNode
            }
            node = newNode //記錄當前節(jié)點
            stack.append(node) //記錄當前層級
            setleft = true //下一次,嘗試構建左節(jié)點
            
        }else {
            if treeQueue.isEmpty {
                return root //如果已經(jīng)遍歷完成
            }else {
                node = stack.removeLast() //嘗試構建上層
            }
            setleft = false //下一次晕鹊,嘗試構建右節(jié)點
        }
    }

    return root //返回頭節(jié)點
}

廣度遍歷歸檔&&解歸檔

廣度遍歷的歸檔&&解歸檔比深度遍歷容易理解的多松却。

因為他的隊列,只負責記錄下一次想要處理的節(jié)點溅话。
并不需要在意左右與層級倒退晓锻,只需要處理節(jié)點為空的情況即可。

  • 歸檔
/// 廣度遍歷歸檔
///
/// - Parameter root: 頭節(jié)點
/// - Returns: 歸檔字符串
func BFSArchive(root: TreeNode?) -> String {
    
    if root == nil {
        return ""
    }
    
    var res = ""
    var queue = [TreeNode]()
    queue.append(root!)
    
    res += root!.val.description + "!"
    
    while !queue.isEmpty {
        let node = queue.removeFirst() //將當前節(jié)點出隊
        
        if node.left != nil { //嘗試將左節(jié)點入隊
            queue.append(node.left!)
            
            res += node.left!.val.description + "!" //打印當前節(jié)點
        }else {
            res += "#!" //打印當前節(jié)點
        }
        
        if node.right != nil { //嘗試將右節(jié)點入隊
            queue.append(node.right!)
            res += node.right!.val.description + "!" //打印當前節(jié)點
        }else {
            res += "#!" //打印當前節(jié)點
        }
    }
    
    return res
}
  • 解歸檔
/// 廣度遍歷解歸檔
///
/// - Parameter str: 歸檔字符串
/// - Returns: 頭節(jié)點
func BFSRearchive(str: String?) -> TreeNode?{
    
    var treeQueue = (str?.components(separatedBy: "!"))!
    var i = 0
    treeQueue.removeLast() //這里切割完畢之后最后數(shù)組的最后一位為""
    
    var queue = [TreeNode]()

    if treeQueue.isEmpty || treeQueue.first == "#" { //頭節(jié)點為空
        return nil
    }
    
    let root = TreeNode.init(_val: Int(treeQueue[i])!) //設置root節(jié)點
    i+=1
    queue.append(root)
    
    while !queue.isEmpty && i<treeQueue.count{
        let node = queue.removeFirst() //將當前節(jié)點出隊
        if treeQueue[i] != "#" { //嘗試構建左節(jié)點
            node.left = TreeNode.init(_val: Int(treeQueue[i])!)
        }
        i+=1
        if treeQueue[i] != "#" { //嘗試構建右節(jié)點
            node.right = TreeNode.init(_val: Int(treeQueue[i])!)
        }
        i+=1
        
        if node.left != nil { //嘗試將左節(jié)點入隊
            queue.append(node.left!)
        }
        if node.right != nil { //嘗試將右節(jié)點入隊
            queue.append(node.right!)
        }

    }
    return root
}

二叉樹的子樹

在二叉樹中以任何一個節(jié)點為頭部飞几,其下方的整棵樹作為二叉樹的子樹带射。

  • 子樹
  • 非子樹

平衡二叉樹(AVL樹)

  1. 空樹為平衡二叉樹
  2. 不為空的二叉樹。其中所有的子樹循狰,左右兩側高度差不超過1窟社。

如下圖中第三棵二叉樹券勺。
2節(jié)點的子樹下方,左側高度為2灿里,右側高度為0关炼。所以不是一個平衡二叉樹。

  • 判斷是否為平衡二叉樹

通過遞歸的方式判斷每個子樹是否為AVL樹

一旦一側子節(jié)點為空匣吊,另一側若高度大于2儒拂,則判定為否

/// 是否為平衡二叉樹
///
/// - Parameter root: 子樹頭節(jié)點
/// - Returns: 子樹是否平衡
func isBalance(root : TreeNode?) -> Bool {
    if root == nil { //空樹為AVL樹
        return true
    }
    
    let left = root?.left
    let right = root?.right
    if ((left?.left != nil) || (left?.right != nil)) && right == nil{
        return false  //左側比右側高2
    }
    if ((right?.left != nil) || (right?.right != nil)) && left == nil{
        return false  //右側比左側高2
    }
    
    //否則繼續(xù)判定子樹
    if isBalance(root: left) && isBalance(root: right) {
        return true
    }else {
        return false
    }
}

搜索二叉樹

又叫二叉查找樹,二叉排序樹
特征為色鸳,每個子樹的頭節(jié)點>左節(jié)點社痛,并且頭節(jié)點<右節(jié)點

二叉樹的中序排列,一定是一個有序數(shù)組命雀。反之亦然
紅黑樹蒜哀,平衡搜索二叉樹(平衡AVL樹)等,都是搜索二叉樹的不同實現(xiàn)吏砂。

目的都是提高搜索二叉樹的效率撵儿,調(diào)整代價降低。

  • 判斷一個二叉樹是否為搜索二叉樹

在中序遍歷中狐血,如果上次的值小于當前的值淀歇,則證否

/// 判斷一個二叉樹樹否為搜索二叉樹
///
/// - Parameter root: 根節(jié)點
/// - Returns: 結果
func isBST(root: TreeNode?) -> Bool {

    var stack = [TreeNode]()
    var node = root
    
    var lastValue = -NSIntegerMax
    
    while !stack.isEmpty || node != nil {
        if node != nil {
            stack.append(node!) //將當前節(jié)點依次入棧
            node = node!.left //嘗試遍歷左節(jié)點
        } else {

            let parentsNode = stack.removeLast() //取出當前節(jié)點的父節(jié)點

            if lastValue > parentsNode.val {
                return false
            }
            lastValue = parentsNode.val
            node = parentsNode.right //嘗試遍歷右節(jié)點
        }
    }
    
    return true
}

  • 復原一個交換了位置的搜索二叉樹

搜索二叉樹本身的中序遍歷是升序排序。一旦有兩節(jié)點交換了位置匈织,就一定有一到兩個部分產(chǎn)生降序浪默。

#1. 遍歷中出現(xiàn)了兩次局部降序
#1,2,3,4,5
#1,5,3,4,2

第一個錯誤的節(jié)點為第一次降序較大的節(jié)點
第二個錯誤的節(jié)點為第二次降序較小的節(jié)點

#2. 遍歷中只出現(xiàn)了一次局部降序
#1,2,3,4,5
#1,2,4,3,5

第一個錯誤的節(jié)點為此次降序較大的節(jié)點
第二個錯誤的節(jié)點為此次降序較小的節(jié)點


滿二叉樹

  • 對于國內(nèi)的滿二叉樹

除最后一層無任何子節(jié)點外,每一層上的所有結點都有兩個子結點二叉樹缀匕。

從圖形形態(tài)上看纳决,滿二叉樹外觀上是一個三角形


國內(nèi)的滿二叉樹屬于完全二叉樹

這種滿二叉樹的層數(shù)為L,節(jié)點數(shù)為N弦追。
則N = 2^L-1 ,L = log(N+1)

  • 對于國外的滿二叉樹

滿二叉樹的結點要么是葉子結點岳链,度為0花竞,要么是度為2的結點劲件,不存在度為1的結點。


完全二叉樹

在滿二叉樹的基礎上约急,最后一層所有的結點都連續(xù)集中在最左邊零远,這就是完全二叉樹。


  • 判斷完全二叉樹

通過寬度遍歷的方式進行厌蔽。

  • 計算完全二叉樹的節(jié)點個數(shù)牵辣,要求復雜度小于O(N)

完全二叉樹的左右子樹,一定有一邊是滿二叉樹(左側高度H奴饮,右側高度H-1)纬向。

先遍歷左子樹左邊界择浊,再遍歷右子樹左邊界。從而判斷哪邊為滿二叉樹逾条。
滿二叉樹側琢岩,N=2^H。非滿二叉樹側权埠,遞歸澳腹。

//完全二叉樹節(jié)點個數(shù)
func nodeNum(root: TreeNode?) -> Int {
    if root == nil {
        return 0
    }
    return bs(node: root!, level: 1, h: mostLeftLeve(node: root, level: 1))
}



/// 以node為頭的所有節(jié)點個數(shù)
///
/// - Parameters:
///   - node: 當前節(jié)點
///   - level: 當前節(jié)點層數(shù)
///   - h: 總深度
/// - Returns: 節(jié)點個數(shù)
func bs(node: TreeNode,level: Int ,h: Int) -> Int {
    if level == h {
        return 1
    }
    
    //比較節(jié)點右子樹深度與當前樹深度
    if mostLeftLeve(node: node.right, level: level+1) == h {
        //左樹已滿替蛉。2^(h-level)+右樹節(jié)點數(shù)
        return 1<<(h-level) + bs(node: node.right!, level: level+1, h: h)
    }else {
        //右樹已滿。2^(h-level-1)+左樹節(jié)點數(shù)
        return 1<<(h-level-1) + bs(node: node.left!, level: level+1, h: h)
    }
}


/// 獲取當前子樹總高度
///
/// - Parameters:
///   - node: 頭節(jié)點
///   - level: 當前層級
/// - Returns: 左邊界總高度
func mostLeftLeve(node: TreeNode?,level: Int) -> Int {
    var node = node
    var level = level
    while node != nil {
        node = node!.left!
        level+=1
    }
    return level-1
}

每層只遍歷一個節(jié)點的子樹糕篇,總計LogN。
每個子樹獲取右子樹左邊界遍酌心,需要經(jīng)歷LogN次計算拌消。
總復雜度O((LogN^2))

  • 數(shù)組與完全二叉樹

如果從下標從1開始存儲,則編號為i的結點的主要關系為:
雙親:下取整 (i/2)
左孩子:2i
右孩子:2i+1

如果從下標從0開始存儲谒府,則編號為i的結點的主要關系為:
雙親:下取整 ((i-1)/2)
左孩子:2i+1
右孩子:2i+2

#這個規(guī)律拼坎,通常用來對通過指定下標取得相關節(jié)點下標。

后序節(jié)點與前驅(qū)節(jié)點

中序遍歷中的下一個遍歷點與上一個遍歷點

2的后序節(jié)點為3完疫,2的前驅(qū)節(jié)點為1


二叉樹中兩節(jié)點間的距離

可以向上或向下走泰鸡,但每個節(jié)點只能經(jīng)過一次。

下圖中2壳鹤,1兩節(jié)點距離為2盛龄。3,5節(jié)點距離為5


  • 最大距離只有三種情況
  1. head左子樹上的最大距離
  2. head右子樹上的最大距離
  3. head左子樹上離head左孩子最遠的距離芳誓,加上head自身節(jié)點余舶,再加上head右子樹上離head右孩子最遠的距離。也就是兩個節(jié)點分別來自不同子樹的情況锹淌。

三個情況下最大的結果匿值,就是以head為頭節(jié)點的整棵樹上最遠的距離。


參考資料

Swift 算法實戰(zhàn)之路:二叉樹
左神牛課網(wǎng)算法課

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末赂摆,一起剝皮案震驚了整個濱河市挟憔,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌烟号,老刑警劉巖绊谭,帶你破解...
    沈念sama閱讀 218,204評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異汪拥,居然都是意外死亡达传,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來宪赶,“玉大人宗弯,你說我怎么就攤上這事÷蓿” “怎么了罕伯?”我有些...
    開封第一講書人閱讀 164,548評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長叽讳。 經(jīng)常有香客問我追他,道長,這世上最難降的妖魔是什么岛蚤? 我笑而不...
    開封第一講書人閱讀 58,657評論 1 293
  • 正文 為了忘掉前任邑狸,我火速辦了婚禮,結果婚禮上涤妒,老公的妹妹穿的比我還像新娘单雾。我一直安慰自己,他們只是感情好她紫,可當我...
    茶點故事閱讀 67,689評論 6 392
  • 文/花漫 我一把揭開白布硅堆。 她就那樣靜靜地躺著,像睡著了一般贿讹。 火紅的嫁衣襯著肌膚如雪渐逃。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,554評論 1 305
  • 那天民褂,我揣著相機與錄音茄菊,去河邊找鬼。 笑死赊堪,一個胖子當著我的面吹牛面殖,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播哭廉,決...
    沈念sama閱讀 40,302評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼脊僚,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了遵绰?” 一聲冷哼從身側響起辽幌,我...
    開封第一講書人閱讀 39,216評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎街立,沒想到半個月后舶衬,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體埠通,經(jīng)...
    沈念sama閱讀 45,661評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡赎离,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,851評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了端辱。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片梁剔。...
    茶點故事閱讀 39,977評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡虽画,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出荣病,到底是詐尸還是另有隱情码撰,我是刑警寧澤,帶...
    沈念sama閱讀 35,697評論 5 347
  • 正文 年R本政府宣布个盆,位于F島的核電站脖岛,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏颊亮。R本人自食惡果不足惜柴梆,卻給世界環(huán)境...
    茶點故事閱讀 41,306評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望终惑。 院中可真熱鬧绍在,春花似錦、人聲如沸雹有。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽霸奕。三九已至溜宽,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間质帅,已是汗流浹背坑质。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留临梗,地道東北人涡扼。 一個月前我還...
    沈念sama閱讀 48,138評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像盟庞,于是被迫代替她去往敵國和親吃沪。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,927評論 2 355