一、链表
链表和数组相似,它们都是有序的列表、都是线性结构(有且仅有一个前驱、有且仅有一个后继)。不同点在于,链表中,数据单位的名称叫做“结点”,而结点和结点的分布,在内存中可以是离散的。
在链表中,每一个结点的结构都包括了两部分的内容:数据域和指针域。JS 中的链表,是以嵌套的对象的形式来实现的:
{
// 数据域
val: 1,
// 指针域,指向下一个结点
next: {
val:2,
next: ...
}
}
大概是这个样子:
二、链表结点的创建
创建链表结点,咱们需要一个构造函数,大概是这种形式:
function ListNode(val) {
this.val = val;
this.next = null;
}
const node = new ListNode(1)
node.next = new ListNode(2)
三、链表的考点
记住在处理链表问题时:处理链表的本质,是处理链表结点之间的指针关系,也就是next指针。
链表的考点一般分为以下三类:
- 链表的处理:合并、删除等(删除是重点中的重点!)
- 链表的反转及其衍生题目
- 链表成环问题及其衍生题目
1、合并或删除问题
合成问题:
真题描述:将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有结点组成的。 (leetcode 21)
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} l1
* @param {ListNode} l2
* @return {ListNode}
*/
var mergeTwoLists = function (l1, l2) {
let node = new ListNode();
let cur = node;
while (l1 && l2) {
if(l1.val <= l2.val){
cur.next = l1;
l1 = l1.next
} else {
cur.next = l2;
l2 = l2.next;
}
cur = cur.next
}
cur.next = l1 !== null ? l1:l2;
// 返回node.text 很关键
return node.next
};
删除问题:
真题描述:给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。(leetcode 83)
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} head
* @return {ListNode}
*/
// 输入:head = [1,1,2]
// 输出:[1,2]
var deleteDuplicates = function (head) {
let cur = head
while (cur && cur.next) {
if (cur.val === cur.next.val) {
cur.next = cur.next.next
} else {
cur = cur.next
}
}
return cur.next
};
真题描述:给定一个排序链表,删除所有含有重复数字的结点,只保留原始链表中 没有重复出现的数字。(leetcode 82)
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} head
* @return {ListNode}
*/
const deleteDuplicates = function(head) {
// 极端情况:0个或1个结点,则不会重复,直接返回
if(!head || !head.next) {
return head
}
let dummy = new ListNode()
dummy.next = head
let cur = dummy
while(cur.next && cur.next.next) {
if(cur.next.val === cur.next.next.val) {
let val = cur.next.val
while(cur.next && cur.next.val===val) {
cur.next = cur.next.next
}
} else {
cur = cur.next
}
}
// 返回链表的起始结点
return dummy.next;
};
2、链表反转问题
一般情况下这类问题的关键时通过双指针法来解决(答案稍微有点复杂,我就不写自己答案了)
提示:第一个问题是一个经典的使用快慢指针来解决的链表问题,可以通过这个问题学习双指针。接下来再来带着双指针的思路来解决反转问题
真题描述:给定一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。(leetcode 22)
真题描述:定义一个函数,输入一个链表的头结点,反转该链表并输出反转后链表的头结点。(完全反转 leetcode 203)
真题描述:反转从位置 m 到 n 的链表。请使用一趟扫描完成反转。(局部反转 leetcode 92)
3、链表成环问题
真题描述:给定一个链表,判断链表中是否有环。(判断是否成环 leetcode 141)
提示:解决这类问题的核心是设 标识(flag),两道题的思路都是一样的
/**
* Definition for singly-linked list.
* function ListNode(val) {
* this.val = val;
* this.next = null;
* }
*/
/**
* @param {ListNode} head
* @return {boolean}
*/
var hasCycle = function (head) {
while (head) {
if (head.flag) {
return true
} else {
head.flag = true;
head = head.next
}
}
return false
};
真题描述:给定一个链表,返回链表开始入环的第一个结点。 如果链表无环,则返回 null。(定位环启动 leetcode 142)
四、解决思路总结 !!!
- 链表问题(一般都是使用 while循环遍历)
- 如果题目输入是整个链表时,对于这种问题。它的链路循环,一般是通过创建一个新的指向头部的节点(dummy节点),以它为头开始循环。dummy节点可以帮我们处理掉头结点为空的边界问题,帮助我们简化解题过程
- 对于链表反转问题 -- 通过双指针或者三指针的方式
- 对于链表成环问题 -- 通过设立标识(flag)的方式来解决
带着这个思路解决链表问题,一般的题目应该都能解决