[前端] 保姆式教你合并k个升序列表

963 阅读5分钟

本文已参与「掘力星计划」,赢取创作大礼包,挑战创作激励金。


尽我所能让你不再默认跳过难题 .

合并K个升序链表

给你一个链表数组,每个链表都已经按升序排列。

请你将所有链表合并到一个升序链表中,返回合并后的链表。

1:
输入:lists = [[1,4,5],[1,3,4],[2,6]]
输出:[1,1,2,3,4,4,5,6]
解释:链表数组如下:
[
  1->4->5,
  1->3->4,
  2->6
]
将它们合并到一个有序链表中得到。
1->1->2->3->4->4->5->62:
输入: lists = []
输出: []

例3:
输入: lists = [[]]
输出: []

首先 , 见到题先不要慌 , 先想一想自己要用什么姿势来干这道题 .

作为优雅的前端学习者 , 我们首先要让自己足够淡然 .


那么先来复习一下相关知识

链表

image.png

图片来自于力扣免费学习中的 链表 好奇宝宝可以点击查阅 .

什么甜美的甜美的是链表 , 说白了就是对象套对象 , 每个对象都有一个值和一个next指针指向下一个对象 .

function ListNode(val) {
            this.val = val;
            this.next = null;
        }

排序

sort 方法用于对数组的元素进行排序。

语法: arr.sort([compareFunction])

compareFunction(a,b) :  用来指定按某种顺序进行排列的函数。如果省略,元素按照转换为的字符串的各
个字符的Unicode位点进行排序。
-   `a`
-   第一个用于比较的元素。
-   `b`
-   第二个用于比较的元素.

如果 `compareFunction(a, b)` 小于 0 ,那么 a 会被排列到 b 之前
如果 `compareFunction(a, b)` 等于 0 , a 和 b 的相对位置不变。
如果 `compareFunction(a, b)` 大于 0 , b 会被排列到 a 之前。
`compareFunction(a, b)` 必须总是对相同的输入返回相同的比较结果,否则排序的结果将是不确定的。

返回值: 排序后的数组。请注意,数组已原地排序,并且不进行复制。

开始解题

首先我们得明确思路 , 先得把值取出来

//我们需要一个结构来存放数组中每个链表的数据域 , 我选择用数组来存放 . 
const list = [];

//我们需要多次添加 , 所以创建一个循环 .
for (let i = 0; i < lists.length; i++) {

//然后我们需要将数据加入数组当中 , 先将数组中的每个链表存到一个变量里 .
        let node = lists[i];
        
//此时对数组进行值的添加 . while会对传入的node进行判断 , 当node == null时 当前链表就被处理完毕
        while (node) {
            list.push(node.val);
            node = node.next;
        }
//while循环结束后 , 数组中一存入第一个链表的所有值 . 
}

然后 , 为了保持结构的有序性 , 我们需要将此时的数组进行排序 .


//我选择对前端萌新来说最简单的冒泡排序 
list.sort((a, b) => a - b);

最后 , 完成最后的一步 -- 将数组转化为链表

//创建头结点
const res = new ListNode();
let new = res;

//循环进行链表数据的加入
for (let i = 0; i < list.length; i++) {
        now.next = new ListNode(list[i]);
        now = now.next;
}
return res.next;

最后再来看看整个代码 :

/**
 * @param {ListNode[]} lists
 * @return {ListNode}
 */
var mergeKLists = function (lists) {
    const list = [];
    for (let i = 0; i < lists.length; i++) {
        let node = lists[i];
        while (node) {
            list.push(node.val);
            node = node.next;
        }
    }
    list.sort((a, b) => a - b);
    const res = new ListNode();
    let now = res;
    // console.log(list)
    for (let i = 0; i < list.length; i++) {
        now.next = new ListNode(list[i]);
        now = now.next;
    }
    return res.next;
};

一道困难题就用最简单的方法解决了 .

但我们永远不会安于现状 .

"人类的赞歌是勇气的赞歌,人类的伟大是勇气的伟大!” ——威廉·A·齐贝林

真正的勇气是知道该畏惧什么和不该畏惧什么,以及去改变那些可以改变的事实。

我们要不断探究 , 像一代 JoJo 一样 , 不断磨砺 .


闲话不说先上代码

var mergeKLists = function(lists) {
    return lists.reduce((p, n) => {
        while (n) {
            p.push(n), n = n.next
        }
        return p
    },[]).sort((a, b) => a.val - b.val).reduceRight((p, n) => (n.next = p, p = n, p), null)
};

作者:mantoufan
链接:https://leetcode-cn.com/problems/merge-k-sorted-lists/solution/reduce-sort5xing-dai-ma-chao-93-by-mantoufan/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

让自己成长的第一步 , 首先得学习大佬的思路

  • 看懂大佬的代码
  • 针对性进行知识的补充
  • 学习后以大佬的思路进行代码实现

慢慢来解析 , 这段代码首先用了reduce()

reduce()方法对数组中的每个元素执行一个由您提供的reducer函数(升序执行),
将其结果汇总为单个返回值。
reducer 函数接收4个参数:

1.  Accumulator (acc) (累计器)
2.  Current Value (cur) (当前值)
3.  Current Index (idx) (当前索引)
4.  Source Array (src) (源数组)

您的 reducer 函数的返回值分配给累计器,该返回值在数组的每个迭代中被记住,并
最后成为最终的单个结果值。

语法: 
arr.reduce(callback(accumulator, currentValue[, index[, array]])[, initialValue])

返回值: 函数累计处理的结果

通俗来说 , reduce可以将链表中的每个值都放入回调函数中 .

那么之后的回调函数部分 :

(p, n) => {
//当存在n结点时 , 不断将n加入数组p中 , 并将n指向下一个结点
    while (n) {
        p.push(n), n = n.next
    }
    return p

再之后:

sort((a, b) => a.val - b.val).reduceRight((p, n) => (n.next = p, p = n, p), null)

回调函数会返回一个 p 这是存着结点的一个数组 , 此时调用数组的sort方法 .

数组每个元素都是一个单独的结点(对象) a.val 和 b.val 即是每个结点的值 , 此时进行了排序 .

最后使用了 reduceRight() 方法

reduceRight() 方法的功能和 reduce() 功能是一样的,不同的是 reduceRight() 从数组的末尾向前将数组中的数组项做累加。

reduceRight() 内部的回调函数则是 , 将传入的每个对象(结点)的next指向再重新连起来 , 相当于将单独排序的结点对象重新连成链表 .

在这道题上 , 同样的思路 , 不同的代码给人的表现力是不同的 , 力扣的题解区还有很多种不同的思路的解法 , 递归+分治 , 双指针 ...... 学海无涯 , 希望我们都能一生学习 .

「欢迎在评论区讨论,掘金官方将在掘力星计划活动结束后,在评论区抽送100份掘金周边,抽奖详情见活动文章」