1. 程式人生 > 其它 >二叉樹——結構查詢相關問題

二叉樹——結構查詢相關問題

1. 判斷t1樹是否包含t2樹的所有拓撲結構

1.1. 問題

給定彼此獨立的兩棵樹頭節點分別為 t1 和 t2,判斷 t1 樹是否包含 t2 樹全部的拓撲結構。

1.2. 思路

題目這裡沒有要求時間複雜度。所以就可以用最簡單的方法,以t1樹上的所有節點都作為根節點和t2比對一次,直到成功一次。
比對的過程也是個遞迴的過程。整個過程相當於兩層迴圈。時間複雜度為O(m×n)

1.3. 程式碼

    public static boolean contains(TreeNode<Integer> t1, TreeNode<Integer> t2) {
        if (t2 == null) return true;
        if (t1 == null) return false;
        return check(t1, t2) || contains(t1.left, t2) || contains(t1.right, t2);
    }


    private static boolean check(TreeNode<Integer> t1, TreeNode<Integer> t2) {
        if (t2 == null) return true;
        if (t1 == null || !t1.val.equals(t2.val))
            return false;
        return check(t1.left, t2.left) && check(t1.right, t2.right);
    }

2. 判斷t1樹是否有與t2樹拓撲結構完全相同的子樹

另一棵樹的子樹

2.1. 問題

給定彼此獨立的兩棵樹頭節點分別為 t1 和 t2,判斷 t1 中是否有與 t2 樹拓撲結構完全相同的子樹。

2.2. 解法

這道題可以和判斷t1樹中是否包含t2樹的所有拓撲結構這道題進行對比。

  • 方法一:使用針對二叉樹的每個節點為根來進行考察的方法,時間複雜度為O(m×n)。

  • 方法二:將兩個二叉樹序列化成字串(序列化時,要在值的前後都加上界限符號),然後用字串匹配演算法檢視t1對應的字串中是否包含t2對應的字串這個子串。KMP演算法,這樣時間複雜度可以做到O(N+M)。

2.3. 程式碼

2.3.1. 遞迴

class Solution {
    public boolean isSubtree(TreeNode root, TreeNode subRoot) {
        if (subRoot == null) {
            return true;
        }
        if (root == null) {
            return false;
        }
        return isSubtree(root.left, subRoot) || isSubtree(root.right, subRoot) || isSame(root, subRoot);
    }

    public boolean isSame(TreeNode p, TreeNode q) {
        if(p == null && q == null) {
            return true;
        }
        if(p == null || q == null) {
            return false;
        }
        if(p.val != q.val) {
            return false;
        }
        return isSame(p.left, q.left) &&isSame(p.right, q.right);
    }
}

2.3.2. 字串匹配法

class Solution {
        public boolean isSubtree(TreeNode root, TreeNode subRoot) {
        if(subRoot == null) {
            return true;
        }
        if(root == null) {
            return false;
        }
        StringBuilder builder  = new StringBuilder();
        serialize(root, builder);
        String rootStr = builder.toString();
        StringBuilder builder2  = new StringBuilder();
        serialize(subRoot, builder2);
        String subRootStr = builder2.toString();
        return rootStr.contains(subRootStr);
    }

    public void serialize(TreeNode root, StringBuilder builder) {
        if(root == null) {
            builder.append("!#!");
            return;
        }
        builder.append('!');
        builder.append(root.val);
        builder.append('!');
        serialize(root.left,builder);
        serialize(root.right, builder);
    }
}

3. 找到二叉樹中符合搜尋二叉樹的最大拓撲結構

3.1. 問題

給定一棵二叉樹的頭節點 head,已知所有節點的值都不一樣,返回其中最大的且符合搜尋二叉樹條件的最大拓撲結構的大小。

比如對於:

它的符合搜尋二叉樹條件的最大拓撲結構為:

3.2. 思路

這道題比較困難,我第一次寫,也是犯了以前做最大矩陣這道題的錯誤,還是按照找最大搜索二叉子樹的樹形dp方法,結果後來測試的時候發現行不通,仔細一想確實不能用樹形dp的方法,因為對於每個節點來說,f(x)和f(x.left)、f(x.right)並沒有遞推關係!我之前寫的程式碼裡的遞推關係都是錯的!
我看了書,書中給了兩個思路:

  1. 時間複雜度為O(n2)的方法:我們只需要遍歷每個節點,求以該節點為拓撲結構根節點的最大拓撲結構的大小,然後取其中最大值即可。

  2. 時間複雜度為O(n)的方法:在遍歷樹的時候建立二叉數的拓撲貢獻記錄即可,然後到上層節點的時候就不需要對其所有子節點進行遍歷,只需遍歷其左子樹的右邊界和右子樹的左邊界即可,然後更新拓撲貢獻記錄。這種方式其實是使用記憶話的手段,來對方法一進行剪枝。

對於方法一而言,對於某個節點而言如何找以它為拓撲結構根節點的最大拓撲結構呢?我們可以遍歷這個節點對應的子樹,對每個節點按照從搜尋二叉樹中找節點的方式進行查詢,如果找到的節點與該節點相同,那麼說明該節點可以作為最大拓撲結構的一部分;否則,該節點和其子節點都不用看了。

方法一遍歷到每個節點的進行處理時候,要檢查該節點的子節點,可能的話其所有子孫節點都會檢查一遍,相當於兩層迴圈,所以是n^2

方法二中每個節點只會成為一個節點的左子樹的右邊界或右子樹的左邊界,所以該節點被檢查的次數不會多於兩次,所以是線性時間複雜度。

3.3. 程式碼

3.3.1. 方法一

    public static int maxTopology1(TreeNode<Integer> root) {
        if (root == null) return 0;
        int res = Math.max(maxTopology1(root.left), maxTopology1(root.right));
        res = Math.max(res, process(root));
        return res;
    }

    private static int process(TreeNode<Integer> root) {
        if (root == null) return 0;
        Queue<TreeNode<Integer>> queue = new LinkedList<>();
        queue.add(root);
        int res = 0;
        TreeNode<Integer> node;
        while (!queue.isEmpty()) {
            node = queue.poll();
            if (isBSTNode(root, node)) {
                res++;
                if (node.left != null)
                    queue.add(node.left);
                if (node.right != null)
                    queue.add(node.right);
            }
        }
        return res;
    }

    private static boolean isBSTNode(TreeNode<Integer> root, TreeNode<Integer> node) {
        TreeNode<Integer> cur = root;
        while (cur != null) {
            if (cur.val > node.val) cur = cur.left;
            else if (cur.val < node.val) cur = cur.right;
            else return cur == node;
        }
        return false;
    }

3.3.2. 方法二

    private static class Record {
        int left;
        int right;

        public Record(int left, int right) {
            this.left = left;
            this.right = right;
        }

        public int sum() {
            return left + right + 1;
        }
    }

    public static int maxTopology2(TreeNode<Integer> root) {
        if (root == null) return 0;
        HashMap<TreeNode<Integer>, Record> records = new HashMap<>();
        return process(root, records);
    }

    private static int process(TreeNode<Integer> node, Map<TreeNode<Integer>, Record> records) {
        if (node == null)
            return 0;
        int res = Math.max(process(node.left, records), process(node.right, records));

        TreeNode<Integer> end = node.left;
        TreeNode<Integer> cur = node.left;
        int minus;
        //這裡第一次寫錯了,第一次迴圈條件寫成end != null && end.val < node.val。
        while (records.containsKey(end) && end.val < node.val) {
            end = end.right;
        }
        if (records.containsKey(end)) {
            minus = records.remove(end).sum();
            while (cur != end) {
                records.get(cur).right -= minus;
                cur = cur.right;
            }
        }

        cur = end = node.right;
        while (records.containsKey(end) && end.val > node.val) {
            end = end.left;
        }
        if (records.containsKey(end)) {
            minus = records.remove(end).sum();
            while (cur != end) {
                records.get(cur).left -= minus;
                cur = cur.left;
            }
        }
        //起初在外層方法裡的records中放(null,new Record(0,0)),然後這裡不對空檢查,其實錯了
        Record self = new Record(0, 0);
        Record left = records.getOrDefault(node.left, null);
        Record right = records.getOrDefault(node.right, null);
        self.left = left != null ? left.sum() : 0;
        self.right = right != null ? right.sum() : 0;
        records.put(node, self);
        return Math.max(res, self.sum());
    }