1. 程式人生 > 其它 >JZ26 二叉搜尋樹與雙向連結串列

JZ26 二叉搜尋樹與雙向連結串列

輸入一棵二叉搜尋樹,將該二叉搜尋樹轉換成一個排序的雙向連結串列。

JZ26 二叉搜尋樹與雙向連結串列

描述

輸入一棵二叉搜尋樹,將該二叉搜尋樹轉換成一個排序的雙向連結串列。如下圖所示

要求:空間複雜度O(1)(即在原樹上操作),時間複雜度O(n)。

注意:

  1. 要求不能建立任何新的結點,只能調整樹中結點指標的指向。當轉化完成以後,樹中節點的左指標需要指向前驅,樹中節點的右指標需要指向後繼
  2. 返回連結串列中的第一個節點的指標
  3. 函式返回的TreeNode,有左右指標,其實可以看成一個雙向連結串列的資料結構

示例

輸入:

{10,6,14,4,8,12,16}

返回值:

From left to right are:4,6,8,10,12,14,16;From right to left are:16,14,12,10,8,6,4;

解析

首先,樹的問題離不開樹的遍歷,本題的樹是二叉搜尋樹,特點為左孩子節點值 < 當前節點值 < 右孩子節點值,即左中右,與二叉樹的中序遍歷順序相同!也就是說,只要進行中序遍歷,就能遍歷二叉搜尋樹中由小到大的節點!

在一開始寫的時候,我沒有注意到空間複雜度為O(1)的要求,即不能開闢新的空間;此時的思路是利用一個節點陣列隨著中序遍歷儲存節點,這樣遍歷完後陣列中的順序就是連結串列的節點順序,再進行頭尾指標的調整即可。

程式碼清單

import java.util.ArrayList;
/**
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;

    public TreeNode(int val) {
        this.val = val;
    }
}
*/
public class Solution {
    private ArrayList<TreeNode> nodeList = new ArrayList<>();
    public TreeNode Convert(TreeNode pRootOfTree) {
        MidOrder(pRootOfTree);
        int num = nodeList.size();
        if(num == 0)
            return null;
        // 陣列最後一個是尾結點,單獨設定後繼為null
        for(int i = 0;i < num-1;i++){
            nodeList.get(i).right = nodeList.get(i+1);
        }
        nodeList.get(num-1).right = null;
        // 陣列第一個是頭結點,單獨設定前驅為null
        for(int i = num-1;i > 0;i--){
            nodeList.get(i).left = nodeList.get(i-1);
        }
        nodeList.get(0).left = null;
        return nodeList.get(0);
    }
    
    public void MidOrder(TreeNode node){
        if(node == null)
            return;
        MidOrder(node.left);
        nodeList.add(node);
        MidOrder(node.right);
    }
}

這種方式簡單且容易理解,但不符合題目要求,所以就有了第二種方式!

由於不能開闢新的空間,就說明我們需要隨著樹的遍歷調整指標關係。但在普通的遍歷中,我們只能獲得當前正在遍歷的節點的資訊,這就需要引入一個 pre 變數,用於指向遍歷到的當前節點的上一個節點,這樣在到達每個節點時,都能通過操作 pre 節點和當前節點構造連結串列。

程式碼清單

public class Solution {
    private TreeNode pre = null;
    private TreeNode head = null;
    public TreeNode Convert(TreeNode pRootOfTree) {
        if(pRootOfTree == null)
            return null;
        Convert(pRootOfTree.left);
        if( head == null){
            // 開始執行的第一個節點就是頭結點!
            head = pRootOfTree;
        }
        if( pre != null){
            // 上一個設定為前驅
            pRootOfTree.left = pre;
            // 當前是上一個的後繼
            pre.right = pRootOfTree;
        }
        // 調整 pre,繼續走
        pre = pRootOfTree;
        Convert(pRootOfTree.right);
        return head;
    }
}

不過還有一個點需要注意,由於中序遍歷過程對於二叉搜尋樹來說是由小到大的,也就是說最後 pre 會指向連結串列末尾,即值最大的節點,與題目要求返回連結串列頭結點的要求不符;這就需要另外引入一個變數 head,儲存遍歷時的第一個節點,也就是連結串列的頭結點。

上述問題也有另一種解決方式,即按左中右的順序遍歷,pre 會指向連結串列末尾,那麼按照右中左的順序遍歷,pre 不就可以指向連結串列頭部了?這種方式更加巧妙,程式碼量也更少,不過這裡就不列出來了。

總結

本題的破局之處是二叉搜尋樹的結構符合中序遍歷的順序,只要進行中序遍歷,就能從小到大地訪問二叉搜尋樹的節點!