LeetCode 前1000題二叉樹題目系統總結(吐血推薦!)

大綱

題解

本文中全部題解,都在 github.com/LjyYano/Lee…java

LeetCode 樹的定義

二叉樹

public class TreeNode {

    int val;
    TreeNode left;
    TreeNode right;

    TreeNode(int x) {
        val = x;
    }
}
複製代碼

N叉樹

public class Node {
    public int val;
    public List<Node> children;

    public Node() {
    }

    public Node(int _val, List<Node> _children) {
        val = _val;
        children = _children;
    }
}
複製代碼

二叉樹遍歷

二叉樹最基本的遍歷方式就是:前序遍歷、中序遍歷和後序遍歷。node

  • 前序遍歷首先訪問根節點,而後遍歷左子樹,最後遍歷右子樹。
  • 中序遍歷是先遍歷左子樹,而後訪問根節點,而後遍歷右子樹。
  • 後序遍歷是先遍歷左子樹,而後遍歷右子樹,最後訪問樹的根節點。

簡單概況以下:git

  • 前序遍歷:根左右
  • 中序遍歷:左根右
  • 後序遍歷:左右根

TIPS:前中後序遍歷區別在於三字中的中間那個字,前、中、後序分別對應左、根、右。github

二叉樹前序遍歷

  1. 二叉樹的前序遍歷

給定一個二叉樹,返回它的 前序 遍歷。面試

示例:算法

輸入: [1,null,2,3]  

   1
    \
     2
    /
   3 

輸出: [1,2,3]
複製代碼

進階: 遞歸算法很簡單,你能夠經過迭代算法完成嗎?數組

遞歸

public List<Integer> preorderTraversal(TreeNode root) {
    List<Integer> ans = new ArrayList<>();
    robot(root, ans);
    return ans;
}

private void robot(TreeNode p, List<Integer> ans) {
    if(p == null) return;
    // 根左右
    ans.add(p.val);
    robot(p.left, ans);
    robot(p.right, ans);
}
複製代碼

迭代

迭代步驟:網絡

  1. 從根節點開始,每次迭代彈出當前棧頂元素
  2. 將其孩子節點壓入棧中(先壓右孩子再壓左孩子)
public List<Integer> preorderTraversal2(TreeNode root) {
    List<Integer> ans = new ArrayList<>();
    Stack<TreeNode> stack = new Stack<>();
    // 將根節點入棧
    stack.push(root);
    while (!stack.isEmpty()) {
        // 取出棧頂元素
        TreeNode tmp = stack.pop();
        if (tmp != null) {
            ans.add(tmp.val);
            // 將其孩子節點壓入棧中(先右節點、再左節點)
            stack.add(tmp.right);
            stack.add(tmp.left);
        }
    }
    return ans;
}
複製代碼

算法複雜度:數據結構

  • 時間複雜度:樹中每一個節點都遍歷一次,所以時間複雜度爲 O(N),其中 N 爲樹中節點的數量。
  • 空間複雜度:
    • 最壞狀況:樹爲鏈表,樹的高度=樹的節點個數,此時空間複雜度是 O(N)。
    • 最好狀況:樹高度平衡,此時空間複雜度是 O(logn)。

二叉樹中序遍歷

  1. 二叉樹的中序遍歷

給定一個二叉樹,返回它的 中序 遍歷。app

示例:

輸入: [1,null,2,3]
   1
    \
     2
    /
   3

輸出: [1,3,2]
複製代碼

進階: 遞歸算法很簡單,你能夠經過迭代算法完成嗎?

遞歸

public List<Integer> inorderTraversal(TreeNode root) {
    List<Integer> ans = new ArrayList<>();
    robot(root, ans);
    return ans;
}

private void robot(TreeNode root, List<Integer> ans) {
    if (root == null) {
        return;
    }
    // 左根右
    robot(root.left, ans);
    ans.add(root.val);
    robot(root.right, ans);
}
複製代碼

迭代

public List<Integer> inorderTraversal(TreeNode root) {
    List<Integer> ans = new ArrayList<>();
    Stack<TreeNode> stack = new Stack<>();
    while (!stack.isEmpty() || root != null) {
        // 一直放入左兒子(左)
        while (root != null) {
            stack.push(root);
            root = root.left;
        }
        // 訪問當前元素(根),把右兒子入棧(右)
        if (!stack.isEmpty()) {
            root = stack.pop();
            ans.add(root.val);
            root = root.right;
        }
    }
    return ans;
}
複製代碼

算法複雜度

  • 時間複雜度:O(n)。遞歸函數 T(n) = 2⋅T(n/2)+1。
  • 空間複雜度:最壞狀況下須要空間O(n),平均狀況爲O(logn)。

二叉樹後序遍歷

  1. 二叉樹的後序遍歷

給定一個二叉樹,返回它的 後序 遍歷。

示例:

輸入: [1,null,2,3]  
   1
    \
     2
    /
   3 

輸出: [3,2,1]
複製代碼

進階: 遞歸算法很簡單,你能夠經過迭代算法完成嗎?

遞歸

public List<Integer> postorderTraversal(TreeNode root) {
    List<Integer> ans = new ArrayList<>();
    robot(root, ans);
    return ans;
}

private void robot(TreeNode p, List<Integer> ans) {
    if (p == null) {
        return;
    }
    // 左右根
    robot(p.left, ans);
    robot(p.right, ans);
    ans.add(p.val);
}
複製代碼

後序遍歷的非遞歸寫法有些麻煩,由於節點第一次訪問時並不打印,而是在第二次遍歷時纔打印。因此須要一個變量來標記該結點是否訪問過。

迭代:利用輔助類

public class StackNode {
    TreeNode root;
    boolean visit;

    StackNode(TreeNode root) {
        this.root = root;
    }
}

public List<Integer> postorderTraversal2(TreeNode root) {
    List<Integer> ans = new ArrayList<>();
    if (root == null) {
        return ans;
    }
    Stack<StackNode> stack = new Stack<>();
    StackNode node;
    stack.push(new StackNode(root));
    while (!stack.isEmpty()) {
        node = stack.pop();
        if (node == null) {
            continue;
        }
        if (!node.visit) {
            node.visit = true;
            stack.push(node);
            if (node.root.right != null) {
                stack.push(new StackNode(node.root.right));
            }
            if (node.root.left != null) {
                stack.push(new StackNode(node.root.left));
            }
        } else if (node.root != null) {
            ans.add(node.root.val);
        }
    }
    return ans;
}
複製代碼

迭代:逆序輸出

public List<Integer> postorderTraversal3(TreeNode root) {
    LinkedList<Integer> ans = new LinkedList<>();
    if (root == null) {
        return ans;
    }

    Stack<TreeNode> stack = new Stack<>();
    stack.push(root);

    while (!stack.isEmpty()) {
        TreeNode node = stack.pop();
        ans.addFirst(node.val);
        if (node.left != null) {
            stack.push(node.left);
        }
        if (node.right != null) {
            stack.push(node.right);
        }
    }
    return ans;
}
複製代碼

算法複雜度:

  • 時間複雜度:訪問每一個節點剛好一次,所以時間複雜度爲 O(N),其中 N 是節點的個數,也就是樹的大小。
  • 空間複雜度:取決於樹的結構,最壞狀況須要保存整棵樹,所以空間複雜度爲 O(N)。

二叉樹的層次遍歷

  1. 二叉樹的層次遍歷

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

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

3
   / \
  9  20
    /  \
   15   7
複製代碼

返回其層次遍歷結果:

[
  [3],
  [9,20],
  [15,7]
]
複製代碼

遞歸

首先確認樹非空,而後調用遞歸函數 helper(node, level),參數是當前節點和節點的層次。

算法過程:

  1. ans 爲結果列表,level 爲當前遍歷的層數(初始爲0)
  2. 若 ans 的長度 = level,向 ans 增長一個空列表
  3. 將節點值放入 ans 的第 level 個列表結尾
  4. 遍歷左右子節點,此時 level + 1
public List<List<Integer>> levelOrder(TreeNode root) {
    List<List<Integer>> ans = new ArrayList<>();
    robot(root, ans, 0);
    return ans;
}

private void robot(TreeNode root, List<List<Integer>> ans, int level) {
    if (root == null) {
        return;
    }
    if (ans.size() == level) {
        ans.add(new ArrayList());
    }
    ans.get(level).add(root.val);
    robot(root.left, ans, level + 1);
    robot(root.right, ans, level + 1);
}
複製代碼

複雜度分析

  • 時間複雜度:O(N),由於每一個節點剛好會被運算一次。
  • 空間複雜度:O(N),保存輸出結果的數組包含 N 個節點的值。

迭代

算法過程:

第 0 層只包含根節點 root ,算法實現以下:

  1. 初始化隊列只包含一個節點 root 和層次編號 0 : level = 0。
  2. 當隊列非空的時候:
    • 新建一個空列表,表示當前層結果 current。
    • 計算當前層有多少個元素:等於隊列的長度。
    • 將這些元素從隊列中彈出,並加入 current 列表中。
    • 將他們的孩子節點做爲下一層壓入隊列中。
    • 將 當前層結果 current 放入 ans 中。
    • 進入下一層循環。
public List<List<Integer>> levelOrder(TreeNode root) {
    List<List<Integer>> ans = new ArrayList<>();
    if (root == null) {
        return ans;
    }

    Queue<TreeNode> queue = new LinkedList<>();
    queue.add(root);

    while (!queue.isEmpty()) {
        List<Integer> current = new ArrayList<>();

        // 當前層的元素個數
        int length = queue.size();
        for (int i = 0; i < length; ++i) {
            TreeNode node = queue.remove();

            // 放入結果
            current.add(node.val);

            // 依次將 node 的左右子節點加入隊列
            if (node.left != null) {
                queue.add(node.left);
            }
            if (node.right != null) {
                queue.add(node.right);
            }
        }
        ans.add(current);
    }
    return ans;
}
複製代碼

複雜度分析

  • 時間複雜度:O(N),由於每一個節點剛好會被運算一次。
  • 空間複雜度:O(N),保存輸出結果的數組包含 N 個節點的值。

二叉樹的右視圖

  1. 二叉樹的右視圖

給定一棵二叉樹,想象本身站在它的右側,按照從頂部到底部的順序,返回從右側所能看到的節點值。

示例:

輸入: [1,2,3,null,5,null,4]
輸出: [1, 3, 4]
解釋:

   1            <---
 /   \
2     3         <---
 \     \
  5     4       <---
複製代碼

本題是層序遍歷的變種,層序遍歷是存儲二叉樹每行的每一個元素,而本題僅存儲二叉樹每行的最後一個元素。

遞歸

public List<Integer> rightSideView(TreeNode root) {
    List<Integer> ans = new ArrayList<>();
    robot(root, ans, 0);
    return ans;
}

private void robot(TreeNode root, List<Integer> ans, int level) {
    if (root == null) {
        return;
    }
    if (ans.size() == level) {
        ans.add(root.val);
    }
    // 層序遍歷的 ans 是一個 List<List<Integer>,是 ans.get(level).add(root.val);
    ans.set(level, root.val);
    robot(root.left, ans, level + 1);
    robot(root.right, ans, level + 1);
}
複製代碼

迭代

public List<Integer> rightSideView(TreeNode root) {
    List<Integer> ans = new ArrayList<>();
    if (root == null) {
        return ans;
    }

    Queue<TreeNode> queue = new LinkedList<>();
    queue.add(root);

    while (!queue.isEmpty()) {
        int current = 0;

        // 當前層的元素個數
        int length = queue.size();
        for (int i = 0; i < length; ++i) {
            TreeNode node = queue.remove();

            // 放入結果
            current = node.val;

            // 依次將 node 的左右子節點加入隊列
            if (node.left != null) {
                queue.add(node.left);
            }
            if (node.right != null) {
                queue.add(node.right);
            }
        }
        ans.add(current);
    }
    return ans;
}
複製代碼

在每一個樹行中找最大值

  1. 在每一個樹行中找最大值

您須要在二叉樹的每一行中找到最大的值。

示例:

輸入:

1
     / \
    3   2
   / \   \  
  5   3   9 

輸出: [1, 3, 9]
複製代碼

本題也是層序遍歷的變種,層序遍歷是存儲二叉樹每行的每一個元素,而本題僅存儲二叉樹每行的最大元素。

遞歸

public List<Integer> largestValues(TreeNode root) {
    List<Integer> ans = new ArrayList<>();
    robot(root, ans, 0);
    return ans;
}

private void robot(TreeNode root, List<Integer> ans, int level) {
    if (root == null) {
        return;
    }

    if (ans.size() <= level) {
        ans.add(Integer.MIN_VALUE);
    }

    ans.set(level, Math.max(ans.get(level), root.val));
    robot(root.left, ans, level + 1);
    robot(root.right, ans, level + 1);
}
複製代碼

迭代

public List<Integer> largestValues(TreeNode root) {
    List<Integer> ans = new ArrayList<>();
    if (root == null) {
        return ans;
    }

    Queue<TreeNode> queue = new LinkedList<>();
    queue.add(root);

    while (!queue.isEmpty()) {
        int current = Integer.MIN_VALUE;

        // 當前層的元素個數
        int length = queue.size();
        for (int i = 0; i < length; ++i) {
            TreeNode node = queue.remove();

            // 放入結果(變化的地方)
            current = Math.max(current, node.val);

            // 依次將 node 的左右子節點加入隊列
            if (node.left != null) {
                queue.add(node.left);
            }
            if (node.right != null) {
                queue.add(node.right);
            }
        }
        ans.add(current);
    }
    return ans;
}
複製代碼

二叉樹的垂序遍歷

  1. 二叉樹的垂序遍歷

給定二叉樹,按垂序遍歷返回其結點值。

對位於 (X, Y) 的每一個結點而言,其左右子結點分別位於 (X-1, Y-1) 和 (X+1, Y-1)。

把一條垂線從 X = -infinity 移動到 X = +infinity ,每當該垂線與結點接觸時,咱們按從上到下的順序報告結點的值( Y 座標遞減)。

若是兩個結點位置相同,則首先報告的結點值較小。

按 X 座標順序返回非空報告的列表。每一個報告都有一個結點值列表。

示例 1:

輸入:[3,9,20,null,null,15,7]
輸出:[[9],[3,15],[20],[7]]
解釋: 
在不喪失其廣泛性的狀況下,咱們能夠假設根結點位於 (0, 0):
而後,值爲 9 的結點出如今 (-1, -1);
值爲 3 和 15 的兩個結點分別出如今 (0, 0) 和 (0, -2);
值爲 20 的結點出如今 (1, -1);
值爲 7 的結點出如今 (2, -2)。
複製代碼

示例 2:

@w=400

輸入:[1,2,3,4,5,6,7]
輸出:[[4],[2],[1,5,6],[3],[7]]
解釋:
根據給定的方案,值爲 5 和 6 的兩個結點出如今同一位置。
然而,在報告 "[1,5,6]" 中,結點值 5 排在前面,由於 5 小於 6。
複製代碼

提示:

樹的結點數介於 1 和 1000 之間。 每一個結點值介於 0 和 1000 之間。

解題思路

設根節點的(x,y)=(0,0),對於節點座標(x,y),其左節點座標爲(x-1,y-1),右節點座標爲(x+1,y-1)。

  1. 構造 List posNodes,遍歷二叉樹每一個節點,填入 PositionNode 的 x 座標、y 座標、val 值。
  2. 將 posNodes 展開成 key 是 x 座標、value 是對應 PositionNode 的 positionMap,注意這裏使用 TreeMap,由於要對 x 座標排序。
  3. 構建結果 ans:遍歷 positionMap,相同位置節點,按照天然順序排序(即先按照 Y 降序排列;若 Y 相同,則按照 val 升序排列)

代碼

public class PositionNode {
    int val;
    int x;
    int y;

    PositionNode(int val, int x, int y) {
        this.val = val;
        this.x = x;
        this.y = y;
    }

    public int getVal() {
        return val;
    }

    public int getX() {
        return x;
    }

    public int getY() {
        return y;
    }
}

public List<List<Integer>> verticalTraversal(TreeNode root) {

    // 構建節點的位置信息
    List<PositionNode> posNodes = new ArrayList<>();
    robot(root, posNodes, 0, 0);

    // 將 posNodes 展開成 key 是 x 座標、value 是對應 PositionNode 的 map
    // 注意這裏使用 TreeMap,由於要對 x 座標排序
    Map<Integer, List<PositionNode>> positionMap = posNodes.stream().collect(
            Collectors.groupingBy(PositionNode::getX, TreeMap::new, Collectors.toList()));

    // 構建結果
    List<List<Integer>> ans = new ArrayList<>();
    positionMap.forEach((k, v) -> {
        // 題目要求:相同位置節點,按照天然順序排序(即先按照 Y 降序排列;若 Y 相同,則按照 val 升序排列)
        v.sort(Comparator.comparing(PositionNode::getY).reversed()
                .thenComparing(PositionNode::getVal));
        ans.add(v.stream().map(PositionNode::getVal).collect(Collectors.toList()));
    });
    return ans;
}

private void robot(TreeNode root, List<PositionNode> posNodes, int x, int y) {
    if (root == null) {
        return;
    }

    posNodes.add(new PositionNode(root.val, x, y));

    // 根節點的(x,y)=(0,0),若某個節點座標爲(x,y),則左節點座標爲(x-1,y-1),右節點座標爲(x+1,y-1)
    robot(root.left, posNodes, x - 1, y - 1);
    robot(root.right, posNodes, x + 1, y - 1);
}
複製代碼

二叉樹的鋸齒形層次遍歷

  1. 二叉樹的鋸齒形層次遍歷

給定一個二叉樹,返回其節點值的鋸齒形層次遍歷。(即先從左往右,再從右往左進行下一層遍歷,以此類推,層與層之間交替進行)。

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

3
   / \
  9  20
    /  \
   15   7
複製代碼

返回鋸齒形層次遍歷以下:

[
  [3],
  [20,9],
  [15,7]
]
複製代碼

本題是層序遍歷的變種,僅需將層序遍歷的偶數行逆序便可。

遞歸

public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
    List<List<Integer>> ans = new ArrayList<>();
    robot(root, ans, 0);
    // 與層序遍歷相比,變化的部分
    for (int i = 1; i < ans.size(); i += 2) {
        Collections.reverse(ans.get(i));
    }
    return ans;
}

private void robot(TreeNode root, List<List<Integer>> ans, int level) {
    if (root == null) {
        return;
    }
    if (ans.size() == level) {
        ans.add(new ArrayList());
    }
    ans.get(level).add(root.val);
    robot(root.left, ans, level + 1);
    robot(root.right, ans, level + 1);
}
複製代碼

迭代

public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
    List<List<Integer>> ans = new ArrayList<>();
    if (root == null) {
        return ans;
    }

    Queue<TreeNode> queue = new LinkedList<>();
    queue.add(root);

    while (!queue.isEmpty()) {
        List<Integer> current = new ArrayList<>();

        // 當前層的元素個數
        int length = queue.size();
        for (int i = 0; i < length; ++i) {
            TreeNode node = queue.remove();

            // 放入結果
            current.add(node.val);

            // 依次將 node 的左右子節點加入隊列
            if (node.left != null) {
                queue.add(node.left);
            }
            if (node.right != null) {
                queue.add(node.right);
            }
        }
        ans.add(current);
    }
    // 與層序遍歷相比,變化的部分
    for (int i = 1; i < ans.size(); i += 2) {
        Collections.reverse(ans.get(i));
    }
    return ans;
}
複製代碼

N 叉樹遍歷

N叉樹的層序遍歷

  1. N叉樹的層序遍歷

給定一個 N 叉樹,返回其節點值的層序遍歷。 (即從左到右,逐層遍歷)。

例如,給定一個 3叉樹 :

@w=400

返回其層序遍歷:

[
     [1],
     [3,2,4],
     [5,6]
]
複製代碼

說明:

  1. 樹的深度不會超過 1000。
  2. 樹的節點總數不會超過 5000。
public List<List<Integer>> levelOrder(Node root) {
    List<List<Integer>> ans = new ArrayList<>();
    robot(root, ans, 0);
    return ans;
}

private void robot(Node root, List<List<Integer>> ans, int level) {
    if (root == null) {
        return;
    }

    if (ans.size() <= level) {
        ans.add(new ArrayList<>());
    }

    ans.get(level).add(root.val);

    if (root.children == null) {
        return;
    }
    for (Node child : root.children) {
        robot(child, ans, level + 1);
    }
}
複製代碼

N叉樹的前序遍歷

  1. N叉樹的前序遍歷

給定一個 N 叉樹,返回其節點值的前序遍歷。

例如,給定一個 3叉樹 :

@w=400

返回其前序遍歷: [1,3,5,6,2,4]。

說明: 遞歸法很簡單,你可使用迭代法完成此題嗎?

遞歸

public List<Integer> preorder(Node root) {
    List<Integer> ans = new ArrayList<>();
    robot(root, ans);
    return ans;
}

private void robot(Node root, List<Integer> ans) {
    if (root == null) {
        return;
    }

    ans.add(root.val);
    if (root.children == null) {
        return;
    }

    for (Node child : root.children) {
        robot(child, ans);
    }
}
複製代碼

迭代

public List<Integer> preorder(Node root) {
    List<Integer> ans = new ArrayList<>();
    if (root == null) {
        return ans;
    }
    Deque<Node> deque = new ArrayDeque<>();
    deque.push(root);
    while (!deque.isEmpty()) {
        Node tmp = deque.pop();
        if (tmp != null) {
            ans.add(tmp.val);
            if (tmp.children == null) {
                continue;
            }
            for (int i = tmp.children.size() - 1; i >= 0; i--) {
                deque.push(tmp.children.get(i));
            }
        }
    }
    return ans;
}
複製代碼

N叉樹的後序遍歷

  1. N叉樹的後序遍歷

給定一個 N 叉樹,返回其節點值的後序遍歷。

例如,給定一個 3叉樹 :

@w=400

返回其後序遍歷: [5,6,3,2,4,1].

說明: 遞歸法很簡單,你可使用迭代法完成此題嗎?

遞歸

public List<Integer> postorder(Node root) {
    List<Integer> ans = new ArrayList<>();
    robot(root, ans);
    return ans;
}

private void robot(Node root, List<Integer> ans) {
    if (root == null) {
        return;
    }

    if (root.children != null) {
        for (Node child : root.children) {
            robot(child, ans);
        }
    }
    ans.add(root.val);
}
複製代碼

迭代

// 後續遍歷:左右中
// 迭代順序:中右左
public List<Integer> postorder(Node root) {
    List<Integer> ans = new ArrayList<>();
    if (root == null) {
        return ans;
    }
    Stack<Node> stack = new Stack<>();
    stack.push(root);
    while (!stack.isEmpty()) {
        Node pop = stack.pop();
        ans.add(pop.val);
        List<Node> children = pop.children;
        if (children == null) {
            continue;
        }
        for (Node child : children) {
            stack.push(child);
        }
    }
    Collections.reverse(ans);
    return ans;
}
複製代碼

二叉搜索樹

不一樣的二叉搜索樹

  1. 不一樣的二叉搜索樹

給定一個整數 n,求以 1 ... n 爲節點組成的二叉搜索樹有多少種?

示例:

輸入: 3
輸出: 5
解釋:
給定 n = 3, 一共有 5 種不一樣結構的二叉搜索樹:

   1         3     3      2      1
    \       /     /      / \      \
     3     2     1      1   3      2
    /     /       \                 \
   2     1         2                 3
複製代碼
public int numTrees(int n) {
    int[] ans = new int[n + 2];
    ans[0] = 1; ans[1] = 1; ans[2] = 2;
    // f(n) = f(0)*f(n-1) + f(1)*f(n-2) + ……
    for(int i = 3; i <= n; i++) {
        for(int j = 0; j <= i - 1; j++) {
            ans[i] += ans[j] * ans[i - j - 1];
        }
    }
    return ans[n];
}
複製代碼

不一樣的二叉搜索樹 II

  1. 不一樣的二叉搜索樹 II

給定一個整數 n,生成全部由 1 ... n 爲節點所組成的二叉搜索樹。

示例:

輸入: 3
輸出:
[
  [1,null,3,2],
  [3,2,null,1],
  [3,1,null,null,2],
  [2,1,3],
  [1,null,2,null,3]
]
解釋:
以上的輸出對應如下 5 種不一樣結構的二叉搜索樹:

   1         3     3      2      1
    \       /     /      / \      \
     3     2     1      1   3      2
    /     /       \                 \
   2     1         2                 3
複製代碼
public List<TreeNode> generateTrees(int n) {
    if(n <= 0) return new ArrayList<>();
    return build(1, n);
}
private List<TreeNode> build(int start, int end) {
    List<TreeNode> roots = new ArrayList<>();       
    if(start > end) {           
        // null也要放入,不然下面的雙重循環進不去
        roots.add(null);
        return roots;
    }
    if(start == end) {
        roots.add(new TreeNode(start));
        return roots;
    }
    for(int i = start; i <= end; i++) {
        List<TreeNode> leftList = build(start, i - 1);
        List<TreeNode> rightList = build(i + 1, end);
        for(TreeNode left : leftList) {             
            for(TreeNode right : rightList) {
                TreeNode root = new TreeNode(i);
                root.left = left;
                root.right = right;
                roots.add(root);
            }
        }
    }
    return roots;
}
複製代碼

驗證二叉搜索樹

  1. 驗證二叉搜索樹

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

假設一個二叉搜索樹具備以下特徵:

  • 節點的左子樹只包含小於當前節點的數。
  • 節點的右子樹只包含大於當前節點的數。
  • 全部左子樹和右子樹自身必須也是二叉搜索樹。

示例 1:

輸入:
    2
   / \
  1   3
輸出: true
複製代碼

示例 2:

輸入:
    5
   / \
  1   4
     / \
    3   6
輸出: false
解釋: 輸入爲: [5,1,4,null,null,3,6]。
     根節點的值爲 5 ,可是其右子節點值爲 4 。
複製代碼
public boolean isValidBST(TreeNode root) {
    // 用long型
    return isValidBST(root, Long.MIN_VALUE, Long.MAX_VALUE);
}

private boolean isValidBST(TreeNode root, long min, long max) {
    if (root == null) {
        return true;
    }
    if (root.val >= max || root.val <= min) {
        return false;
    }
    return isValidBST(root.left, min, root.val) && isValidBST(root.right, root.val, max);
}
複製代碼

恢復二叉搜索樹

  1. 恢復二叉搜索樹

二叉搜索樹中的兩個節點被錯誤地交換。

請在不改變其結構的狀況下,恢復這棵樹。

示例 1:

輸入: [1,3,null,null,2]

   1
  /
 3
  \
   2

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

   3
  /
 1
  \
   2
複製代碼

示例 2:

輸入: [3,1,4,null,null,2]

  3
 / \
1   4
   /
  2

輸出: [2,1,4,null,null,3]

  2
 / \
1   4
   /
  3
複製代碼

進階:

  • 使用 O(n) 空間複雜度的解法很容易實現。
  • 你能想出一個只使用常數空間的解決方案嗎?
TreeNode first = null, second = null;
TreeNode pre = new TreeNode(Integer.MIN_VALUE);
public void recoverTree(TreeNode root) {
    if(root == null) return;
    robot(root);
    if(first != null && second != null) {
        int tmp = first.val;
        first.val = second.val;
        second.val = tmp;
    }
}
private void robot(TreeNode root) {
    if(root == null) return;
    robot(root.left);
    // 找到交換的兩個節點
    if(first == null && pre.val > root.val) {
        first = pre;
    }
    if(first != null && pre.val > root.val) {
        second = root;
    }
    pre = root;
    robot(root.right);
}
複製代碼

修剪二叉搜索樹

  1. 修剪二叉搜索樹

給定一個二叉搜索樹,同時給定最小邊界L 和最大邊界 R。經過修剪二叉搜索樹,使得全部節點的值在[L, R]中 (R>=L) 。你可能須要改變樹的根節點,因此結果應當返回修剪好的二叉搜索樹的新的根節點。

示例 1:

輸入: 
    1
   / \
  0   2

  L = 1
  R = 2

輸出: 
    1
      \
       2
複製代碼

示例 2:

輸入: 
    3
   / \
  0   4
   \
    2
   /
  1

  L = 1
  R = 3

輸出: 
      3
     / 
   2   
  /
 1
複製代碼
public TreeNode trimBST(TreeNode root, int L, int R) {
    if (root == null) {
        return null;
    }
    if (root.val > R) {
        return trimBST(root.left, L, R);
    }
    if (root.val < L) {
        return trimBST(root.right, L, R);
    }

    root.left = trimBST(root.left, L, R);
    root.right = trimBST(root.right, L, R);
    return root;
}
複製代碼

將有序數組轉換爲二叉搜索樹

  1. 將有序數組轉換爲二叉搜索樹

將一個按照升序排列的有序數組,轉換爲一棵高度平衡二叉搜索樹。

本題中,一個高度平衡二叉樹是指一個二叉樹每一個節點 的左右兩個子樹的高度差的絕對值不超過 1。

示例:

給定有序數組: [-10,-3,0,5,9],

一個可能的答案是:[0,-3,9,-10,null,5],它能夠表示下面這個高度平衡二叉搜索樹:

      0
     / \
   -3   9
   /   /
 -10  5
複製代碼
public TreeNode sortedArrayToBST(int[] nums) {
    if (nums == null || nums.length == 0) {
        return null;
    }
    return robot(nums, 0, nums.length - 1);
}

private TreeNode robot(int[] nums, int start, int end) {
    if (start > end) {
        return null;
    }
    int mid = start + (end - start) / 2;
    TreeNode root = new TreeNode(nums[mid]);
    root.left = robot(nums, start, mid - 1);
    root.right = robot(nums, mid + 1, end);
    return root;
}
複製代碼

二叉搜索樹迭代器

  1. 二叉搜索樹迭代器

實現一個二叉搜索樹迭代器。你將使用二叉搜索樹的根節點初始化迭代器。

調用 next() 將返回二叉搜索樹中的下一個最小的數。

示例:

BSTIterator iterator = new BSTIterator(root);
iterator.next();    // 返回 3
iterator.next();    // 返回 7
iterator.hasNext(); // 返回 true
iterator.next();    // 返回 9
iterator.hasNext(); // 返回 true
iterator.next();    // 返回 15
iterator.hasNext(); // 返回 true
iterator.next();    // 返回 20
iterator.hasNext(); // 返回 false
複製代碼

提示:

  • next() 和 hasNext() 操做的時間複雜度是 O(1),並使用 O(h) 內存,其中 h 是樹的高度。
  • 你能夠假設 next() 調用老是有效的,也就是說,當調用 next() 時,BST 中至少存在一個下一個最小的數。
class BSTIterator {

    Stack<TreeNode> vector = new Stack<>();
    TreeNode current;

    public BSTIterator(TreeNode root) {
        current = root;
        // 一直放入左兒子(左)
        while (current != null) {
            vector.push(current);
            current = current.left;
        }
    }

    /** @return whether we have a next smallest number */
    public boolean hasNext() {
        return !vector.isEmpty() || current != null;
    }

    /** @return the next smallest number */
    public int next() {
        // 一直放入左兒子(左)
        while (current != null) {
            vector.push(current);
            current = current.left;
        }
        int ans = 0;
        // 訪問當前元素(中),把右兒子入棧(右)
        if (!vector.isEmpty()) {
            current = vector.pop();
            ans = current.val;
            current = current.right;
        }
        return ans;
    }
}

/** * Your BSTIterator object will be instantiated and called as such: * BSTIterator obj = new BSTIterator(root); * int param_1 = obj.next(); * boolean param_2 = obj.hasNext(); */
複製代碼

二叉搜索樹中第K小的元素

  1. 二叉搜索樹中第K小的元素

給定一個二叉搜索樹,編寫一個函數 kthSmallest 來查找其中第 k 個最小的元素。

說明: 你能夠假設 k 老是有效的,1 ≤ k ≤ 二叉搜索樹元素個數。

示例 1:

輸入: root = [3,1,4,null,2], k = 1
   3
  / \
 1   4
  \
   2
輸出: 1
複製代碼

示例 2:

輸入: root = [5,3,6,2,4,null,null,1], k = 3
       5
      / \
     3   6
    / \
   2   4
  /
 1
輸出: 3
複製代碼

進階: 若是二叉搜索樹常常被修改(插入/刪除操做)而且你須要頻繁地查找第 k 小的值,你將如何優化 kthSmallest 函數?

int ans = 0;
int count = 0;

public int kthSmallest(TreeNode root, int k) {
    count = k;
    robot(root);
    return ans;
}

private void robot(TreeNode root) {
    if (root == null) {
        return;
    }
    robot(root.left);
    if (--count == 0) {
        ans = root.val;
        return;
    }
    robot(root.right);
}
複製代碼

二叉搜索樹的最近公共祖先

  1. 二叉搜索樹的最近公共祖先

給定一個二叉搜索樹, 找到該樹中兩個指定節點的最近公共祖先。

百度百科中最近公共祖先的定義爲:「對於有根樹 T 的兩個結點 p、q,最近公共祖先表示爲一個結點 x,知足 x 是 p、q 的祖先且 x 的深度儘量大(一個節點也能夠是它本身的祖先)。」

例如,給定以下二叉搜索樹:  root = [6,2,8,0,4,7,9,null,null,3,5]

示例 1:

輸入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8
輸出: 6 
解釋: 節點 2 和節點 8 的最近公共祖先是 6。
複製代碼

示例 2:

輸入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 4
輸出: 2
解釋: 節點 2 和節點 4 的最近公共祖先是 2, 由於根據定義最近公共祖先節點能夠爲節點自己。
複製代碼

說明:

  • 全部節點的值都是惟一的。
  • p、q 爲不一樣節點且均存在於給定的二叉搜索樹中。
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
    if (root == null || p == null || q == null) {
        return null;
    }
    TreeNode left = lowestCommonAncestor(root.left, p, q);
    if (root.val <= Math.max(p.val, q.val) && root.val >= Math.min(p.val, q.val)) {
        return root;
    }
    TreeNode right = lowestCommonAncestor(root.right, p, q);
    return left == null ? right : left;
}
複製代碼

刪除二叉搜索樹中的節點

  1. 刪除二叉搜索樹中的節點

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

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

  1. 首先找到須要刪除的節點;
  2. 若是找到了,刪除它。

說明: 要求算法時間複雜度爲 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
複製代碼
// todo 題目複雜,面試時不會常考,考慮忽略此題目
複製代碼

二叉搜索樹中的搜索

  1. 二叉搜索樹中的搜索

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

例如,

給定二叉搜索樹:

        4
       / \
      2   7
     / \
    1   3

和值: 2
複製代碼

你應該返回以下子樹:

2     
     / \   
    1   3
複製代碼

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

public TreeNode searchBST(TreeNode root, int val) {
    if (root == null || root.val == val) {
        return root;
    }

    if (root.val < val) {
        return searchBST(root.right, val);
    }

    if (root.val > val) {
        return searchBST(root.left, val);
    }

    return null;
}
複製代碼

二叉搜索樹中的插入操做

給定二叉搜索樹(BST)的根節點和要插入樹中的值,將值插入二叉搜索樹。 返回插入後二叉搜索樹的根節點。 保證原始二叉搜索樹中不存在新值。

注意,可能存在多種有效的插入方式,只要樹在插入後仍保持爲二叉搜索樹便可。 你能夠返回任意有效的結果。

例如,

給定二叉搜索樹:

        4
       / \
      2   7
     / \
    1   3

和 插入的值: 5
複製代碼

你能夠返回這個二叉搜索樹:

4
       /   \
      2     7
     / \   /
    1   3 5
複製代碼

或者這個樹也是有效的:

5
       /   \
      2     7
     / \   
    1   3
         \
          4
複製代碼
public TreeNode insertIntoBST(TreeNode root, int val) {
    if (root == null) {
        return new TreeNode(val);
    }

    if (root.val > val) {
        root.left = insertIntoBST(root.left, val);
    } else {
        root.right = insertIntoBST(root.right, val);
    }

    return root;
}
複製代碼

深搜、廣搜

相同的樹

給定兩個二叉樹,編寫一個函數來檢驗它們是否相同。

若是兩個樹在結構上相同,而且節點具備相同的值,則認爲它們是相同的。

示例 1:

輸入:       1         1
          / \       / \
         2   3     2   3

        [1,2,3],   [1,2,3]

輸出: true
複製代碼

示例 2:

輸入:      1          1
          /           \
         2             2

        [1,2],     [1,null,2]

輸出: false
複製代碼

示例 3:

輸入:       1         1
          / \       / \
         2   1     1   2

        [1,2,1],   [1,1,2]

輸出: false
複製代碼
public boolean isSameTree(TreeNode p, TreeNode q) {
    if (p == q) {
        return true;
    }

    if (p == null || q == null) {
        return false;
    }

    return p.val == q.val && isSameTree(p.left, q.left) && isSameTree(p.right, q.right);
}
複製代碼

對稱二叉樹

給定一個二叉樹,檢查它是不是鏡像對稱的。

例如,二叉樹 [1,2,2,3,4,4,3] 是對稱的。

1
   / \
  2   2
 / \ / \
3  4 4  3
複製代碼

可是下面這個 [1,2,2,null,3,null,3] 則不是鏡像對稱的:

1
   / \
  2   2
   \   \
   3    3
複製代碼

說明:

若是你能夠運用遞歸和迭代兩種方法解決這個問題,會很加分。

遞歸

public boolean isSymmetric(TreeNode root) {
    return robot(root, root);
}

boolean robot(TreeNode p, TreeNode q) {

    if (p == null && q == null) {
        return true;
    }

    if (p == null || q == null) {
        return false;
    }

    return p.val == q.val && robot(p.left, q.right) && robot(p.right, q.left);
}
複製代碼

迭代

public boolean isSymmetric2(TreeNode root) {

    if (root == null) {
        return true;
    }

    Stack<TreeNode> stack = new Stack<>();

    stack.push(root.left);
    stack.push(root.right);

    while (!stack.isEmpty()) {
        TreeNode p = stack.pop();
        TreeNode q = stack.pop();

        if (p == null && q == null) {
            continue;
        }

        if (p == null || q == null) {
            return false;
        }

        if (p.val != q.val) {
            return false;
        }

        stack.push(p.left);
        stack.push(q.right);

        stack.push(p.right);
        stack.push(q.left);
    }

    return true;
}
複製代碼

翻轉二叉樹

  1. 翻轉二叉樹

示例:

輸入:

4
   /   \
  2     7
 / \   / \
1   3 6   9
複製代碼

輸出:

4
   /   \
  7     2
 / \   / \
9   6 3   1
複製代碼

備註: 這個問題是受到 Max Howell 的 原問題 啓發的 :

谷歌:咱們90%的工程師使用您編寫的軟件(Homebrew),可是您卻沒法在面試時在白板上寫出翻轉二叉樹這道題,這太糟糕了。
複製代碼
public TreeNode invertTree(TreeNode root) {
    if (root == null) {
        return null;
    }

    TreeNode tmp = root.left;
    root.left = root.right;
    root.right = tmp;

    invertTree(root.left);
    invertTree(root.right);

    return root;
}
複製代碼

二叉樹的最大深度

  1. 二叉樹的最大深度

給定一個二叉樹,找出其最大深度。

二叉樹的深度爲根節點到最遠葉子節點的最長路徑上的節點數。

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

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

3
   / \
  9  20
    /  \
   15   7
複製代碼

返回它的最大深度 3 。

public int maxDepth(TreeNode root) {

    if (root == null) {
        return 0;
    }

    int left = maxDepth(root.left);
    int right = maxDepth(root.right);

    return left > right ? (left + 1) : (right + 1);
}
複製代碼

二叉樹的最小深度

  1. 二叉樹的最小深度

給定一個二叉樹,找出其最小深度。

最小深度是從根節點到最近葉子節點的最短路徑上的節點數量。

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

示例:

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

3
   / \
  9  20
    /  \
   15   7
複製代碼

返回它的最小深度  2.

public int minDepth(TreeNode root) {
    if (root == null) {
        return 0;
    }
    int left = minDepth(root.left);
    int right = minDepth(root.right);
    return (left == 0 || right == 0) ? (left + right + 1) : Math.min(left, right) + 1;
}
複製代碼

平衡二叉樹

  1. 平衡二叉樹

給定一個二叉樹,判斷它是不是高度平衡的二叉樹。

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

一個二叉樹每一個節點 的左右兩個子樹的高度差的絕對值不超過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

public boolean isBalanced(TreeNode root) {
    if (root == null) {
        return true;
    }

    return Math.abs(depth(root.left) - depth(root.right)) <= 1 && isBalanced(root.left)
            && isBalanced(root.right);
}

int depth(TreeNode node) {
    if (node == null) {
        return 0;
    }

    return Math.max(depth(node.left), depth(node.right)) + 1;
}
複製代碼

徹底二叉樹的節點個數

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

說明:

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

示例:

輸入:

1
   / \
  2   3
 / \  /
4  5 6

輸出: 6
複製代碼
public int countNodes(TreeNode root) {
    if (root == null) {
        return 0;
    }

    int left = 0;
    int right = 0;

    TreeNode p = root;
    while (p != null) {
        p = p.left;
        left++;
    }

    p = root;
    while (p != null) {
        p = p.right;
        right++;
    }

    if (left == right) {
        return (1 << left) - 1;
    }

    return countNodes(root.left) + countNodes(root.right) + 1;
}
複製代碼

二叉樹最大寬度

  1. 二叉樹最大寬度

給定一個二叉樹,編寫一個函數來獲取這個樹的最大寬度。樹的寬度是全部層中的最大寬度。這個二叉樹與 滿二叉樹(full binary tree) 結構相同,但一些節點爲空。

每一層的寬度被定義爲兩個端點(該層最左和最右的非空節點,兩端點間的null節點也計入長度)之間的長度。

示例 1:

輸入: 

           1
         /   \
        3     2
       / \     \  
      5   3     9 

輸出: 4
解釋: 最大值出如今樹的第 3 層,寬度爲 4 (5,3,null,9)。
複製代碼

示例 2:

輸入: 

          1
         /  
        3    
       / \       
      5   3     

輸出: 2
解釋: 最大值出如今樹的第 3 層,寬度爲 2 (5,3)。
複製代碼

示例 3:

輸入: 

          1
         / \
        3   2 
       /        
      5      

輸出: 2
解釋: 最大值出如今樹的第 2 層,寬度爲 2 (3,2)。
複製代碼

示例 4:

輸入: 

          1
         / \
        3   2
       /     \  
      5       9 
     /         \
    6           7
輸出: 8
解釋: 最大值出如今樹的第 4 層,寬度爲 8 (6,null,null,null,null,null,null,7)。
複製代碼

注意: 答案在32位有符號整數的表示範圍內。

public int widthOfBinaryTree(TreeNode root) {
    int[] ans = new int[1];
    robot(root, ans, new ArrayList<>(), 0, 1);
    return ans[0];
}

private void robot(TreeNode root, int[] ans, ArrayList<Integer> leftIndexes, int level, int index) {
    if (root == null) {
        return;
    }

    if (leftIndexes.size() <= level) {
        leftIndexes.add(index);
    }

    ans[0] = Math.max(ans[0], index - leftIndexes.get(level) + 1);
    robot(root.left, ans, leftIndexes, level + 1, 2 * index);
    robot(root.right, ans, leftIndexes, level + 1, 2 * index + 1);
}
複製代碼

二叉樹的直徑

  1. 二叉樹的直徑

給定一棵二叉樹,你須要計算它的直徑長度。一棵二叉樹的直徑長度是任意兩個結點路徑長度中的最大值。這條路徑可能穿過根結點。

示例 : 給定二叉樹

1
         / \
        2   3
       / \     
      4   5    
複製代碼

返回 3, 它的長度是路徑 [4,2,1,3] 或者 [5,2,1,3]。

注意:兩結點之間的路徑長度是以它們之間邊的數目表示。

public int diameterOfBinaryTree(TreeNode root) {
    int[] ans = new int[1];
    robot(root, ans);
    return ans[0];
}

private int robot(TreeNode root, int[] ans) {
    if (root == null) {
        return 0;
    }

    int left = robot(root.left, ans);
    int right = robot(root.right, ans);

    // 維護直徑最大值
    ans[0] = Math.max(left + right, ans[0]);

    // 返回當前節點的最大直徑
    return Math.max(left, right) + 1;
}
複製代碼

二叉樹的坡度

  1. 二叉樹的坡度

給定一個二叉樹,計算整個樹的坡度。

一個樹的節點的坡度定義即爲,該節點左子樹的結點之和和右子樹結點之和的差的絕對值。空結點的的坡度是0。

整個樹的坡度就是其全部節點的坡度之和。

示例:

輸入: 
         1
       /   \
      2     3
輸出: 1
解釋: 
結點的坡度 2 : 0
結點的坡度 3 : 0
結點的坡度 1 : |2-3| = 1
樹的坡度 : 0 + 0 + 1 = 1
複製代碼

注意:

  • 任何子樹的結點的和不會超過32位整數的範圍。
  • 坡度的值不會超過32位整數的範圍。
public int findTilt(TreeNode root) {
    int[] ans = new int[1];
    robot(root, ans);
    return ans[0];

}

private int robot(TreeNode root, int[] ans) {
    if (root == null) {
        return 0;
    }

    int left = robot(root.left, ans);
    int right = robot(root.right, ans);
    ans[0] += Math.abs(left - right);
    return left + right + root.val;
}
複製代碼

二叉樹的全部路徑

  1. 二叉樹的全部路徑

給定一個二叉樹,返回全部從根節點到葉子節點的路徑。

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

示例:

輸入:

   1
 /   \
2     3
 \
  5

輸出: ["1->2->5", "1->3"]

解釋: 全部根節點到葉子節點的路徑爲: 1->2->5, 1->3
複製代碼
public List<String> binaryTreePaths(TreeNode root) {
    List<String> ans = new ArrayList<>();
    robot(root, ans, "");
    return ans;
}

private void robot(TreeNode root, List<String> ans, String path) {
    if (root == null) {
        return;
    }
    if (root.left == null && root.right == null) {
        ans.add(path + root.val);
        return;
    }
    robot(root.left, ans, path + root.val + "->");
    robot(root.right, ans, path + root.val + "->");
}
複製代碼

二叉樹的最近公共祖先

  1. 二叉樹的最近公共祖先

給定一個二叉樹, 找到該樹中兩個指定節點的最近公共祖先。

百度百科中最近公共祖先的定義爲:「對於有根樹 T 的兩個結點 p、q,最近公共祖先表示爲一個結點 x,知足 x 是 p、q 的祖先且 x 的深度儘量大(一個節點也能夠是它本身的祖先)。」

例如,給定以下二叉樹:  root = [3,5,1,6,2,0,8,null,null,7,4]

示例 1:

輸入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
輸出: 3
解釋: 節點 5 和節點 1 的最近公共祖先是節點 3。
複製代碼

示例 2:

輸入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
輸出: 5
解釋: 節點 5 和節點 4 的最近公共祖先是節點 5。由於根據定義最近公共祖先節點能夠爲節點自己。
複製代碼

說明:

  • 全部節點的值都是惟一的。
  • p、q 爲不一樣節點且均存在於給定的二叉樹中。
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {

    if (root == null || root == p || root == q) {
        return root;
    }

    TreeNode left = lowestCommonAncestor(root.left, p, q);
    TreeNode right = lowestCommonAncestor(root.right, p, q);

    if (left != null && right != null) {
        return root;
    }

    return left == null ? right : left;
}
複製代碼

最深葉節點的最近公共祖先

  1. 最深葉節點的最近公共祖先

給你一個有根節點的二叉樹,找到它最深的葉節點的最近公共祖先。

回想一下:

  • 葉節點 是二叉樹中沒有子節點的節點
  • 樹的根節點的 深度 爲 0,若是某一節點的深度爲 d,那它的子節點的深度就是 d+1
  • 若是咱們假定 A 是一組節點 S 的 最近公共祖先 中的每一個節點都在以 A 爲根節點的子樹中,且 A 的深度達到此條件下可能的最大值。

示例 1:

輸入:root = [1,2,3]
輸出:[1,2,3]
複製代碼

示例 2:

輸入:root = [1,2,3,4]
輸出:[4]
複製代碼

示例 3:

輸入:root = [1,2,3,4,5]
輸出:[2,4,5]
複製代碼

提示:

  • 給你的樹中將有 1 到 1000 個節點。
  • 樹中每一個節點的值都在 1 到 1000 之間。
public TreeNode lcaDeepestLeaves(TreeNode root) {
    if (root == null) {
        return null;
    }

    int left = depth(root.left);
    int right = depth(root.right);

    if (left == right) {
        return root;
    }

    return left > right ? lcaDeepestLeaves(root.left) : lcaDeepestLeaves(root.right);
}

private int depth(TreeNode root) {
    if (root == null) {
        return 0;
    }

    int left = depth(root.left);
    int right = depth(root.right);

    return Math.max(left, right) + 1;
}
複製代碼

路徑和

左葉子之和

  1. 左葉子之和

計算給定二叉樹的全部左葉子之和。

示例:

3
   / \
  9  20
    /  \
   15   7
複製代碼

在這個二叉樹中,有兩個左葉子,分別是 9 和 15,因此返回 24

public int sumOfLeftLeaves(TreeNode root) {
    int[] ans = new int[1];
    robot(root, ans, false);
    return ans[0];
}

private void robot(TreeNode root, int[] ans, boolean isLeft) {
    if (root == null) {
        return;
    }

    if (root.left == null && root.right == null && isLeft) {
        ans[0] += root.val;
    }

    robot(root.left, ans, true);
    robot(root.right, ans, false);
}
複製代碼

路徑總和

  1. 路徑總和

給定一個二叉樹和一個目標和,判斷該樹中是否存在根節點到葉子節點的路徑,這條路徑上全部節點值相加等於目標和。

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

示例:  給定以下二叉樹,以及目標和 sum = 22

5
             / \
            4   8
           /   / \
          11  13  4
         /  \      \
        7    2      1
複製代碼

返回 true, 由於存在目標和爲 22 的根節點到葉子節點的路徑 5->4->11->2

public boolean hasPathSum(TreeNode root, int sum) {
    if (root == null) {
        return false;
    }

    // 葉子節點 && 和爲 sum
    if (root.left == null && root.right == null && sum - root.val == 0) {
        return true;
    }
    return hasPathSum(root.left, sum - root.val) || hasPathSum(root.right, sum - root.val);
}
複製代碼

路徑總和 II

  1. 路徑總和 II

給定一個二叉樹和一個目標和,找到全部從根節點到葉子節點路徑總和等於給定目標和的路徑。

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

示例: 給定以下二叉樹,以及目標和 sum = 22,

5
         / \
        4   8
       /   / \
      11  13  4
     /  \    / \
    7    2  5   1
複製代碼

返回:

[
   [5,4,11,2],
   [5,8,4,5]
]
複製代碼
public List<List<Integer>> pathSum(TreeNode root, int sum) {
    List<List<Integer>> ans = new ArrayList<>();
    robot(root, sum, ans, new ArrayList<>());
    return ans;
}

private void robot(TreeNode root, int sum, List<List<Integer>> ans, List<Integer> tmp) {
    if (root == null) {
        return;
    }
    tmp.add(root.val);
    if (root.left == null && root.right == null && sum == root.val) {
        ans.add(new ArrayList(tmp));
        tmp.remove(tmp.size() - 1);
        return;
    }
    robot(root.left, sum - root.val, ans, tmp);
    robot(root.right, sum - root.val, ans, tmp);
    tmp.remove(tmp.size() - 1);
}
複製代碼

路徑總和 III

  1. 路徑總和 III

給定一個二叉樹,它的每一個結點都存放着一個整數值。

找出路徑和等於給定數值的路徑總數。

路徑不須要從根節點開始,也不須要在葉子節點結束,可是路徑方向必須是向下的(只能從父節點到子節點)。

二叉樹不超過1000個節點,且節點數值範圍是 [-1000000,1000000] 的整數。

示例:

root = [10,5,-3,3,2,null,11,3,-2,null,1], sum = 8

10
     /  \
    5   -3
   / \    \
  3   2   11
 / \   \
3  -2   1
複製代碼

返回 3。和等於 8 的路徑有:

1.  5 -> 3
2.  5 -> 2 -> 1
3.  -3 -> 11
複製代碼
public int pathSum(TreeNode root, int sum) {
    if (root == null) {
        return 0;
    }
    return robot(root, sum) + pathSum(root.left, sum) + pathSum(root.right, sum);
}

private int robot(TreeNode root, int sum) {
    if (root == null) {
        return 0;
    }
    int ans = 0;
    sum -= root.val;

    if (sum == 0) {
        ans++;
    }

    ans += robot(root.left, sum);
    ans += robot(root.right, sum);

    return ans;
}
複製代碼

二叉樹中的最大路徑和

  1. 二叉樹中的最大路徑和

給定一個非空二叉樹,返回其最大路徑和。

本題中,路徑被定義爲一條從樹中任意節點出發,達到任意節點的序列。該路徑至少包含一個節點,且不必定通過根節點。

示例 1:

輸入: [1,2,3]

       1
      / \
     2   3

輸出: 6
複製代碼

示例 2:

輸入: [-10,9,20,null,null,15,7]

   -10
   / \
  9  20
    /  \
   15   7

輸出: 42
複製代碼
public int maxPathSum(TreeNode root) {
    int[] ans = new int[] { Integer.MIN_VALUE };
    robot(root, ans);
    return ans[0];
}

private int robot(TreeNode root, int[] ans) {
    if (root == null) {
        return 0;
    }
    int left = robot(root.left, ans);
    int right = robot(root.right, ans);

    int res = root.val;
    if (Math.max(left, right) > 0) {
        res += Math.max(left, right);
    }

    int gain = Math.max(Math.max(left, right), left + right);

    ans[0] = Math.max(ans[0], root.val);
    ans[0] = Math.max(ans[0], root.val + gain);
    return res;
}
複製代碼

求根到葉子節點數字之和

  1. 求根到葉子節點數字之和

給定一個二叉樹,它的每一個結點都存放一個 0-9 的數字,每條從根到葉子節點的路徑都表明一個數字。

例如,從根到葉子節點路徑 1->2->3 表明數字 123。

計算從根到葉子節點生成的全部數字之和。

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

示例 1:

輸入: [1,2,3]
    1
   / \
  2   3
輸出: 25
解釋:
從根到葉子節點路徑 1->2 表明數字 12.
從根到葉子節點路徑 1->3 表明數字 13.
所以,數字總和 = 12 + 13 = 25.
複製代碼

示例 2:

輸入: [4,9,0,5,1]
    4
   / \
  9   0
 / \
5   1
輸出: 1026
解釋:
從根到葉子節點路徑 4->9->5 表明數字 495.
從根到葉子節點路徑 4->9->1 表明數字 491.
從根到葉子節點路徑 4->0 表明數字 40.
所以,數字總和 = 495 + 491 + 40 = 1026.
複製代碼
public int sumNumbers(TreeNode root) {
    return robot(root, 0);
}

private int robot(TreeNode root, int p) {
    if (root == null) {
        return 0;
    }

    p = p * 10 + root.val;

    if (root.left == null && root.right == null) {
        return p;
    }

    return robot(root.left, p) + robot(root.right, p);
}
複製代碼

序列化二叉樹、構造二叉樹

從前序與中序遍歷序列構造二叉樹

  1. 從前序與中序遍歷序列構造二叉樹

根據一棵樹的前序遍歷與中序遍歷構造二叉樹。

注意: 你能夠假設樹中沒有重複的元素。

例如,給出

前序遍歷 preorder = [3,9,20,15,7]
中序遍歷 inorder = [9,3,15,20,7]
複製代碼

返回以下的二叉樹:

3
   / \
  9  20
    /  \
   15   7
複製代碼
public TreeNode buildTree(int[] pre, int[] in) {
    return robot(pre, in, 0, 0, in.length - 1);
}

private TreeNode robot(int[] pre, int[] in, int preStart, int inStart, int inEnd) {
    if (preStart >= pre.length || inStart > inEnd) {
        return null;
    }
    // 找到pos
    TreeNode root = new TreeNode(pre[preStart]);
    int index = 0;
    for (int i = inStart; i <= inEnd; i++) {
        if (in[i] == root.val) {
            index = i;
            break;
        }
    }
    root.left = robot(pre, in, preStart + 1, inStart, index - 1);
    root.right = robot(pre, in, preStart + 1 + index - inStart, index + 1, inEnd);
    return root;
}
複製代碼

從中序與後序遍歷序列構造二叉樹

  1. 從中序與後序遍歷序列構造二叉樹

根據一棵樹的中序遍歷與後序遍歷構造二叉樹。

注意: 你能夠假設樹中沒有重複的元素。

例如,給出

中序遍歷 inorder = [9,3,15,20,7]
後序遍歷 postorder = [9,15,7,20,3]
複製代碼

返回以下的二叉樹:

3
   / \
  9  20
    /  \
   15   7
複製代碼
public TreeNode buildTree(int[] in, int[] post) {
    return robot(in, post, 0, in.length - 1, 0, post.length - 1);
}

private TreeNode robot(int[] in, int[] post, int inStart, int inEnd, int postStart, int postEnd) {
    if (postStart > postEnd) {
        return null;
    }
    TreeNode root = new TreeNode(post[postEnd]);
    int pos = 0;
    // 找到pos
    for (int i = inStart; i <= inEnd; i++) {
        if (in[i] == root.val) {
            pos = i;
            break;
        }
    }
    root.left = robot(in, post, inStart, pos - 1, postStart, postStart + pos - inStart - 1);
    root.right = robot(in, post, pos + 1, inEnd, postEnd + pos - inEnd, postEnd - 1);
    return root;
}
複製代碼

先序遍歷構造二叉樹

  1. 先序遍歷構造二叉樹

返回與給定先序遍歷 preorder 相匹配的二叉搜索樹(binary search tree)的根結點。

(回想一下,二叉搜索樹是二叉樹的一種,其每一個節點都知足如下規則,對於 node.left 的任何後代,值總 < node.val,而 node.right 的任何後代,值總 > node.val。此外,先序遍歷首先顯示節點的值,而後遍歷 node.left,接着遍歷 node.right。)

示例:

輸入:[8,5,1,7,10,12]
輸出:[8,5,10,1,7,null,12]
複製代碼

@w=400

提示:

  • 1 <= preorder.length <= 100
  • 先序 preorder 中的值是不一樣的。
int index = 0;

public TreeNode bstFromPreorder(int[] preorder) {
    return robot(preorder, Integer.MIN_VALUE, Integer.MAX_VALUE);
}

private TreeNode robot(int[] preorder, int min, int max) {
    if (index == preorder.length) {
        return null;
    }

    int val = preorder[index];
    if (val < min || val > max) {
        return null;
    }
    index++;
    TreeNode root = new TreeNode(val);
    root.left = robot(preorder, min, val);
    root.right = robot(preorder, val, max);
    return root;
}
複製代碼

序列化和反序列化二叉搜索樹

  1. 序列化和反序列化二叉搜索樹

序列化是將數據結構或對象轉換爲一系列位的過程,以便它能夠存儲在文件或內存緩衝區中,或經過網絡鏈接鏈路傳輸,以便稍後在同一個或另外一個計算機環境中重建。

設計一個算法來序列化和反序列化二叉搜索樹。 對序列化/反序列化算法的工做方式沒有限制。 您只需確保二叉搜索樹能夠序列化爲字符串,而且能夠將該字符串反序列化爲最初的二叉搜索樹。

編碼的字符串應儘量緊湊

注意:不要使用類成員/全局/靜態變量來存儲狀態。 你的序列化和反序列化算法應該是無狀態的。

public class Codec {

    // Encodes a tree to a single string.
    // BST 的前序遍歷
    public String serialize(TreeNode root) {
        if (root == null) {
            return "";
        }
        StringBuilder sb = new StringBuilder();
        robot(root, sb);
        return sb.substring(0, sb.length() - 1);
    }

    private void robot(TreeNode root, StringBuilder sb) {
        if (root == null) {
            return;
        }
        sb.append(root.val).append(",");
        robot(root.left, sb);
        robot(root.right, sb);
    }

    // Decodes your encoded data to tree.
    public TreeNode deserialize(String data) {
        if (data == null || Objects.equals(data, "")) {
            return null;
        }
        String[] pre = data.split(",");
        return build(pre, 0, pre.length - 1);
    }

    private TreeNode build(String[] pre, int start, int end) {
        if (start > end) {
            return null;
        }
        TreeNode root = new TreeNode(Integer.valueOf(pre[start]));

        // 找到對應的 index
        int index = end + 1;
        for (int i = start + 1; i <= end; i++) {
            if (Integer.valueOf(pre[i]) > root.val) {
                index = i;
                break;
            }
        }

        root.left = build(pre, start + 1, index - 1);
        root.right = build(pre, index, end);
        return root;
    }

}
// Your Codec object will be instantiated and called as such:
// Codec codec = new Codec();
// codec.deserialize(codec.serialize(root));
複製代碼

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

  1. 二叉樹的序列化與反序列化

序列化是將一個數據結構或者對象轉換爲連續的比特位的操做,進而能夠將轉換後的數據存儲在一個文件或者內存中,同時也能夠經過網絡傳輸到另外一個計算機環境,採起相反方式重構獲得原數據。

請設計一個算法來實現二叉樹的序列化與反序列化。這裏不限定你的序列 / 反序列化算法執行邏輯,你只須要保證一個二叉樹能夠被序列化爲一個字符串而且將這個字符串反序列化爲原始的樹結構。

示例:

你能夠將如下二叉樹:

    1
   / \
  2   3
     / \
    4   5

序列化爲 "[1,2,3,null,null,4,5]"
複製代碼

提示: 這與 LeetCode 目前使用的方式一致,詳情請參閱 LeetCode 序列化二叉樹的格式。你並不是必須採起這種方式,你也能夠採用其餘的方法解決這個問題。

說明: 不要使用類的成員 / 全局 / 靜態變量來存儲狀態,你的序列化和反序列化算法應該是無狀態的。

public String serialize(TreeNode root) {

    if (root == null) {
        return "";
    }

    StringBuilder sb = new StringBuilder();

    Deque<TreeNode> deque = new LinkedList<>();
    deque.add(root);

    while (!deque.isEmpty()) {
        TreeNode p = deque.pop();

        if (p == null) {
            sb.append(",#");
        } else {
            sb.append(",").append(p.val);
            deque.add(p.left);
            deque.add(p.right);
        }
    }

    return sb.toString().substring(1);
}

public TreeNode deserialize(String data) {

    if (data == null || Objects.equals(data, "")) {
        return null;
    }

    String[] s = data.split(",");

    TreeNode[] root = new TreeNode[s.length];

    for (int i = 0; i < root.length; i++) {
        if (!Objects.equals(s[i], "#")) {
            root[i] = new TreeNode(Integer.valueOf(s[i]));
        }
    }

    int parent = 0;

    for (int i = 0; parent * 2 + 2 < s.length; i++) {
        if (root[i] != null) {
            root[i].left = root[parent * 2 + 1];
            root[i].right = root[parent * 2 + 2];
            parent++;
        }
    }

    return root[0];
}
複製代碼

公衆號

coding 筆記、點滴記錄,之後的文章也會同步到公衆號(Coding Insight)中,但願你們關注^_^

相關文章
相關標籤/搜索