148. sort list 排序链表

148题排序链表

题目要求: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是需要小于 链表的总长度,因为需要比较两个链表
 
  // 最好的方式是举一个例子,在纸上画一下,不然光看,总会有些疑问

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值