單連結串列的排序問題

2022-11-23 21:00:17

單連結串列的排序問題

作者:Grey

原文地址:

部落格園:單連結串列的排序問題

CSDN:單連結串列的排序問題

題目連結

LeetCode 148. Sort List

思路一:轉換陣列結合快速排序

將連結串列轉換成陣列,使用快速排序演演算法,然後把陣列排序後的結果還原成連結串列。

時間複雜度 O(n*logn),空間複雜度 O(n)。這個思路的核心就是快速排序演演算法,快速排序演演算法見如下部落格荷蘭國旗問題與快速排序演演算法說明,但是空間複雜度沒有達到最優(最優解可以做到空間複雜度O(1)),完整程式碼如下:

class Solution {
    public ListNode sortList(ListNode head) {
        if (head == null || head.next == null) {
            return head;
        }
        int size = 0;
        ListNode cur = head;
        while (cur != null) {
            size++;
            cur = cur.next;
        }
        ListNode[] nodes = new ListNode[size];
        cur = head;
        int i = 0;
        while (cur != null) {
            nodes[i++] = cur;
            cur = cur.next;
        }
        sortArr(nodes);
        return arrToList(nodes);
    }

    private void sortArr(ListNode[] nodes) {
        p(nodes, 0, nodes.length - 1);
    }

    private void p(ListNode[] arr, int L, int R) {
        if (L >= R) {
            return;
        }
        swap(arr, L + (int) (Math.random() * (R - L + 1)), R);
        int[] equalArea = netherlandsFlag(arr, L, R);
        p(arr, L, equalArea[0] - 1);
        p(arr, equalArea[1] + 1, R);
    }

    private int[] netherlandsFlag(ListNode[] nodes, int L, int R) {
        if (L > R) {
            return new int[]{-1, -1};
        }
        if (L == R) {
            return new int[]{L, R};
        }
        int less = L - 1;
        int more = R;
        ListNode num = nodes[R];
        for (int i = L; i < more; i++) {
            if (nodes[i].val < num.val) {
                swap(nodes, ++less, i);
            } else if (nodes[i].val > num.val) {
                swap(nodes, i--, --more);
            }
        }
        swap(nodes, R, more);
        return new int[]{less + 1, more};
    }

    public void swap(ListNode[] nodes, int i, int j) {
        if (i != j) {
            ListNode t = nodes[i];
            nodes[i] = nodes[j];
            nodes[j] = t;
        }
    }

    public ListNode arrToList(ListNode[] nodes) {
        ListNode head = nodes[0];
        ListNode cur = head;
        for (int i = 1; i < nodes.length; i++) {
            cur.next = nodes[i];
            cur = nodes[i];
        }
        cur.next = null;
        return head;
    }
}

思路二:使用歸併排序

本題可以利用歸併排序演演算法,在時間複雜度同樣為O(n*logn)的情況下,空間複雜度可以達到O(1),本題參考了歸併排序的迭代版本實現,歸併排序演演算法的說明見如下部落格與歸併排序相關的一些問題

歸併排序的迭代方法,思路如下

設定一個步長,從 1 開始,1,2,4,8,16……2^n 方式遞增

每次處理對應步長的連結串列區間範圍內的排序。

步長超過或者等於連結串列長度,則整個連結串列排序完成。

比如原始連結串列為: 1->3->4->2->5->6->4->6->8

先設定步長為 1,連結串列分成如下區間

[0……1],[2……3],[4……5],[6……7],[8……8]

注:最後一組不夠分,則單獨作為一組處理。

將如上區間內部排好序,得到的排序後的連結串列為

1->3,2->4,5->6,4->6,8

然後設定步長為 2,連結串列分成如下區間

[0……3],[4……7],[8……8]

然後將上述區間內部先排好序,得到連結串列為

1->2->3->4,4->5->6->6,8

然後設定步長為 4,連結串列分成如下區間

[0……7],[8……8]

然後將上述區間內部先排好序,得到連結串列為

1->2->3->4->4->5->6->6,8

最後設定步長為 8,連結串列只有一個區間,直接排序,得到最後結果

1->2->3->4->4->5->6->6->8

完整程式碼如下

class Solution {
    public  ListNode sortList(ListNode head) {
        int N = 0;
        ListNode cur = head;
        while (cur != null) {
            N++;
            cur = cur.next;
        }
        ListNode h = head;
        ListNode teamFirst = head;
        ListNode pre = null;
        int L = 1;
        while (L < N) {
            while (teamFirst != null) {
                ListNode[] hthtn = hthtn(teamFirst, L);
                ListNode[] mhmt = merge(hthtn[0], hthtn[1], hthtn[2], hthtn[3]);
                if (h == teamFirst) {
                    h = mhmt[0];
                    pre = mhmt[1];
                } else {
                    pre.next = mhmt[0];
                    pre = mhmt[1];
                }
                teamFirst = hthtn[4];
            }
            teamFirst = h;
            pre = null;
            L <<= 1;
        }
        return h;
    }

    public  ListNode[] hthtn(ListNode teamFirst, int len) {
        ListNode ls = teamFirst;
        ListNode le = teamFirst;
        ListNode rs = null;
        ListNode re = null;
        ListNode next = null;
        int p = 0;
        while (teamFirst != null) {
            // 之所以這裡是小於等於,是因為這裡可能不滿足分組的個數(不足個數)
            if (p <= len - 1) {
                le = teamFirst;
            }
            if (p == len) {
                rs = teamFirst;
            }
            if (p > len - 1) {
                re = teamFirst;
            }
            if (p == (len << 1) - 1) {
                break;
            }
            p++;
            teamFirst = teamFirst.next;
        }
        if (le != null) {
            le.next = null;
        }
        if (re != null) {
            next = re.next;
            re.next = null;
        }
        return new ListNode[]{ls, le, rs, re, next};
    }

    // 返回merge後的頭和尾
    // 注意邊界考慮
    public  ListNode[] merge(ListNode h1, ListNode t1, ListNode h2, ListNode t2) {
        if (h2 == null) {
            return new ListNode[]{h1, t1};
        }
        ListNode head = h1;
        ListNode tail = h1;
        ListNode c = null;
        ListNode pre = null;
        while (h1 != t1.next && h2 != t2.next) {
            if (h1.val > h2.val) {
                c = h2;
                h2 = h2.next;
            } else {
                c = h1;
                h1 = h1.next;
            }
            if (pre == null) {
                // 設定merge後的頭節點,賦值給head
                // 後續就由pre去往下插入節點
                pre = c;

                head = c;
            } else {
                pre.next = c;
                pre = c;
            }
        }
        // h1節點沒越界
        if (h1 != t1.next) {
            while (h1 != t1.next) {
                pre.next = h1;
                pre = pre.next;
                tail = h1;
                h1 = h1.next;
            }
        } else {
            while (h2 != t2.next) {
                pre.next = h2;
                pre = pre.next;
                tail = h2;
                h2 = h2.next;
            }
        }
        return new ListNode[]{head, tail};
    }
}

更多

演演算法和資料結構筆記