單連結串列的排序問題
作者:Grey
原文地址:
題目連結
思路一:轉換陣列結合快速排序
將連結串列轉換成陣列,使用快速排序演算法,然後把陣列排序後的結果還原成連結串列。
時間複雜度 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};
}
}