圖解算法——恢復一棵二叉搜索樹(BST)


題目來源

基礎:給你二叉搜索樹的根節點 root ,該樹中的兩個節點被錯誤地交換。請在不改變其結構的情況下,恢復這棵樹。

進階:使用 O(n) 空間復雜度的解法很容易實現。你能想出一個只使用常數空間的解決方案嗎?

示例1:

輸入:root = [1,3,null,null,2]
輸出:[3,1,null,null,2]
解釋:3 不能是 1 左孩子,因為 3 > 1 。交換 13 使二叉搜索樹有效。

示例2:

輸入:root = [3,1,4,null,null,2]
輸出:[2,1,4,null,null,3]
解釋:2 不能在 3 的右子樹中,因為 2 < 3 。交換 23 使二叉搜索樹有效。

 

來源:力扣(LeetCode)
鏈接:https://leetcode-cn.com/problems/recover-binary-search-tree
著作權歸領扣網絡所有。商業轉載請聯系官方授權,非商業轉載請注明出處。

題目解析

什么意思呢?這是其實是兩道題,第一道是基礎的,就是用基本的解法即可,關鍵是第二種如何優化你的算法。

好,我們先說第一種,大眾思維。

既然題目上說了錯誤地交換了搜索二叉樹(BST)的兩個節點,那么BST又有什么特點呢?

我們知道中序遍歷搜索BST會得到一組升序的數組(比如:[1,2,3,4,5,6,7,8]),那好,按照題意我們交換兩組節點2和6,數組變成[1,6,3,4,5,2,7,8],此時 可發現數組的升序被打破了,因為6>3,5>2。沒錯,我們就利用該性質是不是就可以找出交換的兩個節點位置,然后做交換就好了?

解析方法歸納:

1、先得到BST中序遍歷的數組序列;

2、找到不滿足條件的位置;

3、看節點數有幾個:

  3.1、如果有兩個,即[1,6,3,4,5,2,7,8]中6>3,5>2,那么就將位置分別記為 i 和 j(i < j,其中i是6,j是5,特別提醒並不是2哦),對應的交換錯的節點為 Ai 和 Aj+1 (Ai > Ai+1 && Aj > Aj+1),我們分別記為x,y;

  3.2、如果有一個,即[1,2,3,5,4,6,7,8]中的5>4,那么就將位置記為 i ,交換錯的位置就是Ai 和 Ai+1,我們分別記為x,y;

4、遍歷樹,交換節點 x , y 。

好了。思路也很清楚了,關鍵是如何實現。

第一步:先序遍歷BST這應該挺簡單的,遞歸嘛,我們將數組定位為nums來記錄:

//c++
void inOrder(TreeNode * root, vector<int>& nums){
    if(root == nullptr){
        return;
    }
    inOrder(root->left, nums);
    nums.push_back(root->val);
    inOrder(root->right, nums);
}

第二步:找到不滿足條件的位置;但是該位置可能有一個,也可能有兩個,所以,得要遍歷數組一次。

    vector<int> find2val(TreeNode* root, vector<int>& nums){
        int n1 = 0;
        int n2 = 0;
        bool sec = false;
        for(int i = 0; i<nums.size()-1; i++){
            if(nums[i]>nums[i+1]){
                if(!sec){
                     n1 = nums[i];
                     n2 = nums[i+1];
                     sec = true;
                }
                else
                    n2 = nums[i+1];
            }
        }
        return {n1,n2};
    }

第三步:看數組中到底有幾次

我們這里在主函數中直接就寫成2了,因為最多為兩次,當然也可以將這個次數記錄下來;

第四步:遍歷樹,換位置

void reverse(TreeNode * root, int count, int x, int y){
    if(root!=nullptr){
        if(root->val == x || root->val == y){
            root->val = (root->val == x) ? y : x;//swap (x,y)
            if(--count == 0){//來計數是第幾次如果是第二次了后面的就不用再遍歷了; return;
            }
        }
        reverse(root->left,count,x,y);
        reverse(root->right,count,x,y);
    }
}

第五步:主函數

void recoverTree(TreeNode* root) {
    vector<int> nums;
    inOrder(root, nums);//第一步中序遍歷得到升序數組
    vector<int> swap_vals = find2val(root, nums);//找到兩個被錯誤交換的值
    reverse(root,2,swap_vals[0],swap_vals[1]);//遍歷樹,進行交換;
}

 

算法分析:

  • 時間復雜度:O(N),其中N為BST的節點數。中序遍歷要O(N)的時間,而判斷交換節點在哪里,最好的情況是O(1),最壞的情況是O(N),所以是O(N);
  • 空間復雜度:O(N),因為用到了一個數組來存放升序數列;

以上是一般大眾思維,那么如何進行優化呢?優化的點在哪里呢?

其實,我們沒有必要去引入這個nums數組,因為我們在中序遍歷樹時,如果去維護一個前節點變量,那么我們就可以在遍歷過程中直接進行比較,我們在這里引入一個棧,並且迭代實現中序遍歷,並不是遞歸。具體用法看下面;

如:

      3
     / \
    1   4
       / 
      2

第一步:中序遍歷,先找到最左節點,中途所有的節點都入棧;

 第二步:繼續;

第三步:取棧頂元素,並賦予前一個節點變量pred,並向彈出的節點的右子樹走;

 

 

第四步:繼續,因為1的右節點,也是NULL,故繼續彈棧,彈出來也就是1的父節點3;賦予前一個節點變量pred=3;

 

 

第五步:遍歷右子樹,有值,尋找右子樹中的最左節點,並將沿途所遍歷的節點都入棧;

 

 

 第六步:入棧,找到右子樹的最左節點;

 

 

 第七步:彈出棧頂,此時相當於,找到了以 3 為根節點的,中序遍歷中左子樹的最后一個節點值,和右子樹中的第一個值。這句話的意思是,當你中序遍歷時,3的前后值分別為 1 和 2 ;

看好這里,發現了前后節點值大小異常:2 < 3,記錄下這兩個節點Node1,Node2;

 

 

 第八步:繼續;

 

 

 第九步:遍歷完畢,找到了交換的點Node1,Node2,進行交換即可;

 

 

 

 代碼實現:

class Solution {
public:
    void recoverTree(TreeNode* root) {
        stack<TreeNode*> stk;
        TreeNode* x = nullptr;
        TreeNode* y = nullptr;
        TreeNode* pred = nullptr;

        while (!stk.empty() || root != nullptr) {
            while (root != nullptr) {
                stk.push(root);
                root = root->left;
            }
            root = stk.top();
            stk.pop();
            if (pred != nullptr && root->val < pred->val) {
                y = root;
                if (x == nullptr) {
                    x = pred;
                }
                else break;
            }
            pred = root;
            root = root->right;
        }

        swap(x->val, y->val);
    }
};

這部分圖來自:

作者:LeetCode-Solution
鏈接:https://leetcode-cn.com/problems/recover-binary-search-tree/solution/hui-fu-er-cha-sou-suo-shu-by-leetcode-solution/
來源:力扣(LeetCode)
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。

復雜度分析:

  • 時間復雜度:最壞情況下是需要遍歷整棵樹(即交換節點為BST的最右側的兩個節點),時間復雜度為O(N),N為節點個數;
  • 空間復雜度:O(H),H為BST的高度;注意:中序遍歷的時候,棧的深度取決於樹的高度噢!!!

 

親愛的,你們以為到這里就結束了嗎?

錯,大錯特錯,在這里突然冒出一個Morris中序遍歷算法,這個算法之前是真的不知道。無知了...

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM