數據結構 - 二叉樹 - 面試中常見的二叉樹算法題

數據結構 - 二叉樹 - 面試中常見的二叉樹算法題

數據結構是面試中必定考查的知識點,面試者需要掌握幾種經典的數據結構:線性表(數組、鏈表)、棧與隊列(二叉樹、二叉查找樹、平衡二叉樹、紅黑樹)、

本文主要介紹中的常見的二叉樹數據結構。包括

  • 概念簡介
  • 二叉樹中樹節點的數據結構(Java)
  • 二叉樹的遍歷(Java)
  • 常見的二叉樹算法題(Java)

概念簡介

如果對二叉樹概念已經基本掌握,可以跳過該部分,直接查看常見鏈表算法題。

二叉樹基本概念

二叉樹在圖論中是這樣定義的:二叉樹是一個連通的無環圖,並且每一個頂點的度不大於3。有根二叉樹還要滿足根結點的度不大於2。有了根結點之後,每個頂點定義了唯一的父結點,和最多2個子結點。二叉樹性質如下:

  • 二叉樹的每個結點至多隻有二棵子樹(不存在度大於2的結點),二叉樹的子樹有左右之分,次序不能顛倒。
  • 二叉樹的第 i 層至多有 2i1 個結點。
  • 深度爲 k 的二叉樹至多有 2k1 個結點。
  • 對任何一棵二叉樹T,如果其終端結點數爲n0 ,度爲2的結點數爲n2 ,則n0=n2+1
  • 一棵深度爲k,且有 2k1 個節點稱之爲滿二叉樹
  • 深度爲k,有n個節點的二叉樹,當且僅當其每一個節點都與深度爲k的滿二叉樹中,序號爲1至n的節點對應時,稱之爲完全二叉樹
  • 平衡二叉樹又被稱爲AVL樹(區別於AVL算法),它是一棵二叉排序樹,且具有以下性質:它是一棵空樹或它的左右兩個子樹的高度差的絕對值不超過1,並且左右兩個子樹都是一棵平衡二叉樹。

二叉樹


二叉樹中樹節點的數據結構

二叉樹由一系列樹結點組成,每個結點包括三個部分:一個是存儲數據元素的數據域,另一個是存儲左子結點地址的指針域,另一個是存儲右子結點地址的指針域。

定義樹節點爲類:TreeNode。具體實現如下:

public class TreeNode {

    public int val; // 數據域
    public TreeNode left; // 左子樹根節點
    public TreeNode right; // 右子樹根節點

    public TreeNode() {

    }

    public TreeNode(int val) {
        this.val = val;
    }

}

二叉樹的遍歷

1. 前序遍歷

遞歸解法

  • 如果二叉樹爲空,空操作
  • 如果二叉樹不爲空,訪問根節點,前序遍歷左子樹,前序遍歷右子樹
/**
 * 1. 前序遍歷
 * 遞歸
 * @param root 樹根節點
 */
public static void preorderTraversalRec(TreeNode root){
   if (root == null) {
       return;
   }
   System.out.print(root.val + "->");
   preorderTraversalRec(root.left);
   preorderTraversalRec(root.right);
}

非遞歸解法:用一個輔助stack,總是先把右孩子放進棧。

/**
 * 1. 前序遍歷
 * 非遞歸
 * @param root 樹根節點
 */
public static void preorderTraversal2(TreeNode root) {
    if (root == null) {
        return;
    }
    Stack<TreeNode> stack = new Stack<>(); // 輔助棧
    TreeNode cur = root;
    while (cur != null || !stack.isEmpty()) {
        while (cur != null) { // 不斷將左子節點入棧,直到cur爲空
            stack.push(cur);
            System.out.print(cur.val + "->"); // 前序遍歷,先打印當前節點在打印左子節點,然後再把右子節點加到棧中
            cur = cur.left;
        }
        if (!stack.isEmpty()) { // 棧不爲空,彈出棧元素
            cur = stack.pop(); // 此時彈出最左邊的節點
            cur = cur.right; // 令當前節點爲右子節點
        }
    }
}

/**
 * 1. 前序遍歷
 * 非遞歸解法2
 * @param root 樹根節點
 */
public static void preorderTraversal(TreeNode root) {
    if (root == null) {
        return;
    }
    Stack<TreeNode> stack = new Stack<>(); // 輔助棧保存樹節點
    stack.add(root);
    while (!stack.isEmpty()) { // 棧不爲空
        TreeNode temp = stack.pop();
        System.out.print(temp.val + "->"); // 先根節點,因爲是前序遍歷
        if (temp.right != null) { // 先添加右孩子,因爲棧是先進後出
            stack.add(temp.right);
        }
        if (temp.left != null) {
            stack.add(temp.left);
        }
    }
}
2. 中序遍歷

遞歸解法

  • 如果二叉樹爲空,空操作
  • 如果二叉樹不爲空,中序遍歷左子樹,訪問根節點,中序遍歷右子樹
/**
 * 2. 中序遍歷
 * 遞歸
 * @param root 樹根節點
 */
public static void inorderTraversalRec(TreeNode root){
    if (root == null) {
        return;
    }
    inorderTraversalRec(root.left);
    System.out.print(root.val + "->");
    inorderTraversalRec(root.right);
}

非遞歸解法:用棧先把根節點的所有左孩子都添加到棧內,然後輸出棧頂元素,再處理棧頂元素的右子樹。

/**
 * 2. 中序遍歷
 * 非遞歸
 * @param root 樹根節點
 */
public static void inorderTraversal(TreeNode root) {
    if (root == null) {
        return;
    }
    Stack<TreeNode> stack = new Stack<>(); // 輔助棧
    TreeNode cur = root;
    while (cur != null || !stack.isEmpty()) {
        while (cur != null) { // 不斷將左子節點入棧,直到cur爲空
            stack.push(cur);
            cur = cur.left;
        }
        if (!stack.isEmpty()) { // 棧不爲空,彈出棧元素
            cur = stack.pop(); // 此時彈出最左邊的節點
            System.out.print(cur.val + "->"); // 中序遍歷,先打印左子節點在打印當前節點,然後再把右子節點加到棧中
            cur = cur.right; // 令當前節點爲右子節點
        }
    }
}
3. 後序遍歷

遞歸解法

  • 如果二叉樹爲空,空操作
  • 如果二叉樹不爲空,後序遍歷左子樹,後序遍歷右子樹,訪問根節點
/**
 * 3. 後序遍歷
 * 遞歸
 * @param root 樹根節點
 */
public static void postorderTraversalRec(TreeNode root){
    if (root == null) {
        return;
    }
    postorderTraversalRec(root.left);
    postorderTraversalRec(root.right);
    System.out.print(root.val + "->");
}

非遞歸解法:雙棧法。

/**
 * 3. 後序遍歷
 * 非遞歸
 * @param root 樹根節點
 */
public static void postorderTraversal(TreeNode root) {
    if(root == null) {
        return;
    }
    Stack<TreeNode> stack1 = new Stack<>(); // 保存樹節點
    Stack<TreeNode> stack2 = new Stack<>(); // 保存後序遍歷的結果
    stack1.add(root);
    while (!stack1.isEmpty()) {
        TreeNode temp = stack1.pop();
        stack2.push(temp); // 將彈出的元素加到stack2中
        if (temp.left != null) { // 左子節點先入棧
            stack1.push(temp.left);
        }
        if (temp.right != null) { // 右子節點後入棧
            stack1.push(temp.right);
        }
    }
    while (!stack2.isEmpty()) {
        System.out.print(stack2.pop().val + "->");
    }
}
4. 層次遍歷

思路:分層遍歷二叉樹(按層次從上到下,從左到右)迭代,相當於廣度優先搜索,使用隊列實現。隊列初始化,將根節點壓入隊列。當隊列不爲空,進行如下操作:彈出一個節點,訪問,若左子節點或右子節點不爲空,將其壓入隊列。

/**
 * 4. 層次遍歷
 * @param root 根節點
 */
public static void levelTraversal(TreeNode root){
    if(root == null) {
        return;
    }
    Queue<TreeNode> queue = new LinkedList<>(); // 對列保存樹節點
    queue.add(root);
    while (!queue.isEmpty()) {
        TreeNode temp = queue.poll();
        System.out.print(temp.val + "->");
        if (temp.left != null) { // 添加左右子節點到對列
            queue.add(temp.left);
        }
        if (temp.right != null) {
            queue.add(temp.right);
        }
    }
}

常見的二叉樹算法題

1. 求二叉樹中的節點個數

遞歸解法O(n)

  • 如果二叉樹爲空,節點個數爲0
  • 如果二叉樹不爲空,二叉樹節點個數 = 左子樹節點個數 + 右子樹節點個數 + 1
/**
 * 1. 求二叉樹中的節點個數
 * 遞歸
 * @param root 樹根節點
 * @return 節點個數
 */
public static int getNodeNumRec(TreeNode root) {
    if (root == null) {
        return 0;
    }
    return getNodeNumRec(root.left) + getNodeNumRec(root.right) + 1;
}

非遞歸解法O(n) 。基本思想同LevelOrderTraversal。即用一個Queue,在Java裏面可以用LinkedList來模擬。

/**
 * 1. 求二叉樹中的節點個數
 * 非遞歸
 * @param root 樹根節點
 * @return 節點個數
 */
public static int getNodeNum(TreeNode root) {
    if (root == null) {
        return 0;
    }
    Queue<TreeNode> queue =  new LinkedList<>(); // 用隊列保存樹節點,先進先出
    queue.add(root);
    int count = 1; // 節點數量
    while (!queue.isEmpty()) {
        TreeNode temp = queue.poll(); // 每次從對列中刪除節點,並返回該節點信息
        if (temp.left != null) { // 添加左子孩子到對列
            queue.add(temp.left);
            count++;
        }
        if (temp.right != null) { // 添加右子孩子到對列
            queue.add(temp.right);
            count++;
        }
    }
    return count;
}
2. 求二叉樹的深度(高度)

遞歸解法O(n)

  • 如果二叉樹爲空,二叉樹的深度爲0
  • 如果二叉樹不爲空,二叉樹的深度 = max(左子樹深度, 右子樹深度) + 1
/**
* 求二叉樹的深度(高度) 
* 遞歸
* @return 樹的深度
*/
public static int getDepthRec(TreeNode root) {
   if (root == null) {
       return 0;
   }
   return Math.max(getDepthRec(root.left), getDepthRec(root.right)) + 1;
}

非遞歸解法O(n) 。基本思想同LevelOrderTraversal。即用一個Queue,在Java裏面可以用LinkedList來模擬。

/**
 * 求二叉樹的深度(高度)
 * 非遞歸
 * @param root 樹根節點
 * @return 樹的深度
 */
public static int getDepth(TreeNode root) {
    if (root == null) {
        return 0;
    }
    int currentLevelCount = 1; // 當前層的節點數量
    int nextLevelCount = 0; // 下一層節點數量
    int depth = 0; // 樹的深度

    Queue<TreeNode> queue = new LinkedList<>(); // 對列保存樹節點
    queue.add(root);
    while (!queue.isEmpty()) {
        TreeNode temp = queue.remove(); // 移除節點
        currentLevelCount--; // 當前層節點數減1
        if (temp.left != null) { // 添加左節點並更新下一層節點個數
            queue.add(temp.left);
            nextLevelCount++;
        }
        if (temp.right != null) { // 添加右節點並更新下一層節點個數
            queue.add(temp.right);
            nextLevelCount++;
        }
        if (currentLevelCount == 0) { // 如果是該層的最後一個節點,樹的深度加1
            depth++;
            currentLevelCount = nextLevelCount; // 更新當前層節點數量並且重置下一層節點數量
            nextLevelCount = 0;
        }
    }
    return depth;
}
3. 求二叉樹第k層的節點個數

遞歸解法O(n)

思路:求以root爲根的k層節點數目,等價於求以root左孩子爲根的k-1層(因爲少了root)節點數目 加上以root右孩子爲根的k-1層(因爲 少了root)節點數目。即:

  • 如果二叉樹爲空或者k<1,返回0
  • 如果二叉樹不爲空並且k==1,返回1
  • 如果二叉樹不爲空且k>1,返回root左子樹中k-1層的節點個數與root右子樹k-1層節點個數之和
/**
 * 求二叉樹第k層的節點個數
 * 遞歸
 * @param root 根節點
 * @param k 第k個節點
 * @return 第k層節點數
 */
public static int getNodeNumKthLevelRec(TreeNode root, int k) {
    if (root == null || k < 1) {
        return 0;
    }
    if (k == 1) {
        return 1;
    }
    return getNodeNumKthLevelRec(root.left, k - 1) + getNodeNumKthLevelRec(root.right, k - 1);
}
4. 求二叉樹中葉子節點的個數

遞歸解法

  • 如果二叉樹爲空,返回0
  • 如果二叉樹是葉子節點,返回1
  • 如果二叉樹不是葉子節點,二叉樹的葉子節點數 = 左子樹葉子節點數 + 右子樹葉子節點數
/**
 * 4. 求二叉樹中葉子節點的個數
 * 遞歸
 * @param root 根節點
 * @return 葉子節點個數
 */
public static int getNodeNumLeafRec(TreeNode root) {
    if (root == null) {
        return 0;
    }
    if (root.left == null && root.right == null) {
        return 1;
    }
    return getNodeNumLeafRec(root.left) + getNodeNumLeafRec(root.right);
}

非遞歸解法:基於層次遍歷進行求解,利用Queue進行。

 /**
 * 4. 求二叉樹中葉子節點的個數(迭代)
 * 非遞歸
 * @param root 根節點
 * @return 葉子節點個數
 */
public static int getNodeNumLeaf(TreeNode root){
    if (root == null) {
        return 0;
    }
    int leaf = 0; // 葉子節點個數
    Queue<TreeNode> queue = new LinkedList<>();
    queue.add(root);
    while (!queue.isEmpty()) {
        TreeNode temp = queue.poll();
        if (temp.left == null && temp.right == null) { // 葉子節點
            leaf++;
        }
        if (temp.left != null) {
            queue.add(temp.left);
        }
        if (temp.right != null) {
            queue.add(temp.right);
        }
    }
    return leaf;
}
5. 判斷兩棵二叉樹是否相同的樹

遞歸解法

  • 如果兩棵二叉樹都爲空,返回真
  • 如果兩棵二叉樹一棵爲空,另外一棵不爲空,返回假
  • 如果兩棵二叉樹都不爲空,如果對應的左子樹和右子樹都同構返回真,其他返回假
/**
 * 5. 判斷兩棵二叉樹是否相同的樹。
 * 遞歸
 * @param r1 二叉樹1
 * @param r2 二叉樹2
 * @return 是否相同
 */
public static boolean isSameRec(TreeNode r1, TreeNode r2) {
    if (r1 == null && r2 == null) { // 都是空
        return true;
    } else if (r1 == null || r2 == null) { // 有一個爲空,一個不爲空
        return false;
    }
    if (r1.val != r2.val) { // 兩個不爲空,但是值不相同
        return false;
    }
    return isSameRec(r1.left, r2.left) && isSameRec(r1.right, r2.right); // 遞歸遍歷左右子節點
}

非遞歸解法:利用Stack對兩棵樹對應位置上的節點進行判斷是否相同。

/**
 * 5. 判斷兩棵二叉樹是否相同的樹(迭代)
 * 非遞歸
 * @param r1 二叉樹1
 * @param r2 二叉樹2
 * @return 是否相同
 */
public static boolean isSame(TreeNode r1, TreeNode r2){
    if (r1 == null && r2 == null) { // 都是空
        return true;
    } else if (r1 == null || r2 == null) { // 有一個爲空,一個不爲空
        return false;
    }
    Stack<TreeNode> stack1 = new Stack<>();
    Stack<TreeNode> stack2 = new Stack<>();
    stack1.add(r1);
    stack2.add(r2);
    while (!stack1.isEmpty() && !stack2.isEmpty()) {
        TreeNode temp1 = stack1.pop();
        TreeNode temp2 = stack2.pop();
        if (temp1 == null && temp2 == null) { // 兩個元素都爲空,因爲添加的時候沒有對空節點做判斷
            continue;
        } else if (temp1 != null && temp2 != null && temp1.val == temp2.val) {
            stack1.push(temp1.left); // 相等則添加左右子節點判斷
            stack1.push(temp1.right);
            stack2.push(temp2.left);
            stack2.push(temp2.right);
        } else {
            return false;
        }
    }
    return true;
}
6. 判斷二叉樹是不是平衡二叉樹

遞歸實現:藉助前面實現好的求二叉樹高度的函數

  • 如果二叉樹爲空, 返回真
  • 如果二叉樹不爲空,如果左子樹和右子樹都是AVL樹並且左子樹和右子樹高度相差不大於1,返回真,其他返回假
/**
 * 6. 判斷二叉樹是不是平衡二叉樹
 * 遞歸
 * @param root 根節點
 * @return 是否二叉平衡樹(AVL樹)
 */
public static boolean isAVLTree(TreeNode root) {
    if (root == null) {
        return true;
    }
    if (Math.abs(getDepth(root.left) - getDepth(root.right)) > 1) { // 左右子樹高度差大於1
        return false;
    }
    return isAVLTree(root.left) && isAVLTree(root.right); // 遞歸判斷左右子樹
}
7. 求二叉樹的鏡像

遞歸實現:破壞原來的樹,把原來的樹改成其鏡像

  • 如果二叉樹爲空,返回空
  • 如果二叉樹不爲空,求左子樹和右子樹的鏡像,然後交換左右子樹
/**
 * 7. 求二叉樹的鏡像
 * 遞歸
 * @param root 根節點
 * @return 鏡像二叉樹的根節點
 */
public static TreeNode mirrorRec(TreeNode root) {
    if (root == null) {
        return root;
    }
    TreeNode left = mirrorRec(root.right); // 遞歸鏡像左右子樹
    TreeNode right = mirrorRec(root.left);
    root.left = left; // 更新根節點的左右子樹爲鏡像後的樹
    root.right = right;
    return root;
}

遞歸實現:不能破壞原來的樹,返回一個新的鏡像樹

  • 如果二叉樹爲空,返回空
  • 如果二叉樹不爲空,求左子樹和右子樹的鏡像,然後交換左右子樹
/**
 * 7. 求二叉樹的鏡像
 * 遞歸
 * @param root 根節點
 * @return 鏡像二叉樹的根節點
 */
public static TreeNode mirrorCopyRec(TreeNode root) {
    if (root == null) {
        return root;
    }
    TreeNode newRoot = new TreeNode(root.val); // 創建新節點,然後交換左右子樹
    newRoot.left = mirrorCopyRec(root.right);
    newRoot.right = mirrorCopyRec(root.left);
    return newRoot;
}

非遞歸實現:破壞原來的樹,把原來的樹改成其鏡像

/**
 * 7. 求二叉樹的鏡像
 * 非遞歸
 * @param root 根節點
 * @return 鏡像二叉樹的根節點
 */
public static void mirror(TreeNode root) {
    if (root == null) {
        return ;
    }
    Stack<TreeNode> stack = new Stack<>();
    stack.push(root);
    while (!stack.isEmpty()){
        TreeNode cur = stack.pop();
        // 交換左右孩子
        TreeNode tmp = cur.right;
        cur.right = cur.left;
        cur.left = tmp;

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

    }
}

非遞歸實現:不能破壞原來的樹,返回一個新的鏡像樹

/**
 * 7. 求二叉樹的鏡像
 * 非遞歸
 * @param root 根節點
 * @return 鏡像二叉樹的根節點
 */
public static TreeNode mirrorCopy(TreeNode root) {
    if (root == null) {
        return null;
    }
    Stack<TreeNode> stack = new Stack<TreeNode>();
    Stack<TreeNode> newStack = new Stack<TreeNode>();
    stack.push(root);
    TreeNode newRoot = new TreeNode(root.val);
    newStack.push(newRoot);
    while (!stack.isEmpty()) {
        TreeNode cur = stack.pop();
        TreeNode newCur = newStack.pop();
        if (cur.right != null) {
            stack.push(cur.right);
            newCur.left = new TreeNode(cur.right.val);
            newStack.push(newCur.left);
        }
        if (cur.left != null) {
            stack.push(cur.left);
            newCur.right = new TreeNode(cur.left.val);
            newStack.push(newCur.right);
        }
    }
    return newRoot;
}
8. 判斷兩個二叉樹是否互相鏡像

遞歸解法:與比較兩棵二叉樹是否相同解法一致(題5),非遞歸解法省略。

  • 比較r1的左子樹的鏡像是不是r2的右子樹
  • 比較r1的右子樹的鏡像是不是r2的左子樹
/**
 * 8. 判斷兩個樹是否互相鏡像
 * @param r1 二叉樹 1
 * @param r2 二叉樹 2
 * @return 是否互相鏡像
 */
public static boolean isMirrorRec(TreeNode r1, TreeNode r2) {
    if (r1 == null && r2 == null) {
        return true;
    } else if (r1 == null || r2 == null) {
        return false;
    }
    if (r1.val != r2.val) {
        return false;
    }
    // 遞歸比較r1的左子樹的鏡像是不是r2右子樹
    // 和r1的右子樹的鏡像是不是r2的左子樹
    return isMirrorRec(r1.left, r2.right) && isMirrorRec(r1.right, r2.left);
}
9. 求二叉樹中兩個節點的最低公共祖先節點

遞歸解法

  • 如果兩個節點分別在根節點的左子樹和右子樹,則返回根節點
  • 如果兩個節點都在左子樹,則遞歸處理左子樹;如果兩個節點都在右子樹,則遞歸處理右子樹
/**
 * 9. 求二叉樹中兩個節點的最低公共祖先節點
 * 遞歸
 * @param root 樹根節點
 * @param n1 第一個節點
 * @param n2 第二個節點
 * @return 最低公共祖先節點
 */
public static TreeNode getLastCommonParentRec(TreeNode root, TreeNode n1, TreeNode n2) {
    if (findNodeRec(root.left, n1)) { // 如果n1在左子樹
        if (findNodeRec(root.right, n2)) { // 如果n2在右子樹
            return root; // 返回根節點
        } else { // 如果n2也在左子樹
            return getLastCommonParentRec(root.left, n1, n2); // 遞歸處理
        }
    } else { // 如果n1在右子樹
        if (findNodeRec(root.left, n2)) { // 如果n2在左子樹
            return root; // 返回根節點
        } else { // 如果n2在右子樹
            return getLastCommonParentRec(root.right, n1, n2); // 遞歸處理
        }
    }
}

/**
 * 遞歸判斷一個點是否在樹裏
 * @param root 根節點
 * @param node 查找的節點
 * @return 是否找到該節點
 */
private static boolean findNodeRec(TreeNode root, TreeNode node) {
    if (node == null || root == null) {
        return false;
    }
    if (root == node) {
        return true;
    }
    // 先嚐試在左子樹中查找
    boolean found = findNodeRec(root.left, node);
    if (!found) { // 如果查找不到,再在右子樹中查找
        found = findNodeRec(root.right, node);
    }
    return found;
}

/**
 * 9. 樹中兩個節點的最低公共祖先節點
 * 遞歸解法2(更簡單)
 * @param root 樹根節點
 * @param n1 第一個節點
 * @param n2 第二個節點
 * @return 最低公共祖先節點
 */
public static TreeNode getLastCommonParentRec2(TreeNode root, TreeNode n1, TreeNode n2) {
    if (root == null) {
        return null;
    }
    // 如果有一個match,則說明當前node就是要找的最低公共祖先
    if (root.equals(n1) || root.equals(n2)) {
        return root;
    }
    TreeNode commonLeft = getLastCommonParentRec2(root.left, n1, n2);
    TreeNode commonRight = getLastCommonParentRec2(root.right, n1, n2);
    // 如果一個在左子樹找到,一個在右子樹找到,則說明root是唯一可能得最低公共祖先
    if (commonLeft != null && commonRight != null) {
        return root;
    }
    // 其他情況是要不然在左子樹要不然在右子樹
    if (commonLeft != null) {
        return commonLeft;
    }
    return commonRight;
}

非遞歸算法:得到從二叉樹根節點到兩個節點的路徑,路徑從頭開始的最後一個公共節點就是它們的最低公共祖先節點

/**
 * 9. 樹中兩個節點的最低公共祖先節點
 * 非遞歸
 * @param root 樹根節點
 * @param n1 第一個節點
 * @param n2 第二個節點
 * @return 第一個公共祖先節點
 */
public static TreeNode getLastCommonParent(TreeNode root, TreeNode n1, TreeNode n2) {
    if (root == null || n1 == null || n2 == null) {
        return null;
    }
    ArrayList<TreeNode> p1 = new ArrayList<>();
    boolean res1 = getNodePath(root, n1, p1);
    ArrayList<TreeNode> p2 = new ArrayList<>();
    boolean res2 = getNodePath(root, n2, p2);
    if (!res1 || !res2) {
        return null;
    }
    TreeNode last = null;
    Iterator<TreeNode> iter1 = p1.iterator();
    Iterator<TreeNode> iter2 = p2.iterator();
    while (iter1.hasNext() && iter2.hasNext()) {
        TreeNode tmp1 = iter1.next();
        TreeNode tmp2 = iter2.next();
        if (tmp1 == tmp2) {
            last = tmp1;
        } else { // 直到遇到非公共節點
            break;
        }
    }
    return last;
}

/**
 * 把從根節點到node路徑上所有的點都添加到path中
 * @param root 樹根節點
 * @param node 終點節點
 * @param path 路徑
 * @return 是否是目標節點
 */
public static boolean getNodePath(TreeNode root, TreeNode node, ArrayList<TreeNode> path) {
    if (root == null) {
        return false;
    }
    path.add(root); // 把這個節點添加到路徑中
    if (root == node) {
        return true;
    }
    boolean found = false;
    found = getNodePath(root.left, node, path); // 先在左子樹中找
    if (!found) {
        found = getNodePath(root.right, node, path);
    }
    if (!found) { // 如果實在沒找到證明這個節點不在路徑中,刪除剛剛那個節點
        path.remove(root);
    }
    return found;
}
10. 判斷是否爲二分查找樹BST

遞歸解法:中序遍歷的結果應該是遞增的。

/**
 * 10. 判斷是否爲二分查找樹BST
 * @param root 根節點
 * @param pre 上一個保存的節點
 * @return 是否爲BST樹
 */
public static boolean isValidBST(TreeNode root, int pre){
    if (root == null) {
        return true;
    }
    boolean left = isValidBST(root.left, pre);
    if (!left) {
        return false;
    }
    if(root.val <= pre) {
        return false;
    }
    pre = root.val;
    boolean right = isValidBST(root.right, pre);
    if(!right) {
        return false;
    }
    return true;
}

非遞歸解法:參考非遞歸中序遍歷。

/** 
 * 10. 判斷是否爲二分查找樹BST
 * 非遞歸
 * @param root 根節點
 */
public boolean isValidBST2(TreeNode root){
    Stack<TreeNode> stack = new Stack<>();
    //設置前驅節點
    TreeNode pre = null;
    while(root != null || !stack.isEmpty()){
        while (root != null) { // 將當前節點,以及左子樹一直入棧,循環結束時,root==null
            stack.push(root);
            root = root.left;
        }
        root = stack.pop();
        //比較並更新前驅,與普通遍歷的區別就在下面四行
        if(pre != null && root.val <= pre.val){
            return false;
        }
        pre = root;
        root = root.right;  //訪問右子樹
    }
    return true;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章