[LeetCode] 426. 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;
    }
};

 

Github 同步地址:

https://github.com/grandyang/leetcode/issues/426

 

類似題目:

Binary Tree Inorder Traversal

 

參考資料:

https://leetcode.com/problems/convert-binary-search-tree-to-sorted-doubly-linked-list/

https://leetcode.com/problems/convert-binary-search-tree-to-sorted-doubly-linked-list/discuss/174111/inorder-vs-divide-and-conquer

https://leetcode.com/problems/convert-binary-search-tree-to-sorted-doubly-linked-list/discuss/151228/Java-Simple-InOrder-no-Global

https://leetcode.com/problems/convert-binary-search-tree-to-sorted-doubly-linked-list/discuss/138621/C%2B%2B-solution

https://leetcode.com/problems/convert-binary-search-tree-to-sorted-doubly-linked-list/discuss/149151/Concise-Java-solution-Beats-100

https://leetcode.com/problems/convert-binary-search-tree-to-sorted-doubly-linked-list/discuss/154659/Divide-and-Conquer-without-Dummy-Node-Java-Solution

 

LeetCode All in One 題目講解匯總(持續更新中...)


免責聲明!

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



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