萬字長文!二叉樹入門和刷題看這篇就夠了!

萬字長文!二叉樹入門和刷題看這篇就夠了!


今天是小浩算法 「365刷題計劃」 二叉樹入門 - 整合篇。本篇做爲入門整合篇,已經砍去難度較大的知識點,全部列出的內容,均爲必須掌握。由於很長,寫下目錄:java


  • 二叉樹是啥node

  • 二叉樹的最大深度(DFS)面試

  • 二叉樹的層次遍歷(BFS)算法

  • 二叉搜索樹驗證數組

  • 二叉搜索樹查找數據結構

  • 二叉搜索樹刪除app

  • 平衡二叉樹機器學習

  • 徹底二叉樹ide

  • 二叉樹的剪枝

01

PART

二叉樹是啥


二叉樹有多重要?單就面試而言,在 leetcode 中二叉樹相關的題目佔據了300多道,近三分之一。同時,二叉樹在整個算法板塊中還起到承上啓下的做用:不可是數組和鏈表的延伸,又能夠做爲圖的基礎。總之,很是重要!函數


什麼是二叉樹?官方是這樣定義的:在計算機科學中,二叉樹是每一個結點最多有兩個子樹的樹結構。一般子樹被稱做「左子樹」(left subtree)和「右子樹」(right subtree)。

萬字長文!二叉樹入門和刷題看這篇就夠了!

上面那是個玩笑,二叉樹長這樣:

萬字長文!二叉樹入門和刷題看這篇就夠了!

二叉樹常被用於實現二叉查找樹和二叉堆。樹比鏈表稍微複雜,由於鏈表是線性數據結構,而樹不是。樹的問題不少均可以由廣度優先搜索或深度優先搜索解決。

通常而言,咱們會看到下面這些與樹相關的術語:

小浩概念

與樹相關的術語


樹的結點(node):包含一個數據元素及若干指向子樹的分支;

孩子結點(child node):結點的子樹的根稱爲該結點的孩子;

雙親結點:B 結點是A 結點的孩子,則A結點是B 結點的雙親;

兄弟結點:同一雙親的孩子結點;堂兄結點:同一層上結點;

祖先結點: 從根到該結點的所經分支上的全部結點

子孫結點:以某結點爲根的子樹中任一結點都稱爲該結點的子孫

結點層:根結點的層定義爲1;根的孩子爲第二層結點,依此類推;

樹的深度:樹中最大的結點層

結點的度:結點子樹的個數

樹的度:樹中最大的結點度。

葉子結點:也叫終端結點,是度爲 0 的結點;

分枝結點:度不爲0的結點;

有序樹:子樹有序的樹,好比家族樹;

無序樹:不考慮子樹的順序;


瞭解了上面的基本概念以後。咱們將經過幾道例題,爲你們引入樹的經典操做。

02

PART

二叉樹最大深度


複習上面的概念:樹的深度指的是樹中最大的結點層。



第104題:給定一個二叉樹,找出其最大深度。二叉樹的深度爲根節點到最遠葉子節點的最長路徑上的節點數。


說明: 葉子節點是指沒有子節點的節點。

示例:

給定二叉樹 [3,9,20,null,null,15,7],

3
   / \
  9  20
    /  \
   15   7

基本概念掌握:每一個節點的深度與它左右子樹的深度有關,且等於其左右子樹最大深度值加上 1。即:

maxDepth(root) =
max(maxDepth(root.left),maxDepth(root.right)) + 1

以 [3,9,20,null,null,15,7] 爲例:

  • 咱們要對根節點的最大深度求解,就要對其左右子樹的深度進行求解

萬字長文!二叉樹入門和刷題看這篇就夠了!

  • 咱們看出。以4爲根節點的子樹沒有左右節點,其深度爲1。而以20爲根節點的子樹的深度,一樣取決於它的左右子樹深度。

萬字長文!二叉樹入門和刷題看這篇就夠了!

  • 對於15和7的子樹,咱們能夠一眼看出其深度爲1。

萬字長文!二叉樹入門和刷題看這篇就夠了!

  • 由此咱們能夠獲得根節點的最大深度爲
maxDepth(root-3)
=max(maxDepth(sub-4),maxDepth(sub-20))+1
=max(1,max(maxDepth(sub-15),maxDepth(sub-7))+1)+1
=max(1,max(1,1)+1)+1
=max(1,2)+1
=3

根據分析,咱們經過遞歸進行求解:

1//Go
 2func maxDepth(root *TreeNode) int {
 3    if root == nil {
 4        return 0
 5    }
 6    return max(maxDepth(root.Left), maxDepth(root.Right)) + 1
 7}
 8
 9func max(a int, b int) int {
10    if a > b {
11        return a
12    }
13    return b
14}

其實咱們上面用的遞歸方式,本質上是使用了DFS的思想。因此這裏就能夠引出什麼是DFS:深度優先搜索算法(Depth First Search),對於二叉樹而言,它沿着樹的深度遍歷樹的節點,儘量深的搜索樹的分支,這一過程一直進行到已發現從源節點可達的全部節點爲止。( 注意,這裏的前提是對二叉樹而言。DFS自己做爲圖算法的一種,在後續我會單獨拉出來和回溯放一塊兒講。)

萬字長文!二叉樹入門和刷題看這篇就夠了!

如上圖二叉樹,它的訪問順序爲:

A-B-D-E-C-F-G

到這裏,咱們思考一個問題?雖然咱們用遞歸的方式根據DFS的思想順利完成了題目。可是這種方式的缺點卻顯而易見。由於在遞歸中,若是層級過深,咱們極可能保存過多的臨時變量,致使棧溢出。這也是爲何咱們通常不在後臺代碼中使用遞歸的緣由。若是不理解,下面咱們詳細說明:

事實上,函數調用的參數是經過棧空間來傳遞的,在調用過程當中會佔用線程的棧資源。而遞歸調用,只有走到最後的結束點後函數才能依次退出,而未到達最後的結束點以前,佔用的棧空間一直沒有釋放,若是遞歸調用次數過多,就可能致使佔用的棧資源超過線程的最大值,從而致使棧溢出,致使程序的異常退出。

因此,咱們引出下面的話題:如何將遞歸的代碼轉化成非遞歸的形式。這裏請記住,基本全部的遞歸轉非遞歸,均可以經過棧來進行實現。非遞歸的DFS,代碼以下:

1//java
 2private List<TreeNode> traversal(TreeNode root) {
 3    List<TreeNode> res = new ArrayList<>();
 4    Stack<TreeNode> stack = new Stack<>();
 5    stack.add(root);
 6    while (!stack.empty()) {
 7        TreeNode node = stack.peek();
 8        res.add(node);
 9        stack.pop();
10        if (node.right != null) {
11            stack.push(node.right);
12        }
13        if (node.left != null) {
14            stack.push(node.left);
15        }
16    }
17    return res;
18}

上面的代碼,惟一須要強調的是,爲何須要先右後左壓入數據?是由於咱們須要將先訪問的數據,後壓入棧(請思考棧的特色)。

若是不理解代碼,請看下圖:

萬字長文!二叉樹入門和刷題看這篇就夠了!

說明:

  • 1:首先將a壓入棧

  • 2:a彈棧,將c、b壓入棧(注意順序)

  • 3:b彈棧,將e、d壓入棧

  • 4,5:d、e、c彈棧,將g、f壓入棧

  • 6:f、g彈棧

至此,非遞歸的 DFS 就講解完畢了。那如何經過非遞歸DFS的方式,來對本題求解呢?相信已經很簡單了,這個下去本身試試就ok了了。

03

PART

二叉樹的層次遍歷


在上文中,咱們經過例題學習了二叉樹的DFS(深度優先搜索),其實就是沿着一個方向一直向下遍歷。那咱們可不能夠按照高度一層一層的訪問樹中的數據呢?固然能夠,就是本節中咱們要講的BFS(寬度優先搜索),同時也被稱爲廣度優先搜索。




第102題:給定一個二叉樹,返回其按層次遍歷的節點值。(即逐層地,從左到右訪問全部節點)。


例如:

給定二叉樹: [3,9,20,null,null,15,7],

3

/ \

9 20

/  \

15 7

返回其層次遍歷結果:[[3],[9,20],[15,7]]


BFS,廣度/寬度優先。說白了就是從上到下,先把每一層遍歷完以後再遍歷一下一層。假如咱們的樹以下:

萬字長文!二叉樹入門和刷題看這篇就夠了!

按照BFS,訪問順序以下:

a->b->c->d->e->f->g

瞭解了BFS,咱們開始對本題進行分析。一樣,咱們先考慮本題的遞歸解法。想到遞歸,咱們通常先想到DFS。咱們能夠對該二叉樹進行先序遍歷(根左右的順序),同時,記錄節點所在的層次level,而且對每一層都定義一個數組,而後將訪問到的節點值放入對應層的數組中。

假設給定二叉樹爲[3,9,20,null,null,15,7],圖解以下:

萬字長文!二叉樹入門和刷題看這篇就夠了!

萬字長文!二叉樹入門和刷題看這篇就夠了!

根據分析,代碼以下:

1//Go
 2func levelOrder(root *TreeNode) [][]int {
 3    return dfs(root, 0, [][]int{})
 4}
 5
 6func dfs(root *TreeNode, level int, res [][]int) [][]int {
 7    if root == nil {
 8        return res
 9    }
10    if len(res) == level {
11        res = append(res, []int{root.Val})
12    } else {
13        res[level] = append(res[level], root.Val)
14    }
15    res = dfs(root.Left, level+1, res)
16    res = dfs(root.Right, level+1, res)
17    return res
18}

上面的解法,其實至關因而用DFS的方法實現了二叉樹的BFS。那咱們能不能直接使用BFS的方式進行解題呢?固然能夠。咱們使用Queue的數據結構。咱們將root節點初始化進隊列,經過消耗尾部,插入頭部的方式來完成BFS。

具體步驟以下圖:
萬字長文!二叉樹入門和刷題看這篇就夠了!

根據分析,完成代碼:

1//Go
 2func levelOrder(root *TreeNode) [][]int {
 3    var result [][]int
 4    if root == nil {
 5        return result
 6    }
 7    // 定義一個雙向隊列
 8    queue := list.New()
 9    // 頭部插入根節點
10    queue.PushFront(root)
11    // 進行廣度搜索
12    for queue.Len() > 0 {
13        var currentLevel []int
14        listLength := queue.Len()
15        for i := 0; i < listLength; i++ {
16            // queue.Back():返回隊列中最後一個元素
17            // queue.Remove(queue.Back()).(*TreeNode) : 移除隊列中最後一個元素並將其轉化爲TreeNode類型
18            node := queue.Remove(queue.Back()).(*TreeNode)
19            currentLevel = append(currentLevel, node.Val)
20            if node.Left != nil {
21                queue.PushFront(node.Left)
22            }
23            if node.Right != nil {
24                queue.PushFront(node.Right)
25            }
26        }
27        result = append(result, currentLevel)
28    }
29    return result
30}

04

PART

二叉搜索樹


BST是二叉搜索樹,很重要。BST是二叉搜索樹,很重要。BST是二叉搜索樹,很重要。重要的事情說三遍。



第98題:給定一個二叉樹,判斷其是不是一個有效的二叉搜索樹。


示例 1:

輸入:

5

/ \

1 4

/ \

3   6

輸出: false

解釋: 輸入爲: [5,1,4,null,null,3,6]。

根節點的值爲 5 ,可是其右子節點值爲 4 。


要驗證二叉搜索樹,首先得知道啥是二叉搜索樹。二叉搜索樹(Binary Search Tree),(又:二叉查找樹,二叉排序樹)它或者是一棵空樹,或者是具備下列性質的二叉樹:若它的左子樹不空,則左子樹上全部結點的值均小於它的根結點的值;若它的右子樹不空,則右子樹上全部結點的值均大於它的根結點的值;它的左、右子樹也分別爲二叉搜索樹。

這裏強調一會兒樹的概念:設T是有根樹,a是T中的一個頂點,由a以及a的全部後裔(後代)導出的子圖稱爲有向樹T的子樹。具體來講,子樹就是樹的其中一個節點以及其下面的全部的節點所構成的樹。好比下面這就是一顆二叉搜索樹:

萬字長文!二叉樹入門和刷題看這篇就夠了!

下面這兩個都不是:

  • 圖中4節點位置的數值應該大於根節點

  • 圖中3節點位置的數值應該大於根節點

萬字長文!二叉樹入門和刷題看這篇就夠了!

回到題目,那咱們如何來驗證一顆二叉搜索樹?首先看完題目,咱們很容易想到 遍歷整棵樹,比較全部節點,經過 左節點值<節點值,右節點值>節點值 的方式來進行求解。可是這種解法是錯誤的,由於對於任意一個節點,咱們不光須要左節點值小於該節點,而且左子樹上的全部節點值都須要小於該節點。(右節點一致)因此咱們在此引入上界與下界,用以保存以前的節點中出現的最大值與最小值

代碼其實很簡單:

1//GO
 2func isValidBST(root *TreeNode) bool {
 3    if root == nil{
 4        return true
 5    }
 6    return isBST(root,math.MinInt64,math.MaxInt64)
 7}
 8
 9func isBST(root *TreeNode,min, max int) bool{
10    if root == nil{
11        return true
12    }
13    if min >= root.Val || max <= root.Val{
14        return false
15    }
16    return isBST(root.Left,min,root.Val) && isBST(root.Right,root.Val,max)
17}

難就難在,可能你們看不懂這個遞歸!沒事,祭出大殺器:

萬字長文!二叉樹入門和刷題看這篇就夠了!

這裏須要強調的是,在每次遞歸中,咱們除了進行左右節點的校驗,還須要與上下界進行判斷。其他的就很簡單了。

05

PART

BST的查找


在上文中,咱們學習了二叉搜索樹。那咱們如何在二叉搜索樹中查找一個元素呢?



第700題:給定二叉搜索樹(BST)的根節點和一個值。你須要在BST中找到節點值等於給定值的節點。返回以該節點爲根的子樹。若是節點不存在,則返回 NULL。


例如,給定二叉搜索樹:

4

   / \

  2   7

 / \

1   3

搜索: 2

你應該返回以下子樹:

2     

 / \   

1   3

在上述示例中,若是要找的值是 5,但由於沒有節點值爲 5,咱們應該返回 NULL。


先複習一下,二叉搜索樹(BST)的特性:

1.若它的左子樹不爲空,則全部左子樹上的值均小於其根節點的值

2.若它的右子樹不爲空,則全部右子樹上的值均大於其根節點得值

3.它的左右子樹也分別爲二叉搜索樹

以下圖就是一棵典型的BST:

萬字長文!二叉樹入門和刷題看這篇就夠了!

如今咱們來看題,假設目標值爲 val。根據BST的特性,咱們能夠很容易想到查找過程(上面的驗證比查找稍難一點):

  • 若是val小於當前結點的值,轉向其左子樹繼續搜索;

  • 若是val大於當前結點的值,轉向其右子樹繼續搜索;

  • 若是已找到,則返回當前結點。

萬字長文!二叉樹入門和刷題看這篇就夠了!

很簡單,不是嗎?而後咱們能夠給出迭代和遞歸兩種解法(給個Java的吧!):

1//java
 2
 3//遞歸
 4public TreeNode searchBST(TreeNode root, int val) {
 5    if (root == null)
 6        return null;
 7    if (root.val > val) {
 8        return searchBST(root.left, val);
 9    } else if (root.val < val) {
10        return searchBST(root.right, val);
11    } else {
12        return root;
13    }
14}
15
16//迭代
17public TreeNode searchBST(TreeNode root, int val) {
18    while (root != null) {
19        if (root.val == val) {
20            return root;
21        } else if (root.val > val) {
22            root = root.left;
23        } else {
24            root = root.right;
25        }
26    }
27    return null;
28}

06

PART

BST的刪除


查找有了,下面天然就要講刪除。(爲啥說我要着重墨在BST上面,由於BST這兩年在面試時很是高頻。面試官不可能說問你一個普通二叉樹的題目,要麼就是問堆,要麼就是問BST,或者就直接DFS考察回溯。)



第450題:給定一個二叉搜索樹的根節點 root 和一個值 key,刪除二叉搜索樹中的 key 對應的節點,並保證二叉搜索樹的性質不變。返回二叉搜索樹(有可能被更新)的根節點的引用。


通常來講,刪除節點可分爲兩個步驟:

首先找到須要刪除的節點;

若是找到了,刪除它。

說明:要求算法時間複雜度爲 O(h),h 爲樹的高度。

示例:

root = [5,3,6,2,4,null,7]

key = 3

5

/ \

3 6

/ \ \

2 4 7

給定須要刪除的節點值是 3,因此咱們首先找到 3 這個節點,而後刪除它。

一個正確的答案是 [5,4,6,2,null,null,7], 以下圖所示。

5

/ \

4 6

/ \

2 7

另外一個正確答案是 [5,2,6,null,4,null,7]。

5

/ \

2 6

\ \

4   7

若是你看到了這裏,相信確定知道BST是個啥了。因此直接分析題目。咱們要刪除BST的一個節點,首先須要找到該節點。而找到以後,會出現三種狀況。

  1. 待刪除的節點左子樹爲空,讓待刪除節點的右子樹替代本身。
    萬字長文!二叉樹入門和刷題看這篇就夠了!
  2. 待刪除的節點右子樹爲空,讓待刪除節點的左子樹替代本身。
    萬字長文!二叉樹入門和刷題看這篇就夠了!
  3. 若是待刪除的節點的左右子樹都不爲空。咱們須要找到比當前節點小的最大節點(前驅),來替換本身

萬字長文!二叉樹入門和刷題看這篇就夠了!

或者比當前節點大的最小節點(後繼),來替換本身。

分析完畢,直接上代碼。這裏咱們給出經過後繼節點來替代本身的方案(能夠自行實現另外一種方案):

1//go
 2func deleteNode(root *TreeNode, key int) *TreeNode {
 3    if root == nil {
 4        return nil
 5    }
 6    if key < root.Val {
 7        root.Left = deleteNode( root.Left, key )
 8        return root
 9    }
10    if key > root.Val {
11        root.Right = deleteNode( root.Right, key )
12        return root
13    }
14    //到這裏意味已經查找到目標
15    if root.Right == nil {
16        //右子樹爲空
17        return root.Left
18    }
19    if root.Left == nil {
20        //左子樹爲空
21        return root.Right
22    }
23    minNode := root.Right
24    for minNode.Left != nil {
25        //查找後繼
26        minNode = minNode.Left
27    }
28    root.Val = minNode.Val
29    root.Right = deleteMinNode( root.Right )
30    return root
31}
32
33
34func deleteMinNode( root *TreeNode ) *TreeNode {
35    if root.Left == nil {
36        pRight := root.Right
37        root.Right = nil
38        return pRight
39    }
40    root.Left = deleteMinNode( root.Left )
41    return root
42}

07

PART

平衡二叉樹


BST講解完了。上面也說了,別人考察咱們確定是考察特殊的。那二叉樹裏還有啥特殊的東東嘞?平衡二叉樹算是一個。



**第110題:給定一個二叉樹,判斷它是不是高度平衡的二叉樹。

本題中,一棵高度平衡二叉樹定義爲:**


一個二叉樹每一個節點 的左右兩個子樹的高度差的絕對值不超過1。

示例 1:

給定二叉樹 [3,9,20,null,null,15,7]

3

/ \

9 20

/  \

15 7

返回 true 。

示例 2:

給定二叉樹 [1,2,2,3,3,null,null,4,4]

1

  / \

 2   2

/ \

3 3

/ \

4 4

返回 false 。


題實際上是一道很簡單的題,主要是拿來複習一下高度。咱們想判斷一棵樹是否知足平衡二叉樹,無非就是判斷當前結點的兩個孩子是否知足平衡,同時兩個孩子的高度差是否超過1。那隻要咱們能夠獲得高度,再基於高度進行判斷便可。

萬字長文!二叉樹入門和刷題看這篇就夠了!

這裏惟一要注意的是,當咱們斷定其中任意一個節點若是不知足平衡二叉樹時,那說明整棵樹已經不是一顆平衡二叉樹,咱們能夠對其進行阻斷,不須要繼續遞歸下去。

而後還有一個初學者容易懵逼的:

萬字長文!二叉樹入門和刷題看這篇就夠了!

這玩意,並非平衡二叉樹。上代碼:

1//GO
 2func isBalanced(root *TreeNode) bool {
 3    if root == nil {
 4        return true
 5    }
 6    l := maxDepth(root.Left)
 7    r := maxDepth(root.Right)
 8    if abs(l-r)>1 {
 9        return false
10    }
11    if isBalanced(root.Left){
12        return true
13    }
14    return isBalanced(root.Right)
15}
16
17func maxDepth(root *TreeNode) int {
18    if root == nil {
19        return 0
20    }
21    return max(maxDepth(root.Left),maxDepth(root.Right)) + 1
22}
23
24func max(a,b int) int {
25    if a > b {
26        return a
27    }
28    return b
29}
30
31func abs(a int) int {
32    if a < 0 {
33        return -a
34    }
35    return a 
36}

08

PART

徹底二叉樹


還有啥特殊的,要撈出來說一講的?



第222題:給出一個徹底二叉樹,求出該樹的節點個數。


說明:

徹底二叉樹的定義以下:在徹底二叉樹中,除了最底層節點可能沒填滿外,其他每層節點數都達到最大值,而且最下面一層的節點都集中在該層最左邊的若干位置。若最底層爲第 h 層,則該層包含 1~ 2h 個節點。

示例:

輸入:

1

/ \

2 3

/ \ /

4 5 6

輸出: 6


老樣子,咱們得說說啥是徹底二叉樹。徹底二叉樹由滿二叉樹引出,先來了解一下什麼是滿二叉樹。若是二叉樹中除了葉子結點,每一個結點的度都爲 2,則此二叉樹稱爲滿二叉樹。(二叉樹的度表明某個結點的孩子或者說直接後繼的個數,這個在上面已經說過了。對於二叉樹而言,1度是隻有一個孩子或者說單子樹,2度是有兩個孩子或者說左右子樹都有。)

萬字長文!二叉樹入門和刷題看這篇就夠了!

那什麼又是徹底二叉樹呢:若是二叉樹中除去最後一層節點爲滿二叉樹,且最後一層的結點依次從左到右分佈,則此二叉樹被稱爲徹底二叉樹。好比下面這顆:

萬字長文!二叉樹入門和刷題看這篇就夠了!

這個就不是:

萬字長文!二叉樹入門和刷題看這篇就夠了!

上面作了這麼多題了,你應該能想到我要說啥 --- 遞歸。二叉樹的題目基本上均可以遞歸求解。

1func countNodes(root *TreeNode) int {
2    if root != nil {
3        return 0
4
5    }
6    return 1 + countNodes(root.Right) + countNodes(root.Left)
7}

可是很明顯,出題者確定不是要這種答案。由於這種答案和徹底二叉樹一毛錢關係都沒有。因此咱們繼續思考。

因爲題中已經告訴咱們這是一顆徹底二叉樹,咱們又已知了徹底二叉樹除了最後一層,其餘層都是滿的,而且最後一層的節點所有靠向了左邊。那咱們能夠想到,能夠將該徹底二叉樹能夠分割成若幹滿二叉樹和徹底二叉樹,滿二叉樹直接根據層高h計算出節點爲2^h-1,而後繼續計算子樹中徹底二叉樹節點。那如何分割成若干滿二叉樹和徹底二叉樹呢?對任意一個子樹,遍歷其左子樹層高left,右子樹層高right,相等左子樹則是滿二叉樹,不然右子樹是滿二叉樹。這裏可能不容易理解,咱們看圖。

假如咱們有樹以下:

萬字長文!二叉樹入門和刷題看這篇就夠了!

咱們看到根節點的左右子樹高度都爲3,那麼說明左子樹是一顆滿二叉樹。由於節點已經填充到右子樹了,左子樹一定已經填滿了。因此左子樹的節點總數咱們能夠直接獲得,是2^left - 1,加上當前這個root節點,則正好是2^3,即 8。而後只須要再對右子樹進行遞歸統計便可。
萬字長文!二叉樹入門和刷題看這篇就夠了!

那假如咱們的樹是這樣:

萬字長文!二叉樹入門和刷題看這篇就夠了!

咱們看到左子樹高度爲3,右子樹高度爲2。說明此時最後一層不滿,但倒數第二層已經滿了,能夠直接獲得右子樹的節點個數。同理,右子樹節點+root節點,總數爲2^right,即2^2。再對左子樹進行遞歸查找。

萬字長文!二叉樹入門和刷題看這篇就夠了!

根據分析,得出代碼:

/java

lass Solution {
public int countNodes(TreeNode root) {
if (root == null) {
return 0;
}
int left = countLevel(root.left);
int right = countLevel(root.right);
if (left == right) {
return countNodes(root.right) + (1 << left);
} else {
return countNodes(root.left) + (1 << right);
}
}

private int countLevel(TreeNode root) {
int level = 0;
while (root != null) {
level++;
root = root.left;
}
return level;
}

09

PART

二叉樹的剪枝


該講的都講了,忽然想到忘了一個經典操做 - 剪枝。迅速補上!很是重要!這裏額外說一點,就本人而言,對這個操做以及其衍化形式的使用會比較頻繁。由於我是作反欺詐的,機器學習裏有一個概念叫作決策樹,那若是一顆決策樹徹底生長,就會帶來比較大的過擬合問題。由於徹底生長的決策樹,每一個節點只會包含一個樣本。因此咱們就須要對決策樹進行剪枝操做,來提高整個決策模型的泛化能力... 聽不懂也不要緊,簡單點講,就是我以爲這個很重要,或者每道算法題都很重要。若是你在工做中沒有用到,不是說明算法不重要,而多是你還不夠重要。



第814題:給定二叉樹根結點 root ,此外樹的每一個結點的值要麼是 0,要麼是 1。返回移除了全部不包含 1 的子樹的原二叉樹。


( 節點 X 的子樹爲 X 自己,以及全部 X 的後代。)

示例1:

輸入: [1,null,0,0,1]

輸出: [1,null,0,null,1]

萬字長文!二叉樹入門和刷題看這篇就夠了!

解釋:

只有紅色節點知足條件「全部不包含 1 的子樹」。

右圖爲返回的答案。

萬字長文!二叉樹入門和刷題看這篇就夠了!

示例2:

輸入: [1,0,1,0,0,0,1]

輸出: [1,null,1,null,1]

萬字長文!二叉樹入門和刷題看這篇就夠了!

示例3:

輸入: [1,1,0,1,1,0,1,0]

輸出: [1,1,0,1,1,null,1]

說明:

給定的二叉樹最多有 100 個節點。

每一個節點的值只會爲 0 或 1 。


仍是先解釋一下,啥是剪枝:假設有一棵樹,最上層的是root節點,而父節點會依賴子節點。若是如今有一些節點已經標記爲無效,咱們要刪除這些無效節點。若是無效節點的依賴的節點還有效,那麼不該該刪除,若是無效節點和它的子節點都無效,則能夠刪除。剪掉這些節點的過程,稱爲剪枝,目的是用來處理二叉樹模型中的依賴問題。

說了好多遍了,二叉樹的問題,大多均可以經過遞歸進行求解。直接分析。假設咱們有二叉樹以下:[0,1,0,1,0,0,0,0,1,1,0,1,0]:

長這樣:

萬字長文!二叉樹入門和刷題看這篇就夠了!

剪枝以後是這樣:

萬字長文!二叉樹入門和刷題看這篇就夠了!

剪什麼你們應該都能理解。那關鍵是怎麼剪?過程也很簡單,在遞歸的過程當中,若是當前結點的左右節點皆爲空,且當前結點爲0,咱們就將當前節點剪掉便可。

萬字長文!二叉樹入門和刷題看這篇就夠了!

其實很簡單,直接看代碼:

1func pruneTree(root *TreeNode) *TreeNode {
 2    return deal(root)
 3}
 4
 5func deal(node *TreeNode) *TreeNode {
 6    if node == nil {
 7        return nil
 8    }
 9    node.Left = deal(node.Left)
10    node.Right = deal(node.Right)
11    if node.Left == nil && node.Right == nil && node.Val == 0 {
12        return nil
13    }
14    return node
15}

10

PART

囉嗦一下


二叉樹入門整合系列篇到這裏就完事了,相信你們若是能夠完整看完,必定會有所收穫。可是呢,其實你們能夠看到,上面的系列還有不少內容沒有講。好比很核心的一塊DFS和回溯。這些都會在後面出單獨的系列進行講解,但願你們多多支持!


今天的整合篇去除了以前的一些冗餘內容,對部分圖解也進行了重構,熬夜整合,猝死邊緣。

相關文章
相關標籤/搜索