算法题-链表 JavaScript | 有图 | 有过程 | 有真相

链表算法题解

配套链表结构学习

1. 合并两个有序链表

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]

在这里插入图片描述

l1l2 都不是空链表时,判断 l1l2 哪一个链表的头节点的值更小,将较小值的节点添加到结果链表里,当一个节点被添加到结果里之后,将对应链表中的节点向后移一位。

在循环终止的时候, l1l2 至多有一个是非空的。将非空链表接在合并链表的后面,并返回合并链表即可。

var mergeTwoLists = function (list1, list2) {
    const list = new ListNode(0)
    let tail = list
    while (list1 && list2) {
        if (list1.val > list2.val) {
            tail.next = list2
            list2 = list2.next
        } else {
            tail.next = list1
            list1 = list1.next
        }

        tail = tail.next
    }
    tail.next = list1 || list2;
    return list.next; 
};

2. 删除排序链表中的重复元素

给定一个已排序的链表的头 head删除所有重复的元素,使每个元素只出现一次 。返回 已排序的链表

输入:head = [1,1,2,3,3]
输出:[1,2,3]

在这里插入图片描述

指定cur指针指向头部 head,当 curcur.next 的存在为循环结束条件,当二者有一个不存在时说明链表没有去重复的必要了,当 cur.valcur.next.val 相等时说明需要去重,则将 cur 的下一个指针指向下一个的下一个,这样就能达到去重复的效果,如果不相等则 cur 移动到下一个位置继续循环。

var deleteDuplicates = function(head) {
    let curr = head
    while(curr && curr.next){
        if(curr.val === curr.next.val){
            curr.next = curr.next.next
        } else {
            curr = curr.next
        }
    }
    return head
};

3. 环形链表

给你一个链表的头节点 head ,判断链表中是否有环。如果链表中存在环 ,则返回 true。 否则,返回 false

输入:head = [1,2,3,4,5], pos = 3
输出:true
解释:链表中有一个环,其尾部连接到第三个节点。

在这里插入图片描述

定义两个指针,一快一满。慢指针每次只移动一步,而快指针每次移动两步。初始时,慢指针在位置 head,而快指针在位置 head.next。这样一来,如果在移动的过程中,快指针反过来追上慢指针,就说明该链表为环形链表。否则快指针将到达链表尾部,该链表不为环形链表。

var hasCycle = function (head) {
    if (head == null || head.next == null) {
        return false;
    }
    let slow = head
    let fast = head.next
    while (slow != fast) {
        if (slow === null || fast === null) {
            return false
        }
        slow = slow.next
        if (!fast.next) {
            return false
        }
        fast = fast.next.next
    }
    return true
};

4. 相交链表

给你两个单链表的头节点 headAheadB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null

输入:listA = [1,2,3,4,5], listB = [2,3,4,5]
输出:Intersected at 3
解释:相交节点的值为 3 (注意,如果两个链表相交则不能为 0)。
从各自的表头开始算起,链表 A[1,2,3,4,5],链表 B[2,3,4,5]。
在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 1 个节点。

第一个公共节点node ,**链表 headA**的节点数量为 a ,**链表 headB**的节点数量为b两链表的公共尾部的节点数量为 c ,则有:

  • 头节点 headAnode 前,共有 a - c 个节点;
  • 头节点 headBnode 前,共有 b - c 个节点;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Kvkvb8E0-1660211264123)(E:\Web\test\Notes\数据结构与算法\链表\images\相交链表.png)]

考虑构建两个节点指针 A , B 分别指向两链表头节点 headA , headB ,做如下操作:

  • 指针 A 先遍历完链表 headA ,再开始遍历链表 headB ,当走到 node 时,共走步数为:a + (b - c)

  • 指针 B 先遍历完链表 headB ,再开始遍历链表 headA ,当走到 node 时,共走步数为:b + (a - c)

如下式所示,此时指针 A , B 重合,并有两种情况:

a + (b - c) = b + (a - c)

  1. 若两链表 有 公共尾部 (即 c > 0 ) :指针 A , B 同时指向第一个公共节点node

在这里插入图片描述

  1. 若两链表 无 公共尾部 (即 c = 0 ) :指针 A , B 同时指向 null
    因此返回 A或者B即可。

在这里插入图片描述

var getIntersectionNode = function (headA, headB) {
    let nA = headA,nB = headB
    while (nA != nB) {
        nA = nA === null ? headB : nA.next
        nB = nB === null ? headA : nB.next
    }
    return nA
};

5. 两数相加

给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。

请你将两个数相加,并以相同形式返回一个表示和的链表。

输入:l1 = [2,4,3], l2 = [5,6,4]
输出:[7,0,8]
解释:342 + 465 = 807

在这里插入图片描述

将两个链表看成是相同长度的进行遍历,如果一个链表较短则在前面补 0,比如 987 + 23 = 987 + 023 = 1010
每一位计算的同时需要考虑上一位的进位问题,而当前位计算结束后同样需要更新进位值
如果两个链表全部遍历完毕后,进位值为 1,则在新链表最前方添加节点 1

var addTwoNumbers = function(l1, l2) {
    let pre = new ListNode(0)
    let cur = pre
    let carry = 0
    while(l1 !== null || l2 !== null || carry !== 0){
        l1Val = l1 !== null ? l1.val : 0
        l2Val = l2 !== null ? l2.val : 0
        sumVal = l1Val+l2Val+carry
        carry = parseInt(sumVal/10)

        const sumNode = new ListNode(sumVal%10)
        cur.next = sumNode
        cur = sumNode

        if(l1 !== null) l1 = l1.next
        if(l2 !== null) l2 = l2.next
    }
    return pre.next
};

6. 删除链表的倒数第 N 个结点

给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。

输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]

在这里插入图片描述

  1. 设定双指针 l1l2,初始都指向虚拟节点 head
  2. 移动 l2,直到 l1l2 之间相隔的元素个数为 n
  3. 同时移动 l1l2,直到 l2 指向的为 NULL
  4. l1 的下一个节点指向下下个节点
var removeNthFromEnd = function (head, n) {
    let l1 = head, l2 = head
    // 让l2和l1相差n个节点
    for (let i = 0; i < n; i++) {
        if (l2.next) {
            l2 = l2.next
        } else {
            return head.next
        }
    }
    // 两个指针同步往后移动,一直到l2为null时
    while (l2.next !== null) {
        l1 = l1.next
        l2 = l2.next
    }
    l1.next = l1.next.next
    return head
};

7. 两两交换链表中的节点

给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。

输入:head = [1,2,3,4]
输出:[2,1,4,3]

这里需要用到3个指针,l0l1l2,其中l0表示l1的前一个指针。

假设链表为 :1->2->3->4

这里,我们新建一个节点来表示头节点,方便后续初始化链表,于是链表变为:

0->1->2->3->4

迭代时,每次处理两个节点,所以有

  • 第一轮l1指向1,l2指向2,l0指向0。
  • 第二轮l1指向3,l2指向4,l0指向1。
  • 第三轮 l1指向null(迭代完成)

我们通过 l1.next = l2.nextl2.next = l1 就交换了两个节点,于是1->2 变为 2->1 。但是这里有一个细节,原l0指向的下一个节点为1,这边需要初始化链表,将它的指向修改为2,完成第一轮后的链表如下:

0->2->1->3->4

此时,l0指向0l1指向1l2指向2。当然,我们在链表修改完成后指针也需要l0初始化,将它变为下一轮循环体的前一个节点,也就是3->4的前一个节点1,方便下一个循环。

思路图:

在这里插入图片描述

细节实现图:

在这里插入图片描述

var swapPairs = function (head) {
    let pre = new ListNode(0)
    pre.next = head
    let l1 = pre, l2 = pre, l0 = pre
    while (l1.next !== null && l1.next.next !== null) {
        // l1移动一步,l2移动两步,l0暂时不动
        l1 = l1.next
        l2 = l1.next 
        // 进行节点交换
        l1.next = l2.next
        l2.next = l1
        //初始化链表,让l0指向交换后的前一个节点(0->1->2 交换后 0->1且2->1 ,需要让0->2 )
        l0.next = l2
        //初始化指针,让l0、l2同时指向l1
        l2 = l1
        l0 = l1
    }
    return pre.next
};

8. 旋转链表

给你一个链表的头节点 head ,旋转链表,将链表每个节点向右移动 k 个位置。

输入:head = [1,2,3,4,5], k = 2
输出:[4,5,1,2,3]

这里需要先算下长度count,同时也让指针(tail)停留在最后一个结点处,然后算出新的kk=k % count), 即是将要旋转的结点个数。

细节:当 k==0时,链表旋转完成后其实是没有变化的,无需执行后边的算法直接返回链表就行。
接下来,形成闭环;即让尾节点(tail)指向head,这里它向右边旋转k个则等同于让倒数第k个成为新的头节点,所以再从当前节点(tail)走一次,找到倒数第count-k的前一个节点,将它的next指向为null即完成了链表的旋转。

细节:需要更新头节点

流程实现图:

在这里插入图片描述

var rotateRight = function (head, k) {
    if(head === null || head.next === null || k===0) return head
    let tail = head
    let count = 1
    while (tail.next) {
        tail = tail.next
        count++
    }
    k = k % count
    if (k === 0) return head
    tail.next = head
    let index = 0
    while (index++ < count - k) {
        tail = tail.next
    }
    let newHead = tail.next
    tail.next = null
    return newHead
};

9. 合并K个升序链表

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

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

输入: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->6

在了解了如何合并两个有序链表后的基础上,我们使用一个变量 target 来维护以及合并链表,第 i 次循环就把第 i 个链表和 target 合并,答案保存到 target 中。

如何合并两个升序链表请查看 目录1

在这里插入图片描述

var mergeKLists = function (lists) {
    if (lists.length == 0) return null
    if (lists.length === 1) return lists[0]
    let target = null
    for (let i = 0; i < lists.length; i++) {
        target = merge(target, lists[i])
    }
    return target
};

function merge(list1, list2) {
    const list = new ListNode(0)
    let tail = list
    while (list1 && list2) {
        if (list1.val > list2.val) {
            tail.next = list2
            list2 = list2.next
        } else {
            tail.next = list1
            list1 = list1.next
        }

        tail = tail.next
    }
    tail.next = list1 || list2;
    return list.next; 
}

10. K 个一组翻转链表

给你链表的头节点 head ,每 k 个节点一组进行翻转,请你返回修改后的链表。

k 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。

你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。

输入:head = [1,2,3,4,5], k = 2
输出:[2,1,4,3,5]


输入:head = [1,2,3,4,5], k = 3
输出:[3,2,1,4,5]
  1. 将链表通过k分为一个个的子链表(需要翻转的链表部分)
  2. 需记录翻转链表前驱和后继,方便翻转完成后把已翻转部分和未翻转部分连接起来
  3. 初始需要两个变量 preendpre 代表待翻转链表的前驱,end 代表待翻转链表的末尾
  4. 通过pre可以找到待翻转链表的开头start,通过end找到待翻转链表的后继
  5. 过程中,当end无法进行的时候代表链表已完成反转

在这里插入图片描述

var reverseKGroup = function (head, k) {
    let list = new ListNode(0)
    list.next = head

    let pre = list, end = list
    while (end.next) {
        for (let i = 0; i < k && end; i++) {
            end = end.next
        }
        if (end == null) break;

        // 确认start 和 end 后
        let start = pre.next,next = end.next
        end.next = null
        pre.next = reverse(start)
        start.next = next

        pre = start
        end = pre
    }

    return list.next
};

function reverse(head) {
    let pre = null, curr = head
    while (curr) {
        let next = curr.next
        curr.next = pre
        pre = curr
        curr = next
    }
    return pre
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值