LeetCode 力扣 109. 有序鏈表轉換二叉搜索樹

題目描述(中等難度)

108 題 是一樣的,都是給定一個升序序列,然後生成二分平衡查找樹。區別在於 108 題給定的是數組,這裏給的是鏈表。

解法一

大家先看一下 108 題 吧,算法的關鍵是取到中間的數據做爲根節點。而這裏鏈表的話,由於不支持隨機訪問,所以會麻煩些。最簡單的思路就是我們把鏈表先用線性表存起來,然後題目就轉換成 108 題了。

爲了方便,把上一道題的數組參數改爲List

public TreeNode sortedListToBST(ListNode head) {
    ArrayList<Integer> nums = new ArrayList<>();
    while (head != null) {
        nums.add(head.val);
        head = head.next;
    }
    return sortedArrayToBST(nums);
}

public TreeNode sortedArrayToBST(ArrayList<Integer> nums) {
    return sortedArrayToBST(nums, 0, nums.size());
}

private TreeNode sortedArrayToBST(ArrayList<Integer> nums, int start, int end) {
    if (start == end) {
        return null;
    }
    int mid = (start + end) >>> 1;
    TreeNode root = new TreeNode(nums.get(mid));
    root.left = sortedArrayToBST(nums, start, mid);
    root.right = sortedArrayToBST(nums, mid + 1, end);
    return root;
}

時間複雜度:O(n)

空間複雜度:數組進行輔助,O(n)

解法二

參考 這裏

有沒有一種方案,不用數組的輔助呢?那麼我們需要解決怎麼得到 mid 的值的問題。

最直接的思路就是根據 start 和 end,求出 mid,然後從 head 遍歷 mid - start 次,就到達了 mid 值。但最開始的 end,我們還得遍歷一遍鏈表才能得到,總體來說就是太複雜了。

這裏有一個求中點節點值的技巧,利用快慢指針。

快指針和慢指針同時從頭部開始遍歷,快指針每次走兩步,慢指針每次走一步,當快指針走到鏈表尾部,此時慢指針就指向了中間位置。

除了求中點節點的值不一樣,基本架構和 108 題 是一樣的。

public TreeNode sortedListToBST(ListNode head) {
    return sortedArrayToBST(head, null);
}

private TreeNode sortedArrayToBST(ListNode head, ListNode tail) {
    if (head == tail) {
        return null;
    }
    ListNode fast = head;
    ListNode slow = head;
    while (fast != tail && fast.next != tail) {
        slow = slow.next;
        fast = fast.next.next;
    }

    TreeNode root = new TreeNode(slow.val);
    root.left = sortedArrayToBST(head, slow);
    root.right = sortedArrayToBST(slow.next, tail); 
    return root;
}

時間複雜度:根據遞歸式可知,T(n) = 2 * T(n / 2 ) + nO(nlog(n))

空間複雜度:O(log(n))

解法三

解法二雖然沒有藉助數組,優化了空間複雜度,但是時間複雜度增加了,那麼有沒有一種兩全其美的方法,時間複雜度是解法一,空間複雜度是解法二。還真有,參考 這裏

主要思想是,因爲我們知道題目給定的升序數組,其實就是二叉搜索樹的中序遍歷。那麼我們完全可以按照這個順序去爲每個節點賦值。

實現的話,我們套用中序遍歷的遞歸過程,並且將 startend 作爲遞歸參數,當 start == end 的時候,就返回 null

先回想一下中序遍歷的算法。

public List<Integer> inorderTraversal(TreeNode root) {
    List<Integer> ans = new ArrayList<>();
    getAns(root, ans);
    return ans;
}

private void getAns(TreeNode node, List<Integer> ans) {
    if (node == null) {
        return;
    }
    getAns(node.left, ans); 
    ans.add(node.val);
    getAns(node.right, ans);
}

之前是將 node.val 進行保存,這裏的話我們是給當前節點進行賦值,爲了依次賦值,我們需要一個cur指針指向給定的數列,每賦一個值就進行後移。

ListNode cur = null;

public TreeNode sortedListToBST(ListNode head) {
    cur = head;
    int end = 0;
    while (head != null) {
        end++;
        head = head.next;
    }
    return sortedArrayToBSTHelper(0, end);
}

private TreeNode sortedArrayToBSTHelper(int start, int end) {
    if (start == end) {
        return null;
    }
    int mid = (start + end) >>> 1;
    //遍歷左子樹並且將根節點返回
    TreeNode left = sortedArrayToBSTHelper(start, mid);
    //遍歷當前根節點並進行賦值
    TreeNode root = new TreeNode(cur.val);
    root.left = left;
    cur = cur.next; //指針後移,進行下一次的賦值
    //遍歷右子樹並且將根節點返回
    TreeNode right = sortedArrayToBSTHelper(mid + 1, end);
    root.right = right;
    return root;
}

時間複雜度:O(n),主要是得到開始的 end,需要遍歷一遍鏈表。

空間複雜度:O(log(n)),遞歸壓棧的消耗。

快慢指針求鏈表的中間值,這個技巧不錯。此外,解法三的模仿中序遍歷的過程,然後把給定的數組依次賦值過去,太強了。

更多詳細通俗題解詳見 leetcode.wang

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章