1. 程式人生 > >[LeetCode] Convert Binary Search Tree to Sorted Doubly Linked List 將二叉搜尋樹轉為有序雙向連結串列

[LeetCode] Convert Binary Search Tree to Sorted Doubly Linked List 將二叉搜尋樹轉為有序雙向連結串列

Convert a BST to a sorted circular doubly-linked list in-place. Think of the left and right pointers as synonymous to the previous and next pointers in a doubly-linked list.

Let's take the following BST as an example, it may help you understand the problem better:

 

 

We want to transform this BST into a circular doubly linked list. Each node in a doubly linked list has a predecessor and successor. For a circular doubly linked list, the predecessor of the first element is the last element, and the successor of the last element is the first element.

The figure below shows the circular doubly linked list for the BST above. The "head" symbol means the node it points to is the smallest element of the linked list.

 

 

Specifically, we want to do the transformation in place. After the transformation, the left pointer of the tree node should point to its predecessor, and the right pointer should point to its successor. We should return the pointer to the first element of the linked list.

The figure below shows the transformed BST. The solid line indicates the successor relationship, while the dashed line means the predecessor relationship.

 

這道題給了我們一個二叉搜尋樹,讓我們將其轉化為雙向連結串列。並且題目中給了我們一個帶圖的例子,幫助我們來理解。題目本身並不難理解,我們需要仔細觀察下給的示例圖。首先,轉化成雙向連結串列的每個結點都有left和right指標指向左右兩個結點,不管其原來是否是葉結點還是根結點,轉換後統統沒有區別。其次,我們發現這是個迴圈雙向連結串列,即首尾結點是相連的,原先的二叉搜尋樹中的最左結點和最右結點,現在也互相連線起來了。最後,我們發現返回的結點不再是原二叉搜尋樹的根結點root了,而是最左結點,即最小值結點。

好,發現了上述規律後,我們來考慮如何破題。根據博主多年經驗,跟二叉搜尋樹有關的題,肯定要利用其性質,即左<根<右,即左子結點值小於根結點值小於右子結點值。而且十有八九都得用中序遍歷來解,因為中序遍歷的順序就是左根右啊,跟性質吻合。我們觀察原二叉搜尋樹中結點4連線著結點2和結點5,而在雙向連結串列中,連線的是結點3和結點5,這就是為啥我們要用中序遍歷了,因為只有中序遍歷,結點3之後才會遍歷到結點4,這時候我們可以將結點3和結點4串起來。決定了用中序遍歷之後,就要考慮是迭代還是遞迴的寫法,博主建議寫遞迴的,一般寫起來都比較簡潔,而且遞迴是解樹類問題的神器啊,十有八九都是用遞迴,一定要熟練掌握。再寫中序遍歷之前,其實還有難點,因為我們需要把相鄰的結點連線起來,所以我們需要知道上一個遍歷到的結點是什麼,所以用一個變數pre,來記錄上一個遍歷到的結點。還需要一個變數head,來記錄最左結點,這樣的話,在遞迴函式中,先判空,之後對左子結點呼叫遞迴,這樣會先一直遞迴到最左結點,此時如果head為空的話,說明當前就是最左結點,賦值給head和pre,對於之後的遍歷到的結點,那麼可以和pre相互連線上,然後pre賦值為當前結點node,再對右子結點呼叫遞迴即可,參見程式碼如下:

解法一:

class Solution {
public:
    Node* treeToDoublyList(Node* root) {
        if (!root) return NULL;
        Node *head = NULL, *pre = NULL;
        inorder(root, pre, head);
        pre->right = head;
        head->left = pre;
        return head;
    }
    void inorder(Node* node, Node*& pre, Node*& head) {
        if (!node) return;
        inorder(node->left, pre, head);
        if (!head) {
            head = node;
            pre = node;
        } else {
            pre->right = node;
            node->left = pre;
            pre = node;
        }
        inorder(node->right, pre, head);
    }
};

雖然說樹類問題首推遞迴解法,但是中序遍歷是可以用迭代來寫的,可以參見博主之前的部落格Binary Tree Inorder Traversal。迭代寫法借用了棧,其實整體思路和遞迴解法沒有太大的區別,遞迴的本質也是將斷點存入棧中,以便之後可以返回,這裡就不多講解了,可以參見上面的講解,參見程式碼如下:

解法二:

class Solution {
public:
    Node* treeToDoublyList(Node* root) {
        if (!root) return NULL;
        Node *head = NULL, *pre = NULL;
        stack<Node*> st;
        while (root || !st.empty()) {
            while (root) {
                st.push(root);
                root = root->left;
            }
            root = st.top(); st.pop();
            if (!head) head = root;
            if (pre) {
                pre->right = root;
                root->left = pre;
            }
            pre = root;
            root = root->right;
        }
        head->left = pre;
        pre->right = head;
        return head;
    }
};

這道題還有一種使用分治法Divide and Conquer來做的方法。分治法,顧名思義,就是把一項任務分成兩半,用相同的邏輯去分別處理,之後再粘合起來。混合排序Merge Sort用的也是這種思路。那麼我們可以對左右子結點呼叫遞迴函式,suppose我們得到了兩個各自迴圈的有序雙向連結串列,然後我們把根結點跟左右子結點斷開,將其左右指標均指向自己,這樣就形成了一個單個結點的有序雙向連結串列,雖然只是個光桿司令,但人家仍然是有序雙向連結串列,不是沙雕,就問你叼不叼。那麼此時我們只要再寫一個連線兩個有序雙向連結串列的子函式,就可以將這三個有序雙向連結串列按順序連結起來了。

而連結兩個有序雙向連結串列的子函式也簡單,首先判空,若一個為空,則返回另一個。如果兩個都不為空,則把第一個連結串列的尾結點的右指標鏈上第二個連結串列的首結點,同時第二個連結串列的首結點的左指標鏈上第一個連結串列的尾結點。同理,把第二個連結串列的尾結點的右指標鏈上第一個連結串列的首結點,同時第一個連結串列的首結點的左指標鏈上第二個連結串列的尾結點。有木有讀暈,可以自己畫圖,其實很好理解的誒,參見程式碼如下:

解法三:

class Solution {
public:
    Node* treeToDoublyList(Node* root) {
        if (!root) return NULL;
        Node *leftHead = treeToDoublyList(root->left);
        Node *rightHead = treeToDoublyList(root->right);
        root->left = root;
        root->right = root;
        return connect(connect(leftHead, root), rightHead);
    }
    Node* connect(Node* node1, Node* node2) {
        if (!node1) return node2;
        if (!node2) return node1;
        Node *tail1 = node1->left, *tail2 = node2->left;
        tail1->right = node2;
        node2->left = tail1;
        tail2->right = node1;
        node1->left = tail2;
        return node1;
    }
};

類似題目:

參考資料: