1. 程式人生 > >[LeetCode] Recover Binary Search Tree 復原二叉搜尋樹

[LeetCode] Recover Binary Search Tree 復原二叉搜尋樹

Two elements of a binary search tree (BST) are swapped by mistake.

Recover the tree without changing its structure.

Note:
A solution using O(n) space is pretty straight forward. Could you devise a constant space solution?

這道題要求我們復原一個二叉搜尋樹,說是其中有兩個的順序被調換了,題目要求上說O(n)的解法很直觀,這種解法需要用到遞迴,用中序遍歷樹,並將所有節點存到一個一維向量中,把所有節點值存到另一個一維向量中,然後對存節點值的一維向量排序,在將排好的陣列按順序賦給節點。這種最一般的解法可針對任意個數目的節點錯亂的情況,這裡先貼上此種解法:

// O(n) space complexity
class Solution {
public:
    void recoverTree(TreeNode *root) {
        vector<TreeNode*> list;
        vector<int> vals;
        inorder(root, list, vals);
        sort(vals.begin(), vals.end());
        for (int i = 0; i < list.size(); ++i) {
            list[i]
->val = vals[i]; } } void inorder(TreeNode *root, vector<TreeNode*> &list, vector<int> &vals) { if (!root) return; inorder(root->left, list, vals); list.push_back(root); vals.push_back(root->val); inorder(root
->right, list, vals); } };

然後我上網搜了許多其他解法,看到另一種是用雙指標來代替一維向量的,但是這種方法用到了遞迴,也不是O(1)空間複雜度的解法,這裡需要三個指標,first,second分別表示第一個和第二個錯亂位置的節點,pre指向當前節點的中序遍歷的前一個節點。這裡用傳統的中序遍歷遞迴來做,不過再應該輸出節點值的地方,換成了判斷pre和當前節點值的大小,如果pre的大,若first為空,則將first指向pre指的節點,把second指向當前節點。這樣中序遍歷完整個樹,若first和second都存在,則交換它們的節點值即可。這個演算法的空間複雜度仍為O(n),n為樹的高度,程式碼如下:

// Still O(n) space complexity
class Solution {
public:
    TreeNode *pre;
    TreeNode *first;
    TreeNode *second;
    void recoverTree(TreeNode *root) {
        pre = NULL;
        first = NULL;
        second = NULL;
        inorder(root);
        if (first && second) swap(first->val, second->val);
    }
    void inorder(TreeNode *root) {
        if (!root) return;
        inorder(root->left);
        if (!pre) pre = root;
        else {
            if (pre->val > root->val) {
                if (!first) first = pre;
                second = root;
            }
            pre = root;
        }
        inorder(root->right);
    }
};

這道題的真正符合要求的解法應該用的Morris遍歷,這是一種非遞迴且不使用棧,空間複雜度為O(1)的遍歷方法,可參見我之前的部落格Binary Tree Inorder Traversal 二叉樹的中序遍歷,在其基礎上做些修改,加入first, second和parent指標,來比較當前節點值和中序遍歷的前一節點值的大小,跟上面遞迴演算法的思路相似,程式碼如下:

// Now O(1) space complexity
class Solution {
public:
    void recoverTree(TreeNode *root) {
        TreeNode *first = NULL, *second = NULL, *parent = NULL;
        TreeNode *cur, *pre;
        cur = root;
        while (cur) {
            if (!cur->left) {
                if (parent && parent->val > cur->val) {
                    if (!first) first = parent;
                    second = cur;
                }
                parent = cur;
                cur = cur->right;
            } else {
                pre = cur->left;
                while (pre->right && pre->right != cur) pre = pre->right;
                if (!pre->right) {
                    pre->right = cur;
                    cur = cur->left;
                } else {
                    pre->right = NULL;
                    if (parent->val > cur->val) {
                        if (!first) first = parent;
                        second = cur;
                    }
                    parent = cur;
                    cur = cur->right;
                }
            }
        }
        if (first && second) swap(first->val, second->val);
    }
};