题目要求:O(n log n) 时间复杂度和常数级空间复杂度下
方法
1, 暴力法,取出来放数组里面,排序,然后重新生成链表,但是不符合题目的要求
2,递归 【自上而下】
思路就是:
2-1,找到链表的中间点(参考找到链表中间点)
2-2,对 中间点分割成为的两个链表分别进行排序,到这里就是调用自己了,递归,对两个链表进行排序。
2-3, 然后合并这两个 有序链表(参考合并有序数组)
3,迭代 【自下而上】
实现
第二种和第三种都需要一个公共方法,合并有序数组,先定义这个方法:
节点定义:
function ListNode(val, next) {
this.val = (val===undefined ? 0 : val)
this.next = (next===undefined ? null : next)
}
合并两个有序链表:
function merge(head1, head2) {
let dummy = new ListNode(0)
let head = dummy
while (head1!= null && head2!=null) {
if (head1.val <= head2.val) {
head.next = head1
head1 = head1.next
} else {
head.next = head2
head2 = head2.next
}
head = head.next
}
// 优化
// while(head1 != null) {
// head.next = head1
// head1 = head1.next
// head = head.next
// }
// while(head2 != null) {
// head.next = head2
// head2 = head2.next
// head = head.next
// }
if (head1 != null) {
head.next = head1
} else if (head2 != null) {
head.next = head2
}
return dummy.next
}
递归
递归方法一
// 方法 2.1 (自上而下):
var sortList = function(head) {
// 为啥要tail, 因为每次分割
var sortListInner = function(head, tail) {
//递归跳出条件,如果节点个数<=1
if (head === null) {
return head
}
// 因为是前闭后开[head, tail)
if (head.next === tail) {
head.next = null
return head
}
// 拆分
let slow = head
let fast = head
while (fast != tail && fast.next != tail) {
slow = slow.next
fast = fast.next.next
}
let middle = slow
// 合并排序
let list1 = sortListInner(slow, middle)
let list2 = sortListInner(middle, tail)
let res = merge(list1, list2)
return res
}
递归方法二
// 方法 2.1 (自上而下)对上面方法的改进:
var sortList = function(head) {
if (head == null || head.next == null) { return head }
// 拆分
let tail = head
let slow = head
let fast = head
while(fast != null && fast.next != null) {
tail = slow
slow = slow.next
fast = fast.next.next
}
// slow 是中间节点 middle
// tail.next = slow/middle
tail.next = null
let left = sortList(head)
let right = sortList(slow)
let res = merge(left, right)
return res
}
迭代
有点复杂,需要自己用例子手画一下
/方法2.2(自下而上):
var sortList = function(head) {
if (head == null) { return head }
let len = 0
let node = head
//1. 计算长度
while(node != null) {
len++
node = node.next
}
// 2. 引入dummyNode
let dummy = new ListNode(0)
dummy.next = head
// 比较的长度每次*2, 2^0 2^1 2^2 2^3 2^4
for (let subLen = 1; subLen < len; subLen <<=1) {
let prev = dummy
let cur = dummy.next
// 比如 l = 1, 说明每次需要合并2个长度为1的ListNode
while(cur != null) {
// 3.1 拆一个长度为subLen的链表1
let head_1 = cur
for (let i = 1; i < subLen && cur != null && cur.next != null; i++) {
cur = cur.next
}
// 3.2 拆一个长度为subLen的链表2
let head_2 = cur.next
cur.next = null // 断开链表1 与 链表2之间的关系
cur = head_2 // 链表2的表头,重新赋值给cur
for (let i = 1; i < subLen && cur != null && cur.next != null; i++) { // 再次拆分长度为subLen的链表,但是这个链表【最后一个】长度可能 < subLen
cur = cur.next
}
// 3.3 断开链表2最后的next链接
let next = null
if (cur != null) {
next = cur.next
cur.next = null
}
// 3.4 合并这两个有序链表
const merged = merge(head_1, head_2)
// 这一步很关键,因为前面将dummy的指针赋值给了prev [let prev = dummy], 这里实际上while第一次执行之后,dummy.next就是最新的合并的第一个节点
// 后续while第二次进来的时候,prev已经和dummy没有关系了,手写画一下更加清晰,不然头大
// 将pre.next 指向这个合并后的,之前prev.next 指向的是head_1
prev.next = merged
while(prev.next != null) { // 将prev移动到subLen*2的这个位置,也就是 merged的最后一个节点
prev = prev.next
}
cur = next // 当前cur应该是下一个节点, 请注意到这一步执行之后,prev.next === cur 这个判定为true
// console.log(cur, prev.next === cur , "内层循环-----")
}
// 外层while 循环完之后, 这一轮的合并已经走完
}
return dummy.next
}
// from bottom to top,举个例子:
// 比如最开始为 6 5 3 1 8 7 2 4
// 最开始就是 (6 5 比较)(3 1 比较)(8 7 比较)(2 4 比较)【这时候 比较的长度是1】
// 变为 (5 6) (1 3) (7 8) (2 4) 【这个时候比较的长度是2个,56一组 13一组】
// 变为 (1 2 3 4) (5 6 7 8) 【这时候比较的长度是4个,1356一组 2478一组】
// 比较的长度为 1=2^0 2=2^1 4=2^2
// 这个比较的长度subLength是需要小于 链表的总长度,因为需要比较两个链表
// 最好的方式是举一个例子,在纸上画一下,不然光看,总会有些疑问