【LeetCode选讲·第十二期】「合并K个升序链表」「两两交换链表中的节点」

122 阅读3分钟

T23 合并K个升序链表

题目链接:leetcode-cn.com/problems/me…

贪心算法

这是一道典型的以链表为背景的「贪心算法」题。考虑到题目中所传入的所有链表都已按升序排列,我们只要每次选取头部节点val最小的链表,直至所有链表的next属性都被置为null即可。

这里我们继续使用「哨兵技巧」,忘记的同学请戳这里

由于题目比较简单,这里我们直接过一遍代码:

function mergeKLists(lists) {
    let dummyNode = new ListNode();
    let curNode = dummyNode;
    //过滤所有空链表
    lists = lists.filter(listNode => listNode !== null);
    while(lists.length > 0) {
        /* 搜索持有最小数的节点 */
        let targetNode = null;
        let targetIdx = null;
        let minValue = Infinity;
        lists.forEach((listNode, idx) => {
            if(listNode.val < minValue) {
                minValue = listNode.val;
                targetNode = listNode;
                targetIdx = idx;
            }
        });
        /* 更新lists数组 */
        if(targetNode.next !== null) {
            lists[targetIdx] = targetNode.next;
        } else {
            lists.splice(targetIdx, 1);
        }
        /* 更新答案链表头部节点 */
        curNode = curNode.next =  targetNode;
    }
    return dummyNode.next;
}

T24 两两交换链表中的节点

题目链接:leetcode-cn.com/problems/sw…

比较容易想到的解法

这是一道偏简单的「模拟题」。

通过审题,我们注意到本题主要的难点在于需要处理节点总数为奇数和为偶数的两种链表。

一种比较容易想到的方法是我们可以引入一个变量k,用于在遍历链表的过程中记录链表的节点总数,再进行分别处理。

代码如下:

function swapPairs(listNode) {
    let dummyNode = new ListNode();
    let curNode = dummyNode;
    let k = 0;    //标记当前节点的序号并统计总节点数
    let L1 = null;
    let L2 = null;
    while(listNode !== null) {
        if((++k) % 2 === 1) {
            L1 = listNode;
            listNode = listNode.next;
        } else {
            //交换L1和L2
            L2 = listNode;
            listNode = listNode.next;
            curNode.next = L2;
            curNode = L2.next = L1;
        }
    }
    //对于奇数节点数的链表直接保留最后一位
    if(k % 2 === 1) {
        curNode = curNode.next = L1;
    }
    //统一更新末端节点的next指针
    curNode.next = null;  
    return dummyNode.next;
}

这么做虽然很简单,但缺点也很明显:由于需要进行分别处理,代码会显得非常冗长。

合并处理

如果我们不希望引入新的变量,可以解决这个问题吗?

答案是肯定的,并且这也很容易实现。

其关键就在于我们需要直接将原链表挂载到输出的dummyNode.next上,并且实时更新交换后置换到后面的L1所指的节点为原先L2所指的节点,实现对原链表的直接的局部操作,而不是像上面的代码:通过维护始终指向原链表剩余部分头部的变量listNode,来将一个个挪动原链表中的节点到dummyNode.next上。

代码如下:

function swapPairs(listNode) {
    let dummyNode = new ListNode();
    let curNode = dummyNode;  
    curNode.next = listNode;
    //这里我们让curNode始终指向链表已完成交换部分的最右端节点
    //故curNode.next和curNode.next.next就指向下一组需要交换的节点
    while(curNode.next !== null && curNode.next.next !== null) {
        let L1 = curNode.next;
        let L2 = curNode.next.next;
        curNode.next = L2;  //将curNode的下一个节点指向L2
        L1.next = L2.next;   //将L1的下一个节点指向原本L2的下一个节点
        curNode = L2.next = L1;  //将L2的下一个节点指向L1
    }
    return dummyNode.next;
}

写在文末

我是来自在校学生编程兴趣小组江南游戏开发社的PAK向日葵,我们目前正在致力于开发自研的非营利性网页端同人游戏《植物大战僵尸:旅行》,以锻炼我们的前端应用开发能力。

我们诚挚邀请您体验我们的这款优秀作品,如果您喜欢TA的话,欢迎向您的同事和朋友推荐。如果您有技术方面的问题希望与我们探讨,欢迎直接与我联系。您的支持是我们最大的动力!

QQ图片20220701165008.png