算法学习记录(九十三)

123 阅读2分钟

148. 排序链表

image.png

解: 看到这个题,让我想到了曾经做过的链表partition,那么这里也用快排的思路(看官方题解其实应该用归并最好)。

  1. partition的目的是根据target值的大小,把整个链表划分成三个区域,分别是小于target的区域,等于target的区域,大于target的区域。
  2. 链表partiton的过程就是初始化6个变量,分别代表三个链表区域的头尾节点。然后去遍历整个链表,将每个节点分别挂载到对应的区域节点上,最终将划分好的三个区域分离,然后返回三个独立的链表。由于快排需要随机target,所以还需要返回小于区和大于区的最大值和最小值
  3. partition函数完成后,只需要选定随机数然后让partiton去分区就好了。由于这是个链表,不能用随机下标去取值,所以需要知道这段链表的最大最小值,从这里面选一个随机数。
  4. 调用partiton后,会获得三个链表,让小于区和大于区的链表去递归执行选随机数分区的操作,执行完毕后将小于区的尾结点链接到等于区链表,等于区尾结点链接到大于区链表,然后返回头结点
  5. 以上所说三个链表都是指小于区、等于区、大于区都有值时的情况。所以链接链表的时候需要多注意判断边界条件。
function sortList(head) {
    let min = Infinity
    let max = -Infinity
    let cur = head
    while (cur) {
        min = Math.min(cur.val, min)
        max = Math.max(cur.val, max)
        cur = cur.next
    }
    return getRes(head, max, min)
}
function getRes (node, maxVal, minVal) {
    if (!node || !node.next) return node
    // 取max-min之间的随机数
    const target = ~~(Math.random() * (maxVal - minVal)) + minVal
    // 快排中这里需要返回两个坐标,这里返回分区后的三段链表,以及小于区链表的值区间,大于区链表的值区间(因为取target随机数的时候需要用到)
    const { smStart, eqStart, eqEnd, lgStart, smMin, smMax, lgMin, lgMax } = partitionList(node, target)
    // 处理小于区链表
    const smList = smStart ? getRes(smStart, smMax, smMin) : null
    // 处理大于区链表
    const lgList = lgStart ? getRes(lgStart, lgMax, lgMin) : null
    // 处理完毕后将三个链表互相链接, 需要小于区链表的尾结点去链接后面链表
    let smTail = smList
    while (smTail && smTail.next) smTail = smTail.next
    smTail && (smTail.next = eqStart ?? lgList)
    eqEnd && (eqEnd.next = lgList)
    // 返回链表头结点
    return smList ?? eqStart ?? lgList
}
function partitionList(head, target) {
    // 链表根据target来划分出三个区域,以下六个遍历就是这三个区域的头尾节点
    let smStart,smEnd,eqStart,eqEnd,lgStart,lgEnd = null
    // 快排需要选取随机数,所以需要知道链表值的区间
    let smMin = Infinity
    let smMax = -Infinity
    let lgMin = Infinity
    let lgMax = -Infinity
    let curNode = head
    // 遍历链表进行区域划分
    while (curNode) {
        if (curNode.val < target) {
            smMin = Math.min(curNode.val, smMin)
            smMax = Math.max(curNode.val, smMax)
            smStart = smStart ?? curNode
            smEnd && smEnd.next = curNode
            smEnd = curNode
        }
        if (curNode.val === target) {
            eqStart = eqStart ?? curNode
            eqEnd && eqEnd.next = curNode
            eqEnd = curNode
        }
        if (curNode.val > target) {
            lgMin = Math.min(curNode.val, lgMin)
            lgMax = Math.max(curNode.val, lgMax)
            lgStart = lgStart ?? curNode
            lgEnd && lgEnd.next = curNode
            lgEnd = curNode
        }
        curNode = curNode.next
    }
    // 将划分出来的三个链表分离并返回
    lgEnd && (lgEnd.next = null)
    smEnd && (smEnd.next = null)
    eqEnd && (eqEnd.next = null)
    return { smStart, eqStart, eqEnd, lgStart, smMin, smMax, lgMin, lgMax }
}