1. 程式人生 > 其它 >LeetCode 6. 二叉樹(一)

LeetCode 6. 二叉樹(一)

刷題順序來自:程式碼隨想錄

目錄

二叉樹的遞迴遍歷

144. 二叉樹的前序遍歷

給你二叉樹的根節點 root ,返回它節點值的 前序 遍歷。

public List<Integer> preorderTraversal(TreeNode root) {
    ArrayList<Integer> list = new ArrayList<>();
    preOrder(list, root);

    return list;
}

private void preOrder(ArrayList<Integer> list, TreeNode root) {
    if(root!=null) {
        list.add(root.val);
        preOrder(list, root.left);
        preOrder(list, root.right);
    }
}

145. 二叉樹的後序遍歷

    public List<Integer> postorderTraversal(TreeNode root) {
        ArrayList<Integer> list  = new ArrayList<>();
        postOrder(list, root);

        return list;
    }

    private void postOrder(ArrayList<Integer> list, TreeNode root) {
        if(root != null) {
            postOrder(list, root.left);
            postOrder(list, root.right);
            list.add(root.val);
        }
    }

94. 二叉樹的中序遍歷

public List<Integer> inorderTraversal(TreeNode root) {
    ArrayList<Integer> list = new ArrayList<>();
    inOrder(list, root);

    return list;
}

private void inOrder(ArrayList<Integer> list, TreeNode root) {
    if(root != null) {
        inOrder(list, root.left);
        list.add(root.val);
        inOrder(list, root.right);
    }
}

二叉樹的迭代遍歷

144. 二叉樹的前序遍歷

彈出棧頂元素,輸出棧頂的值,壓入右節點、左節點。

public List<Integer> preorderTraversal(TreeNode root) {
    ArrayList<Integer> list = new ArrayList<>();
    Stack<TreeNode> stack = new Stack<>();

    if(root != null) {
        stack.push(root);
    }

    while(!stack.empty()) {
        TreeNode node = stack.pop();
        if(node.right != null) {
            stack.push(node.right);
        }
        if(node.left != null) {
            stack.push(node.left);
        }
        list.add(node.val);
    }

    return list;
}

可以採取統一風格的寫法,其主要思想是:

  • 當棧頂元素沒有被訪問過,將棧頂元素彈出,按倒序壓入(例如前序,那就按右左中的順序壓入),並且在壓入中節點(當前節點)時,在其後壓入null表示該節點已經被訪問過了
  • 當棧頂元素為null,表示該節點被訪問過,連續彈出2次並輸出值
public List<Integer> preorderTraversal(TreeNode root) {
    ArrayList<Integer> list = new ArrayList<>();
    Stack<TreeNode> stack = new Stack<>();

    if(root != null) {
        stack.push(root);
    }

    while(!stack.empty()) {
        if(stack.peek() != null) {
            TreeNode node = stack.pop();
            if(node.right != null) {
                stack.push(node.right);
            }
            if(node.left != null) {
                stack.push(node.left);
            }
            stack.push(node);
            stack.push(null);
        }
        else {
            stack.pop();
            list.add(stack.pop().val);
        }
    }

    return list;
}

145. 二叉樹的後序遍歷

彈出節點棧頂元素,壓入左節點、右節點,將棧頂元素壓入輸出棧。依次彈出輸出棧的值。

public List<Integer> postorderTraversal(TreeNode root) {
    ArrayList<Integer> list  = new ArrayList<>();

    Stack<TreeNode> nodeStack = new Stack<>();
    Stack<Integer> outputStack = new Stack<>();

    if(root != null) {
        nodeStack.push(root);
    }

    while(!nodeStack.empty()) {
        TreeNode node = nodeStack.pop();
        if(node.left != null) {
            nodeStack.push(node.left);
        }
        if(node.right != null) {
            nodeStack.push(node.right);
        }            
        outputStack.push(node.val);
    }

    while(!outputStack.empty()) {
        list.add(outputStack.pop());
    }

    return list;
}

94. 二叉樹的中序遍歷

一直向棧中壓左節點直到當前節點為空,彈出棧頂節點,壓右節點,重複。

public List<Integer> inorderTraversal(TreeNode root) {
    ArrayList<Integer> list = new ArrayList<>();

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

    if(root != null) {
        stack.push(root);
    }

    TreeNode curr = root;
    while(!stack.empty()) {
        if(curr != null) {
            curr = curr.left;
        }
        else {
            curr = stack.pop();
            list.add(curr.val);
            curr = curr.right;
        }
        if(curr != null) {
            stack.push(curr);
        }
    }

    return list;
}

二叉樹層序遍歷

102. 二叉樹的層序遍歷

給你一個二叉樹,請你返回其按 層序遍歷 得到的節點值。 (即逐層地,從左到右訪問所有節點)。

主要思想是通過FIFO的佇列遍歷。

public List<List<Integer>> levelOrder(TreeNode root) {
    ArrayList<List<Integer>> res = new ArrayList<>();
    LinkedList<TreeNode> currLayer = new LinkedList<>();

    if(root != null) {
        currLayer.add(root);
    }
	
    // 每次迴圈遍歷一層
    while(currLayer.size() != 0) {
        currLayer.add(null);  // 在當前層最後加一個null的標誌
        ArrayList<Integer> list = new ArrayList<>(); // 儲存當前層遍歷的結果
		
        // 除了加null標誌,還可以控制迴圈次數等於currLayer.size()
        while(currLayer.getFirst() != null) {
            TreeNode node = currLayer.removeFirst();
            list.add(node.val);
            
            // 左右節點新增到隊尾(與當前層有null標誌隔開)
            if(node.left != null) {
                currLayer.add(node.left);
            } 
            if(node.right != null) {
                currLayer.add(node.right);
            }
        }
        currLayer.removeFirst();  // 移除之前新增的null標誌

        res.add(list);  // 儲存當前層的遍歷結果
    }

    return res;
}

通過遞迴的方式遍歷。

public List<List<Integer>> levelOrder(TreeNode root) {
    ArrayList<List<Integer>> res = new ArrayList<>();
    level(res, root, 1);

    return res;
}

private void level(List<List<Integer>> res, TreeNode node, int deep) {
    if(node == null) return;
	
    // 當前節點是該層第一個節點,需要例項化一個數組
    if(res.size() < deep) {
        res.add(new ArrayList<Integer>());
    }

    res.get(deep-1).add(node.val);

    level(res, node.left, deep+1);
    level(res, node.right, deep+1);
}

107. 二叉樹的層序遍歷 II

給定一個二叉樹,返回其節點值自底向上的層序遍歷。 (即按從葉子節點所在層到根節點所在的層,逐層從左向右遍歷)。

和上一題類似,只需要把輸出結果在棧中反轉即可,或者直接呼叫Collections.reverse()方法。

public List<List<Integer>> levelOrderBottom(TreeNode root) {
    ArrayList<List<Integer>> res = new ArrayList<>();
    LinkedList<TreeNode> currLayer = new LinkedList<>();

    if(root == null) {
        return res;
    }
    else {
        currLayer.add(root);
    }

    Stack<List<Integer>> outputStack = new Stack<>();  // 
    while(currLayer.size() != 0) {
        int count = currLayer.size();
        ArrayList<Integer> list = new ArrayList<>();

        for(int i = 0; i < count; i++) {
            TreeNode node = currLayer.removeFirst();
            list.add(node.val);
            if(node.left != null) {
                currLayer.add(node.left);
            }
            if(node.right != null) {
                currLayer.add(node.right);
            }
        }

        outputStack.push(list);  // 先將輸出結果放入棧
    }

    // 輸出結果反轉
    while(!outputStack.empty()) {
        res.add(outputStack.pop());
    }

    return res;
}

199. 二叉樹的右檢視

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

public List<Integer> rightSideView(TreeNode root) {
    ArrayList<Integer> res = new ArrayList<>();
    LinkedList<TreeNode> currLayer = new LinkedList<>();

    if(root == null) {
        return res;
    }
    else {
        currLayer.add(root);
    }

    while(currLayer.size() > 0) {
        int len = currLayer.size();

        for(int i = 0; i < len; i++) {
            TreeNode node = currLayer.removeFirst();
            if(node.left != null) {
                currLayer.add(node.left);
            }
            if(node.right != null) {
                currLayer.add(node.right);
            }
            
            // 新增最右側節點的值
            if(i == len - 1) {
                res.add(node.val);
            }
        }
    }
    return res;
}

637. 二叉樹的層平均值

給定一個非空二叉樹, 返回一個由每層節點平均值組成的陣列。

public List<Double> averageOfLevels(TreeNode root) {
    ArrayList<Double> res = new ArrayList<>();
    LinkedList<TreeNode> currLayer = new LinkedList<>();

    if(root != null) {
        currLayer.add(root);
    }

    while(currLayer.size() != 0) {
        int len = currLayer.size();
        double sum = 0;  // 當前層累加和
        for(int i = 0; i < len; i++) {
            TreeNode node = currLayer.removeFirst();

            if(node.left != null) {
                currLayer.add(node.left);
            }
            if(node.right != null) {
                currLayer.add(node.right);
            }

            sum += node.val;
        }
        res.add(sum / len);
    }

    return res;
}

429. N 叉樹的層序遍歷

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

public List<List<Integer>> levelOrder(Node root) {
    ArrayList<List<Integer>> res = new ArrayList<>();
    LinkedList<Node> currLayer = new LinkedList<>();

    if(root != null) {
        currLayer.add(root);
    }

    while(currLayer.size() != 0) {
        int len = currLayer.size();
        ArrayList<Integer> list = new ArrayList<>();

        for(int i = 0; i < len; i++) {
            Node node = currLayer.removeFirst();
            list.add(node.val);
			
            // 將孩子節點依次加入佇列
            List<Node> children = node.children;
            if(children != null) {
                for(Node child: children) {
                    currLayer.add(child);
                }
            }
        }
        res.add(list);
    }
    return res;
}

515. 在每個樹行中找最大值

給定一棵二叉樹的根節點 root ,請找出該二叉樹中每一層的最大值。

public List<Integer> largestValues(TreeNode root) {
    ArrayList<Integer> res = new ArrayList<Integer>();
    LinkedList<TreeNode> currLayer = new LinkedList<>();

    if(root != null) {
        currLayer.add(root);
    }

    while(currLayer.size() != 0) {
        int len = currLayer.size();
        int max = currLayer.peekFirst().val;

        for(int i = 0; i < len; i++) {
            TreeNode node = currLayer.removeFirst();
            if(node.left != null) {
                currLayer.add(node.left);
            }
            if(node.right != null) {
                currLayer.add(node.right);
            }
            if(max < node.val) {
                max = node.val;
            }
        }
        res.add(max);
    }
    return res;
}

116. 填充每個節點的下一個右側節點指標

給定一個 完美二叉樹 ,其所有葉子節點都在同一層,每個父節點都有兩個子節點。二叉樹定義如下:

struct Node {
  int val;
  Node *left;
  Node *right;
  Node *next;
}

填充它的每個 next 指標,讓這個指標指向其下一個右側節點。如果找不到下一個右側節點,則將 next 指標設定為 NULL

public Node connect(Node root) {
    LinkedList<Node> currLayer = new LinkedList<>();

    if(root != null) {
        currLayer.add(root);
    }

    while(currLayer.size() != 0) {
        int len = currLayer.size();
        for(int i = 0; i < len; i++) {
            Node node = currLayer.removeFirst();
            if(node.left != null) {
                currLayer.add(node.left);
            }
            if(node.right != null) {
                currLayer.add(node.right);
            }
            if(i != len - 1) {
                node.next = currLayer.peekFirst();
            }
        }
    }
    return root;
}

也可以用遞迴的方法。

public Node connect(Node root) {
    if(root == null || root.left == null) {
        return root;
    }

    root.left.next = root.right;

    if(root.next != null) {
        root.right.next = root.next.left;
    }

    connect(root.left);
    connect(root.right);

    return root;
}

117. 填充每個節點的下一個右側節點指標 II

給定一個二叉樹。二叉樹定義如下:

struct Node {
  int val;
  Node *left;
  Node *right;
  Node *next;
}

填充它的每個 next 指標,讓這個指標指向其下一個右側節點。如果找不到下一個右側節點,則將 next 指標設定為 NULL

與上一題第一個層級遍歷的程式碼相同。

翻轉二叉樹

226. 翻轉二叉樹

翻轉一棵二叉樹。

遞迴

public TreeNode invertTree(TreeNode root) {
    if(root == null) {
        return root;
    }

    TreeNode temp = root.left;
    root.left = root.right;
    root.right = temp;

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

    return root;
}

廣度優先

使用FIFO佇列層級遍歷,每次遍歷時翻轉節點的左右子樹。

深度優先

使用棧遍歷,每次遍歷時翻轉節點的左右子樹。

對稱二叉樹

101. 對稱二叉樹

給定一個二叉樹,檢查它是否是映象對稱的。

public boolean isSymmetric(TreeNode root) {
    if(root == null) {
        return true;
    }
    else {
        return symmetric(root.left, root.right);
    }
}

// 比較tree1和tree2是否是對稱的
private boolean symmetric(TreeNode tree1, TreeNode tree2) {
    if(tree1 == null && tree2 == null) {  // 都為null
        return true;
    }

    if(tree1 == null || tree2 == null) {  // 其中有一個為null
        return false;
    }
	
    // 比較根節點的值
    // 如果相同,則比較tree1的左子樹與tree2的右子樹,tree1的右子樹與tree2的左子樹
    return tree1.val == tree2.val && symmetric(tree1.left, tree2.right) && symmetric(tree1.right, tree2.left);
}

100. 相同的樹

給你兩棵二叉樹的根節點 pq ,編寫一個函式來檢驗這兩棵樹是否相同。

與上一題類似的遞迴演算法。

public boolean isSameTree(TreeNode p, TreeNode q) {
    if(p == null && q == null) {
        return true;
    }
    if(p == null || q == null) {
        return false;
    }

    return p.val == q.val && isSameTree(p.left, q.left) && isSameTree(p.right, q.right);
}

572. 另一棵樹的子樹

給你兩棵二叉樹 rootsubRoot 。檢驗 root 中是否包含和 subRoot 具有相同結構和節點值的子樹。如果存在,返回 true ;否則,返回 false

二叉樹 tree 的一棵子樹包括 tree 的某個節點和這個節點的所有後代節點。tree 也可以看做它自身的一棵子樹。

public boolean isSubtree(TreeNode root, TreeNode subRoot) {
    if(isSameTree(root, subRoot)) {
        return true;
    }
    
    if(root == null) {
        return false;
    }
	
    // 左子樹和右子樹之間有一個存在該子樹即可
    return isSubtree(root.left, subRoot) || isSubtree(root.right, subRoot);
}

// 判斷tree1與tree2是否相等
private boolean isSameTree(TreeNode tree1, TreeNode tree2) {
    if(tree1 == null && tree2 == null) {
        return true;
    }
    if(tree1 == null || tree2 == null) {
        return false;
    }
    return tree1.val == tree2.val && isSameTree(tree1.left, tree2.left) && isSameTree(tree1.right, tree2.right);
}

二叉樹的深度

104. 二叉樹的最大深度

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

使用遞迴方法,也可以用佇列層級遍歷。

public int maxDepth(TreeNode root) {
    return depth(root, 0);
}

private int depth(TreeNode root, int deep) {
    if(root == null) {
        return deep;
    }
    else {
        return Math.max(depth(root.left, deep + 1), depth(root.right, deep + 1));
    }
}

559. N 叉樹的最大深度

給定一個 N 叉樹,找到其最大深度。

遞迴方法:

public int maxDepth(Node root) {
    if(root == null) {
        return 0;
    }
    
    // 最大深度等於所有兒子節點中的最大深度+1
    int max = 0;
    for(Node child: root.children) {
        int depth = maxDepth(child);
        if(depth > max) {
            max = depth;
        }
    }
    return max + 1; 
}

111. 二叉樹的最小深度

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

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

遞迴

public int minDepth(TreeNode root) {
    if(root == null) {
        return 0;
    }

    if(root.left == null && root.right == null) {
        return 1;
    }
	
    // 左右子樹都不為null,則最小深度位較小子樹深度+1
    if(root.left != null && root.right != null) {
        return Math.min(minDepth(root.left), minDepth(root.right)) + 1;
    }
	
    // 左右子樹有一個為null,則最小深度為不為空子樹的深度+1
    if(root.left == null) {
        return minDepth(root.right) + 1;
    }
    else {
        return minDepth(root.left) + 1;
    }
}

一種更簡潔的寫法:

public int minDepth(TreeNode root) {
    if(root == null) {
        return 0;
    }

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

    if(left == 0 || right == 0) {
        return left + right + 1;
    }
    else {
        return Math.min(left, right) + 1;
    }
}

迭代

使用佇列層級遍歷,當遇到第一個葉子節點時返回。

public int minDepth(TreeNode root) {
    if(root == null) {
        return 0;
    }

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

    int depth = 0;
    while(currLayer.size() != 0) {
        depth++;
        int len = currLayer.size();
        for(int i = 0; i < len; i++) {
            TreeNode node = currLayer.removeFirst();
            if(node.left == null && node.right == null) {  // 遇到葉子節點
                currLayer.clear();  // 觸發while迴圈終止條件
                break;
            }
            if(node.left != null) currLayer.add(node.left);
            if(node.right != null) currLayer.add(node.right);
        }
    }

    return depth;
}

完全二叉樹節點個數

222. 完全二叉樹的節點個數

給你一棵 完全二叉樹 的根節點 root ,求出該樹的節點個數。

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

遞迴

節點個數=左子樹個數+右子樹個數+1。

public int countNodes(TreeNode root) {
    if(root == null) return 0;

    int left = countNodes(root.left);
    int right = countNodes(root.right);
    return left + right + 1;
}

迭代

可以繼續用層級遍歷的方法,但是效率太低(4ms)。可以先計算二叉樹的深度(一直搜尋最左邊的節點),然後計算最後一層節點的個數,因為除了最後一層, 之前層都是滿節點的,這樣效率更高(0ms)。

public int countNodes(TreeNode root) {
    if(root == null) return 0;
    int depth = 1;
    TreeNode node = root;
    while(node.left != null) {  // 計算深度
        depth++;
        node = node.left;
    }

    ArrayList<TreeNode> list = new ArrayList<>();
    lastLayer(list, root, depth);
	
    // 除了最後一層的節點數量+最後一層節點數量
    return (int) Math.pow(2, depth - 1) - 1 + list.size();
}

// 將最後一層節點加入list
private void lastLayer(ArrayList<TreeNode> list, TreeNode root, int depth) {
    if(root == null) return;

    if(depth == 1) {
        list.add(root);
    }
    else {
        lastLayer(list, root.left, depth-1);
        lastLayer(list, root.right, depth-1);
    }
}

平衡二叉樹

110. 平衡二叉樹

給定一個二叉樹,判斷它是否是高度平衡的二叉樹。本題中,一棵高度平衡二叉樹定義為:一個二叉樹每個節點 的左右兩個子樹的高度差的絕對值不超過 1 。

自頂向下遞迴

如果一個二叉樹左子樹和右子樹的高度差不大於1,且左子樹、右子樹都是是平衡的,則這個二叉樹是平衡的。這種做法效率較低,因為節點的高度被重複計算了很多次。

// 左子樹、右子樹高度差<=1,且左子樹、右子樹都是平衡的
public boolean isBalanced(TreeNode root) {
    if(root == null) return true;
    int left = getHeight(root.left);
    int right = getHeight(root.right);
    return Math.abs(left-right) <= 1 && isBalanced(root.left) && isBalanced(root.right);
}

// 左子樹和右子樹較大的高度+1
private int getHeight(TreeNode root) {
    if(root == null) return 0;
    int left = getHeight(root.left);
    int right = getHeight(root.right);
    return Math.max(left, right) + 1;
}

遞迴優化

對上面的演算法做一些剪枝操作,避免重複計算,使用-1標記當前子樹已經不平衡了,如果不平衡,則沒有必要進行計算高度了。

public boolean isBalanced(TreeNode root) {
    return getHeight(root) != -1;
}

private int getHeight(TreeNode root) {
    if(root == null) return 0;
	
    // 左子樹或右子樹已經不平衡了,那麼當前樹也不平衡
    int left = getHeight(root.left);
    if(left == -1) return -1;
    int right = getHeight(root.right);
    if(right == -1) return -1;
	
    if(Math.abs(left - right) > 1) return -1;  // 左子樹、右子樹高度差超過1,當前樹不平衡
    return Math.max(left, right) + 1;  // 如果是平衡的,返回高度
}

二叉樹的所有路徑

257. 二叉樹的所有路徑

給你一個二叉樹的根節點 root ,按 任意順序 ,返回所有從根節點到葉子節點的路徑。葉子節點 是指沒有子節點的節點。

遞迴

public List<String> binaryTreePaths(TreeNode root) {
    ArrayList<String> list = new ArrayList<>();
    getPath(root, list, "");
    return list;
}

private void getPath(TreeNode root, List<String> list, String path) {
    if(root == null) return;

    // 將當前節點新增到路徑中
    path += root.val;

    if(root.left == null && root.right == null) {  // 走到葉子節點返回
        list.add(path);
    }
    else {  // 繼續往左右子樹探索
        path += "->";
        getPath(root.left, list, path);
        getPath(root.right, list, path);
    }
}

優化,使用可變字串StringBuilder,此時要注意需要深拷貝。

public List<String> binaryTreePaths(TreeNode root) {
    ArrayList<String> list = new ArrayList<>();
    getPath(root, list, new StringBuilder(""));
    return list;
}

private void getPath(TreeNode root, List<String> list, StringBuilder path) {
    if(root == null) return;

    path.append(root.val);

    if(root.left == null && root.right == null) {
        list.add(path.toString());
    }
    else {
        path.append("->");
        getPath(root.left, list, new StringBuilder(path.toString()));
        getPath(root.right, list, new StringBuilder(path.toString()));
    }
}