Day 18 二叉樹part06

12点不睡觉还想干啥?發表於2024-07-21

530. 二叉搜尋樹的最小絕對差

二叉搜尋樹經常要用到其中序遍歷是遞增的這一性質,因此,這裡經常會用到這種套路,即中序遍歷,然後使用一個pre記錄前一個訪問的節點,pre初值設定為null即可。

對於這道題,使用dif儲存已經遍歷到的位置中最小的差值,當遇到更小的差值再去更新。總體上就是中序遍歷,因此使用遞迴或迭代都是沒有問題的。

class Solution {
    int dif = Integer.MAX_VALUE;
    TreeNode pre = null;
    public int getMinimumDifference(TreeNode root) {
        getD(root);
        return dif;
    }
    public void getD(TreeNode root){
        if(root.left != null){
            getD(root.left); //左
        }
        int x = root.val;  //中
        if(pre != null){
            int t_dif = x - pre.val >= 0 ? x - pre.val : pre.val - x;
            dif = Math.min(t_dif, dif);
        }
        pre = root;
        if(root.right != null){getD(root.right);} //右
    }
}

501. 二叉搜尋樹中的眾數

這道題儘管我自己實現出來了,但有點太麻煩了。先整體敘述下我的思路,受啟發於239. 滑動視窗最大值,我考慮使用單調佇列來儲存已經遍歷的部分中,頻率最高的元素(元素值和頻次)。

因為是搜尋樹,因此還是使用中序遍歷。如何統計頻率呢,使用tmp陣列(tmp[0]代表數字,tmp[1]代表頻次),每遍歷一個節點,若其與前一個節點值相同,則頻次加一;若與前一個節點不同,則tmp更新為新的數字的頻次。每次訪問完便向單調佇列中插入tmp,單調佇列可以保證只保留頻率最高的元素。

最終只需要將單調佇列中的資料轉成int陣列即可。

class Solution {
    Integer[] tmp = new Integer[2];
    TreeNode pre = null;
    MyQueue queue;
    public int[] findMode(TreeNode root) {
        queue = new MyQueue();
        find(root);
        return queue.toArray();
    }
    public void find(TreeNode root){
        if(root == null) return;
        if(root.left != null) find(root.left);
        if(pre == null || pre.val != root.val){
            tmp[0] = root.val;
            tmp[1] = 1;
        }else if(root.val == pre.val){
            tmp[1] += 1;
        }
        queue.push(new Integer[]{tmp[0], tmp[1]});
        queue.print();
        pre = root;
        if(root.right!=null) find(root.right);
    }

}

class MyQueue{
    Queue<Integer[]> queue;
    public MyQueue(){
        this.queue = new LinkedList();
    } 

    public int peek(){
        if(this.queue.isEmpty()) return 0;
        return this.queue.peek()[1];
    }

    public void push(Integer[] tmp){ //該陣列第一個元素代表節點值,第二哥元素代表頻率
        if(tmp[1] > this.peek()){
            queue.clear();
            queue.offer(tmp);
        }else if(tmp[1] == this.peek()){
            queue.offer(tmp);
        }
    }
    public int[] toArray(){
        int[] res = new int[this.queue.size()];
        int i = 0;
        for(Integer[] tmp : this.queue){
            res[i++] = tmp[0];
        }
        return res;
    }
}

其實這道題官解的思路與我的思路並沒有什麼不同,只是我的實現方法過於複雜了,並不需要為此就設計一個單調佇列。使用maxCount記錄當前最大的頻次即可,之後當出現count大於maxCount就把結果集合清空,再新增當前元素。(與我的有限佇列中push的部分含義相同。

class Solution {
    int count, num, maxCount = -1;
    TreeNode pre = null;
    ArrayList<Integer> ans = new ArrayList();
    public int[] findMode(TreeNode root) {
        find(root);
        int[] ans_array = new int[ans.size()];
        int i = 0;
        for(Integer num: ans) ans_array[i++] = num;
        return ans_array;
    }
    public void find(TreeNode root){
        if(root == null) return;
        if(root.left != null) find(root.left);
        if(pre == null || pre.val != root.val){
            num = root.val;
            count = 1;
        }else if(root.val == pre.val){
            count++;
        }
        if(count == maxCount){
            ans.add(num);
        }else if(count > maxCount){
            maxCount = count;
            ans.clear();
            ans.add(num);
        }
        pre = root;
        if(root.right!=null) find(root.right);
    }
}

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

這道題我自己寫的這個方法,很容易理解,但是時間複雜度太高,先放在這說一下自己的思路吧。使用層序遍歷,當每次遍歷一個節點,判斷他是不是pq的公共祖先,是則更新res,由於是層序遍歷,一定可以保證最後一個遍歷到的公共祖先是最近公共祖先。但這樣反覆去判斷是否是祖先的過程複雜度太高,這段程式碼複雜度應該是O(n**2);

class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        TreeNode res = root;
        Queue<TreeNode> queue = new LinkedList();
        queue.offer(root);
        while(!queue.isEmpty()){
            TreeNode tmp = queue.poll();
            if(isAncestor(tmp, p) && isAncestor(tmp, q)) res = tmp;
            if(tmp.left != null) queue.offer(tmp.left);
            if(tmp.right != null) queue.offer(tmp.right);
        }
        return res;
    }
    public boolean isAncestor(TreeNode root, TreeNode p){
        if(root == p) return true;
        if(root == null) return false;
        return isAncestor(root.left, p) || isAncestor(root.right, p);
    }
}

題解中的遞迴寫法真的是很難懂的,因為他和遞迴的模板並不完全相同,不能按照理解其他遞迴的方式去理解這道題的解法。

對於一般的遞迴,我們肯定認為遞迴去解決的子問題和原問題是相同的,只是更小了,是一個子問題。對於這個題解,如果我們還這麼理解是行不通的。具體而言,我們要解決的問題是找到公共祖先,那麼該函式返回的一定是pq的公共祖先才對,那為什麼會出現left和right都不為null的情況,即公共祖先既出現在左子樹又出現在右子樹,顯然這麼理解是不正確的。

理解大佬們的程式碼,需要先把lowestCommonAncestor()當成是一個純粹的二叉樹後序遍歷找節點函式(先忘記它是一個查詢最近公共祖先的函式),目的是找到p或者q節點,然後根據返回結果來判斷最近公共祖先。這樣才能解釋left和right都不為空的情況。(這段話引自題解下面的評論中)

但其實這樣下來我還是無法想到怎麼把他運用到其他的遞迴問題中去,只能當作一個特例先記住。

class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if(root == null) return null;
        if(root == p || root == q) return root;
        TreeNode left = lowestCommonAncestor(root.left, p, q);
        TreeNode right = lowestCommonAncestor(root.right, p, q);
        if(left == null) return right;
        if(right == null) return left;
        return root;
    }
}

還有一種思路比較好,但我寫的程式碼實在太醜陋。遍歷獲取從root到p和從root到q的路徑,取路徑最後一個相同的節點就是最近公共祖先。我寫的太拉了,今天也太晚了不想改了,就這樣吧,累了,睡。

class Solution {
    List<TreeNode> path = new ArrayList();
    List<TreeNode> path_p, path_q;
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        getPath(root, q);
        path_q = path_p;
        path_p = null;
        path = new ArrayList();
        getPath(root, p);
        System.out.println(path_q);
        TreeNode ans = null;
        for(int i = 0; i < Math.min(path_q.size(), path_p.size()); i++){
            if(path_p.get(i) == path_q.get(i))
                ans = path_p.get(i);
        }
        return ans;
    }

    public void getPath(TreeNode root, TreeNode p){
        if(root == null) return;
        path.add(root);
        if(root == p) {path_p = new ArrayList(path); return; }
        if(root.left != null){
            getPath(root.left, p);
            path.remove(path.size()-1);
        }
        if(root.right != null){
            getPath(root.right, p);
            path.remove(path.size()-1);
        }
    }
}

相關文章