LeetCode入門指南 之 二叉樹

WINLSR 發表於 2021-08-30
演算法 LeetCode

二叉樹的遍歷

遞迴:

void traverse (TreeNode root) {
    if (root == null) {
        return null;
    }
    //前序遍歷位置
    traverse(root.left);
    //中序遍歷位置
    traverse(root.right);
    //後序遍歷位置
}

144. 二叉樹的前序遍歷

前序非遞迴:

public static List<Integer> preOrder(TreeNode root) {
    if (root == null) {
        return null;
    }

    List<Integer> res = new LinkedList<>();
    Deque<TreeNode> stack = new LinkedList<>();
    stack.push(root);

    while (!stack.isEmpty()) {
        // 先遍歷根結點
        TreeNode node = stack.pop();
        res.add(node.val);

        // 列印順序為:根 左 右,因此先將右子結點入棧
        if (node.right != null) {
            stack.push(node.right);
        }

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

    }

    return res;
}

94. 二叉樹的中序遍歷

中序非遞迴:

 public static List<Integer> infixOrder(TreeNode root) {
     if (root == null) {
        return null;
     }
     
     List<Integer> res = new LinkedList<>();
     TreeNode temp = root;
     Deque<TreeNode> stack = new LinkedList<>();
     while (temp != null || !stack.isEmpty()) {
         while (temp != null) {
             stack.push(temp); // 加入棧
             temp = temp.left; // 到最左邊結點停止
         }

         temp = stack.pop();   // 訪問棧頂元素
         res.add(temp.val);

         temp = temp.right;    //下一個遍歷的元素是temp的右子樹的最左邊結點
     }

     return res;
 }

145. 二叉樹的後序遍歷

後序非遞迴:

// 後序可參照前序:後序為:左右根,我們只需按照:根右左遍歷然後翻轉即可
public static List<Integer> postOrder(TreeNode root) {
    if (root == null) {
        return null;
    }

    LinkedList<Integer> res = new LinkedList<>();
    Deque<TreeNode> stack = new LinkedList<>();
    stack.push(root);
    while (!stack.isEmpty()) {
        TreeNode temp = stack.pop();
        // 每次新增到頭,最後輸出的結果自然為:根右左的逆序
        res.addFirst(temp.val);

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

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

    return res;
}

注意:如果非遞迴解法難以理解,可以先按照上面的程式碼結合案例手推一下。重要的還是要先形成模板並記憶,間隔著多做幾次也就慢慢理解了。

102. 二叉樹的層序遍歷

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

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> res = new LinkedList<>();
        if (root == null) {
            return res;
        }
		// 建立佇列並加入頭結點
        Deque<TreeNode> queue = new LinkedList<>();
        queue.offer(root);

        while (!queue.isEmpty()) {
            // 獲取當前層的結點個數
            int size = queue.size();
            List<Integer> rowList = new LinkedList<>();

            // 將當前層結點按照先進先出(從左至右)的方式出隊,同時將非空子結點加入隊尾
            for (int i = 0; i < size; i++) {
                TreeNode node = queue.poll();
                rowList.add(node.val);

                if (node.left != null) {
                    queue.offer(node.left);
                }

                if (node.right != null) {
                    queue.offer(node.right);
                }
            }
			
            res.add(rowList);
        }

        return res;
    }
}

104. 二叉樹的最大深度

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

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

lass Solution {
    /**
     * 定義:
     *      返回以root為根結點的最大深度
     */
    public int maxDepth(TreeNode root) {
        //base case, root為空說明樹的高度為0,退出遞迴
        if (root == null) {
            return 0;
        }

        /**
         *  根據定義,就根節點來說,樹的最大深度為:
         *    左右子樹的最大深度中的最大值 + 1
         */
        int left = maxDepth(root.left);
        int right = maxDepth(root.right);

        return 1 + Math.max(left, right);
    }
}
  • 二叉樹相關的很多題目都是由二叉樹的三種遞迴遍歷演化而來
  • 此題其實就是二叉樹的後序遍歷演化而來,要知道當前二叉樹的最大深度自然要先知道兩棵子樹的最大深度,因此用後序遍歷(自底向上)
  • 編寫遞迴程式切記不要用腦袋模擬遞迴棧,函式定義好後,根據定義編寫程式碼即可

110. 平衡二叉樹

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

class Solution {
    //先假定是平衡二叉樹
    private boolean balance = true;
    public boolean isBalanced(TreeNode root) {
        height(root);
        return balance;
    }

    //後序遍歷而來,自底向上獲取兩棵子樹的高度,並檢查節點左右子樹高度只差是否小於等於1
    private int height(TreeNode root) {
        if (root == null) {
            return 0;
        }

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

        if (Math.abs(left - right) > 1) {
            balance = false;
        }

        return Math.max(left, right) + 1;
    }
}

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

路徑 被定義為一條從樹中任意節點出發,沿父節點 - 子節點連線,達到任意節點的序列。同一個節點在一條路徑序列中 至多出現一次 。該路徑 至少包含一個 節點,且不一定經過根節點。

路徑和 是路徑中各節點值的總和。

給你一個二叉樹的根節點 root ,返回其 最大路徑和

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public int maxPathSum(TreeNode root) {
        maxSumToDescendant(root);
        return maxPathSum;
    }
    
	//先將 樹的最大路徑和 初始化為最小值
    private int maxPathSum = Integer.MIN_VALUE;
    
    // 函式定義:當前結點到 子孫(不一定包含葉子結點) 結點的最大路徑和(最少為其自身一個結點)
    private int maxSumToDescendant(TreeNode root) {
        if (root == null) {
            return 0;
        }
		
        // 小於0則認為對最大路徑和沒有貢獻
        int left = Math.max(0, maxSumToDescendant(root.left));
        int right = Math.max(0, maxSumToDescendant(root.right));
        
        // 自底向上返回的過程中順帶計算 樹的最大路徑和(當前結點到左邊子孫結點的最大路徑和 + 當前結點 + 當前結點到右邊子孫結點的最大路徑和)
        maxPathSum = Math.max(maxPathSum, left + root.val + right);

        return root.val + Math.max(left, right);
    }
}

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

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

思路:後序遍歷演變而來,若找到其中一個結點就自底向上返回。若p、q互不為對方子樹中的結點,p、q最終會在某個結點相遇,該節點就為最近公共祖先;否則p或q即為最近公共結點。

class Solution {
    //重要已知:p != q
    //     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 ? left : right;
    }
}

107. 二叉樹的層序遍歷 II

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

class Solution {
    public List<List<Integer>> levelOrderBottom(TreeNode root) {
        LinkedList<List<Integer>> res = new LinkedList<>();
        if (root == null) {
            return res;
        }

        Deque<TreeNode> queue = new LinkedList<>();
        queue.offerLast(root);

        while (!queue.isEmpty()) {
            //獲取當前層的結點數量
            int size = queue.size();

            //暫存當前層的所有結點
            List<Integer> tempList = new LinkedList<>();
            TreeNode tempNode;
            for (int i = 0; i < size; i++) {
                tempNode = queue.poll();
                tempList.add(tempNode.val);

                if (tempNode.left != null) {
                    queue.offer(tempNode.left);
                }

                if (tempNode.right != null) {
                    queue.offer(tempNode.right);
                }
            }

            //將每一層的結果 逆序 放入最終的list中
            res.addFirst(tempList);
        }
        return res;
    }
}

103. 二叉樹的鋸齒形層序遍歷

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

class Solution {
    public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
        List<List<Integer>> res = new LinkedList<>();
        if (root == null) {
            return res;
        }

        Deque<TreeNode> queue = new LinkedList<>();
        queue.offerLast(root);
        boolean flag = true;

        while(!queue.isEmpty()) {
            //獲取當前層的結點數量
            int size = queue.size();

            LinkedList<Integer> tempList = new LinkedList<>();
            TreeNode tempNode;
            for (int i = 0; i < size; i++) {
                tempNode = queue.pollFirst();

                if (flag == true) {
                    //從前往後,順序存放
                    tempList.addLast(tempNode.val);
                } else {
                    //從前往後,逆序存放
                    tempList.addFirst(tempNode.val);
                }

                if (tempNode.left != null) {
                    queue.offerLast(tempNode.left);
                }

                if (tempNode.right != null) {
                    queue.offerLast(tempNode.right);
                }
            }

            res.add(tempList);
            //每遍歷完一層切換flag
            flag = !flag;
        }
        return res;
    }
}

二叉搜尋樹

98. 驗證二叉搜尋樹

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

假設一個二叉搜尋樹具有如下特徵:

  • 節點的左子樹只包含小於當前節點的數。
  • 節點的右子樹只包含大於當前節點的數。
  • 所有左子樹和右子樹自身必須也是二叉搜尋樹。
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public boolean isValidBST(TreeNode root) {
        return isValidBST(root, null, null);
    }

    //定義:當前結點為根結點的二叉樹是否為二叉搜尋樹。二叉搜尋樹的每個結點都有一個上下界(除了根節點)
    private boolean isValidBST(TreeNode node, TreeNode low, TreeNode high) {
        //base case
        if (node == null) {
            return true;
        }

        //base case,當前結點小於等於下界或大於等於上界都不滿足二叉搜尋樹
        if (low != null && node.val <= low.val) return false;
        if (high != null && node.val >= high.val) return false;

        boolean ret = isValidBST(node.left, low, node) && isValidBST(node.right, node, high);
        return ret;
    }
}

推薦題解:驗證二叉搜尋樹(BST:給子樹上所有節點都加一個邊界☀)

701. 二叉搜尋樹中的插入操作

給定二叉搜尋樹(BST)的根節點和要插入樹中的值,將值插入二叉搜尋樹。 返回插入後二叉搜尋樹的根節點。 輸入資料 保證 ,新值和原始二叉搜尋樹中的任意節點值都不同。

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

class Solution {
    // 定義:將當前值插入以當前結點為根的二叉搜尋樹並返回根節點
    public TreeNode insertIntoBST(TreeNode root, int val) {
        if (root == null) {
            return new TreeNode(val);
        }

        // 一定要根據定義來編寫遞迴程式碼
        if (root.val > val) {
            // val 小於當前結點值則插入左子樹
            root.left = insertIntoBST(root.left, val);
        } else {
            root.right = insertIntoBST(root.right, val);
        }

        return root;
    } 
}

450. 刪除二叉搜尋樹中的節點

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

一般來說,刪除節點可分為兩個步驟:

  1. 首先找到需要刪除的節點;
  2. 如果找到了,刪除它。

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

class Solution {
    // 定義:刪除當前結點為根結點的二叉搜尋樹中值為 key 的結點
    public TreeNode deleteNode(TreeNode root, int key) {
        // base case
        if (root == null) {
            return root;
        }

        // 切記根據定義編寫遞迴程式碼
        if (root.val == key) {
            //base case, 刪除結點是葉子結點或只是有一個子樹的非葉結點
            if (root.left == null) return root.right;
            if (root.right == null) return root.left;

            // 有兩個子樹的非葉子結點,用右子樹的最小結點替換當前結點,然後刪除右子樹最小結點
            TreeNode node = getMin(root.right);
            root.val = node.val;

            root.right = deleteNode(root.right, node.val);

          // key 大於當前結點值 則根據定義在右子樹中刪除
        } else if (root.val < key) {
            root.right = deleteNode(root.right, key);
        } else if (root.val > key){
            root.left = deleteNode(root.left, key);
        }

        return root;
    }

    // 獲取root為根的子樹的最小結點(最左邊結點)
    private TreeNode getMin(TreeNode root) {
        while(root.left != null) {
            root = root.left;
        }

        return root;
    }
}