链表的合并
真题描述:将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有结点组成的。
示例: 输入:1->2->4, 1->3->4 输出:1->1->2->3->4->4
分析: 将两个链表从头开始对比,根据对比的值依次插入新的链表,这里与其说是新的链表,其实就是一个人造的前驱节点,该节点的下一节点指向哪里是根据两个链表的对比来的
function ListNode(val) {
this.val = val;
this.next = null;
}
function mergeTwoLists(l1,l2){
// 初始化一个链表
let head = new ListNode();
// 当前的链表,可以开始往链表上添加内容
let cur = head;
while (l1 && l2) {
// 对比两个链表的值,如果l1值小,cur的next指向l1,并且将l1向前一步
if(l1.val <= l2.val){
cur.next = l1;
l1 = l1.next;
}else {
cur.next = l2;
l2 = l2.next;
}
// cur链表有值之后也要往前一步
cur = cur.next;
}
// 链表如果不一样长,需要将cur.next指向剩下的链表
cur.next = l1 === null ? l2 : l1;
return cur;
}
链表的删除
真题描述:给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次 示例 1:
输入: 1->1->2
输出: 1->2
示例 2:
输入: 1->1->2->3->3
输出: 1->2->3
分析:删除某一节点,是将目标节点的前驱节点的next指向目标节点的后继节点;因为已有链表是排序好的,所以只需要将当前节点和下一节点进行比较就好
function deleteDuplicates(head){
let cur = head;
while (cur && cur.next) {
// 如果当前节点和下一节点值相同,将当前节点的next指向下一节点的next
if(cur.val === cur.next.val){
cur.next = cur.next.next;
}else {
cur = cur.next;
}
}
return head;
}
链表删除问题的延申
真题描述:给定一个排序链表,删除所有含有重复数字的结点,只保留原始链表中 没有重复出现的数字
示例 1:
输入: 1->2->3->3->4->4->5
输出: 1->2->5
示例 2:
输入: 1->1->1->2->3
输出: 2->3
分析:这类问题也叫dummy问题,因为要删除的节点数不定,那么目标节点就不定,导致前驱节点也不知道,所以需要人为的造一个前驱节点
function deleteDuplicates(head){
// 如果存在多个节点
if(head && head.next){
const dummy = new ListNode();
// 指向链表的头节点
dummy.next = head;
// dummy的头节点val是空,所以从下一节点开始对比
let cur = dummy;
while (cur.next && cur.next.next) {
if(cur.next.val === cur.next.next.val){
// 因为会存在多个重复值,所以需要记录下当前值
let val = cur.next.val;
// 这里是判断cur.next是因为需要从当前节点开始删除,并且已经满足的后面两个节点是一样的
while (cur.next && cur.next.val === val) {
cur.next = cur.next.next;
}
}else {
cur = cur.next;
}
}
}
return dummy.next;
}
链表中的快慢指针
理解:
快慢指针是指,两个指针从同一方向出发,只是一个在前一个在后,并且一个快一个慢
真题描述:给定一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
示例: 给定一个链表: 1->2->3->4->5, 和 n = 2.
当删除了倒数第二个结点后,链表变为 1->2->3->5.
说明: 给定的 n 保证是有效的。
解析:这里涉及到的是倒数第n个,链表不能倒着遍历,所以将问题转换为删除正数的第len-n-1个节点,之前说过删除节点需要定位目标节点的前驱节点,即len-n个节点;这里我们需要快指针去求链表的长度,慢指针就能定位目标节点;根据传入的n让快指针先走n步,然后快慢指针一起走,快指针走到最后的时候,慢指针所处的位置就是要删除节点的前驱节点
function removeNthFromEnd (head,n) {
let dummy = new ListNode();
dummy.next = head;
// 设置快慢指针
let fast = dummy;
let slow = dummy;
// 快指针一直走,走到第n个位置
while (n !== 0) {
fast = fast.next;
n--;
}
// 快慢指针一起走,当快指针走到队尾,就可以得到目标节点
while (fast.next) {
fast = fast.next;
slow = slow.next;
}
// 删除后继节点
slow.next = slow.next.next;
return dummy.next;
}
链表的反转
真题描述:定义一个函数,输入一个链表的头结点,反转该链表并输出反转后链表的头结点
示例:
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
解析: 将链表进行反转即可,这里需要用到三个指针,cur表示目标节点,pre是前驱节点,next是后继节点;当目标节点的next指向前驱节点的时候就反转了;然后将三个指针往前移动,这是目标节点的后继节点是存在next中的,反转结束之后pre就会变成链表的头节点
function reverseList(head){
let pre = null;
let cur = head;
// 当前节点遍历完整个链表会是null
while (cur) {
// 先将next存下来
let next = cur.next;
cur.next = pre;
// 将pre往前走
pre = cur;
// cur往前走
cur = next;
}
return pre;
}
局部反转一个链表
真题描述:反转从位置 m 到 n 的链表。请使用一趟扫描完成反转。
示例:
输入: 1->2->3->4->5->NULL, m = 2, n = 4
输出: 1->4->3->2->5->NULL
解析: 很容易想到,找到需要反转的段,根据链表的反转就可以进行反转;只是需要将链表的前后段缓存下拉
const reverseBetween = function(head, m, n) {
let dummy = new ListNode();
// 先让p走到反转区间的初始位置
let p = dummy;
for(let i=0;i<m - 1;i++){
p = p.next;
}
// 将反转区间前面的内容缓存起来
let leftHead = p;
// 用start表示需要反转区间的初始位置
let start = leftHead.next;
let pre = start;
let cur = pre.next;
// 开始反转
for(let i=m;i<n;i++){
let next = cur.next;
cur.next = pre;
prev = cur;
cur = next;
}
// 得到pre是反转的
leftHead.next = pre;
start.next = cur;
return dummy.next;
}
环形链表
真题描述:给定一个链表,判断链表中是否有环。
解析: 对指针走过的节点都添加一个标记,下次走来的时候如果有标记代表是一个环
function hasCycle(head){
while (head) {
if(head.flag){
return true;
}else {
head.flag = true;
head = head.next;
}
}
return false;
}
定位环的起点
真题描述:给定一个链表,返回链表开始入环的第一个结点。 如果链表无环,则返回 null。
解析: 其实就是返回我们第一个打点的地方;快慢指针解决的思路:两个指针在同一点一起出发,slow一次走一步、fast 一次走两步。这样如果它们是在一个有环的链表里移动,一定有相遇的时刻
function detectCycle(head){
let index = 0;
while (head) {
if(head.flag){
return head.index;
}else {
head.flag = true;
head.index = index;
head = head.next;
}
}
return null;
}