一、链表基础知识
1、链表的存储方式:
链表是通过指针域的指针链接在内存中各个节点。
所以链表中的节点在内存中不是连续分布的 ,而是散乱分布在内存中的某地址上,分配机制取决于操作系统的内存管理。
2、链表增删改查时间复杂度:
(1)增删:O(1)
(2)查:O(n)
链表的长度可以是不固定的,并且可以动态增删, 适合数据量不固定,频繁增删,较少查询的场景。
3、链表定义
(1)Java
java
public class ListNode {
// 结点值
int val;
// 下一个结点
ListNode next;
// 节点的构造函数(无参)
public ListNode() {
}
// 节点的构造函数(有一个参数)
public ListNode(int val) {
this.val = val;
}
// 节点的构造函数(有两个参数)
public ListNode(int val, ListNode next) {
this.val = val;
this.next = next;
}
}
(2)JavaScript
javascript
class ListNode {
val;
next = null;
constructor(value) {
this.val = value;
this.next = null;
}
}
(3)C++
cpp
struct ListNode {
int val; // 节点上存储的元素
ListNode *next; // 指向下一个节点的指针
ListNode(int x) : val(x), next(NULL) {} // 节点的构造函数
};
//1、通过自己定义构造函数初始化节点:
ListNode* head = new ListNode(5);
//2、也可以不自己定义构造函数,C++默认生成一个构造函数。
//但是这个构造函数不会初始化任何成员变量
//使用默认构造函数初始化节点:
ListNode* head = new ListNode();
head->val = 5;
//总结:如果不定义构造函数使用默认构造函数,在初始化的时候不能直接给变量赋值
二、移除链表元素
问题:
题意:删除链表中等于给定值 val 的所有节点。
示例 1: 输入:head = [1,2,6,3,4,5,6], val = 6 输出:[1,2,3,4,5]
示例 2: 输入:head = [], val = 1 输出:[]
示例 3: 输入:head = [7,7,7,7], val = 7 输出:[]
(1)C++(使用原来的链表来进行移除节点操作)
cpp
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
//删除头结点
while(head!=NULL && head->val==val){
ListNode* tmp = head;
head=head->next;
delete tmp;
}
//删除非头结点
ListNode* a=head;
while(a!=NULL && a->next!=NULL){
if(a->next->val==val){
ListNode* tmp =a->next;
a->next=a->next->next;
delete tmp;
}else{
a=a->next;
}
}
return head;
}
};
//时间复杂度: O(n)空间复杂度: O(1)
(2)C++(设置一个虚拟头结点在进行移除节点操作)
cpp
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
ListNode* dummy = new ListNode(0);
dummy->next=head;
ListNode* a = dummy;
while(a->next!=NULL){
if(a->next->val==val){
ListNode* tmp=a->next;
a->next=a->next->next;
delete tmp;
}else{
a=a->next;
}
}
//return 头结点的时候,dummyNode->next;才是新的头结点
head=dummy->next;
delete dummy;
return head;
}
};
//时间复杂度: O(n)空间复杂度: O(1)
(3)Java(使用原来的链表来进行移除节点操作)
java
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode removeElements(ListNode head, int val) {
while(head!=null&&head.val==val){
head=head.next;
}
ListNode a=head;
while(a!=null && a.next!=null){
if(a.next.val==val){
a.next=a.next.next;
}else{
a=a.next;
}
}
return head;
}
}
(4)Java(设置一个虚拟头结点在进行移除节点操作)
java
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode removeElements(ListNode head, int val) {
ListNode dummy=new ListNode();
dummy.next=head;
ListNode a=dummy;
while(a.next!=null){
if(a.next.val==val){
a.next=a.next.next;
}else{
a=a.next;
}
}
return dummy.next;
}
}
(5)JavaScript(设置一个虚拟头结点在进行移除节点操作)
javascript
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} head
* @param {number} val
* @return {ListNode}
*/
var removeElements = function(head, val) {
const res = new ListNode(0,head);
let cur=res;
while(cur.next){
if(cur.next.val === val) {
cur.next = cur.next.next;
}else{
cur = cur.next;
}
}
return res.next;
};
三、设计链表
问题:
在链表类中实现这些功能:
- get(index):获取链表中第 index 个节点的值。如果索引无效,则返回-1。
- addAtHead(val):在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点。
- addAtTail(val):将值为 val 的节点追加到链表的最后一个元素。
- addAtIndex(index,val):在链表中的第 index 个节点之前添加值为 val 的节点。如果 index 等于链表的长度,则该节点将附加到链表的末尾。如果 index 大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。
- deleteAtIndex(index):如果索引 index 有效,则删除链表中的第 index 个节点。

(1)Java(单链表)
java
class MyLinkedList {
class ListNode{
int val;
ListNode next;
ListNode(int val){
this.val=val;
}
}
//存储链表元素个数
private int size;
//虚拟头结点
private ListNode head;
//初始化链表
public MyLinkedList() {
this.size = 0;
this.head = new ListNode(0);
}
public int get(int index) {
if(index<0 || index>=size){
return -1;
}
ListNode a=head;
for(int i=0;i<=index;i++){
a=a.next;
}
return a.val;
}
public void addAtHead(int val) {
ListNode newNode=new ListNode(val);
newNode.next=head.next;
head.next=newNode;
size++;
}
public void addAtTail(int val) {
ListNode newNode=new ListNode(val);
ListNode b=head;
while(b.next!=null){
b=b.next;
}
b.next=newNode;
size++;
}
public void addAtIndex(int index, int val) {
if(index<0||index>size){
return;
}
ListNode c=head;
for(int i=0;i<index;i++){
c=c.next;
}
ListNode newNode=new ListNode(val);
newNode.next=c.next;
c.next=newNode;
size++;
}
public void deleteAtIndex(int index) {
if(index<0||index>=size){
return;
}
ListNode c=head;
for(int i=0;i<index;i++){
c=c.next;
}
c.next=c.next.next;
size--;
}
}
(2)JavaScript(双链表)
java
class MyLinkedList {
class ListNode{
int val;
ListNode next,prev;
ListNode(int val){
this.val=val;
}
}
//存储链表元素个数
private int size;
//虚拟头结点和尾结点
private ListNode head,tail;
//初始化链表
public MyLinkedList() {
this.size = 0;
this.head = new ListNode(0);
this.tail = new ListNode(0);
this.head.next=tail;
this.tail.prev=head;
}
public int get(int index) {
if(index<0 || index>=size){
return -1;
}
ListNode cur=head;
if(index>=size/2){
cur=tail;
for(int i=0;i<size-index;i++){
cur=cur.prev;
}
}else{
cur=head;
for(int i=0;i<=index;i++){
cur=cur.next;
}
}
return cur.val;
}
public void addAtHead(int val) {
addAtIndex(0,val);
}
public void addAtTail(int val) {
addAtIndex(size,val);
}
public void addAtIndex(int index, int val) {
if(index<0||index>size){
return;
}
ListNode pre=head;
for(int i=0;i<index;i++){
pre=pre.next;
}
ListNode newNode=new ListNode(val);
newNode.next=pre.next;
pre.next.prev=newNode;
newNode.prev=pre;
pre.next=newNode;
size++;
}
public void deleteAtIndex(int index) {
if(index<0||index>=size){
return;
}
ListNode pre=head;
for(int i=0;i<index;i++){
pre=pre.next;
}
pre.next.next.prev=pre;
pre.next=pre.next.next;
size--;
}
}
四、反转数组
问题:
题意:反转一个单链表。
示例: 输入: 1->2->3->4->5->NULL 输出: 5->4->3->2->1->NULL
双指针法:
(1)Java
java
class Solution {
public ListNode reverseList(ListNode head) {
ListNode temp = null;
ListNode pre=null;
ListNode cur=head;
while(cur!=null){
temp=cur.next;
cur.next=pre;
pre=cur;
cur=temp;
}
return pre;
}
}
(2)JavaScript
javascript
/**
* @param {ListNode} head
* @return {ListNode}
*/
var reverseList = function(head) {
let cur=head,pre=null,temp=null;
while(cur){
temp=cur.next;
cur.next=pre;
pre=cur;
cur=temp;
}
return pre;
};
(3)C++
cpp
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode* temp;
ListNode* pre=NULL;
ListNode* cur=head;
while(cur!=NULL){
temp=cur->next;
cur->next=pre;
pre=cur;
cur=temp;
}
return pre;
}
};
五、两两交换链表中的节点
问题:
给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。

(1)Java
java
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode swapPairs(ListNode head) {
ListNode dummyHead=new ListNode(-1);
dummyHead.next=head;
ListNode cur=dummyHead;
ListNode temp;
ListNode firstNode;
ListNode secondNode;
while(cur.next!=null&&cur.next.next!=null){
temp = cur.next.next.next;
firstNode=cur.next;
secondNode=cur.next.next;
cur.next=secondNode;
secondNode.next=firstNode;
firstNode.next=temp;
cur=firstNode;
}
return dummyHead.next;
}
}
(2)Javascript
javascript
/**
* 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}
*/
var swapPairs = function(head) {
let cur= new ListNode(0,head),temp=cur;
while(temp.next&&temp.next.next){
let pre=temp.next,right=temp.next.next;
pre.next=right.next;
right.next=pre;
temp.next=right;
temp=pre;
}
return cur.next;
};
(3)C++(时间复杂度:O(n);空间复杂度:O(1))
cpp
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
ListNode* dummyHead=new ListNode(0);
dummyHead->next=head;
ListNode* cur=dummyHead;
while(cur->next!=nullptr&&cur->next->next!=nullptr){
ListNode* temp=cur->next;
ListNode* temp1=cur->next->next->next;
cur->next=cur->next->next;
cur->next->next=temp;
cur->next->next->next=temp1;
cur=cur->next->next;
}
ListNode* result=dummyHead->next;
delete dummyHead;
return result;
}
};
六、链表相交
问题:
给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null 。
图示两个链表在节点 c1 开始相交:

题目数据 保证 整个链式结构中不存在环。
注意,函数返回结果后,链表必须 保持其原始结构 。
示例 1:

示例 2:

示例 3:

注意:交点不是数值相等,而是指针相等
(1)Java
java
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
//让指针走完自己的链表后,去走另一条链表,最后输出p1或p2都可以。
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode p1=headA,p2=headB;
while(p1!=p2){
if(p1==null) p1=headB;
else p1=p1.next;
if(p2==null) p2=headA;
else p2=p2.next;
}
return p2;
}
}
(2)JavaScript
javascript
/**
* Definition for singly-linked list.
* function ListNode(val) {
* this.val = val;
* this.next = null;
* }
*/
/**
* @param {ListNode} headA
* @param {ListNode} headB
* @return {ListNode}
*/
var getLength = function(head){
let len=0,cur=head;
while(cur!=null){
len++;
cur=cur.next;
}
return len;
}
var getIntersectionNode = function(headA, headB) {
let curA=headA,curB=headB;
let lenA=getLength(curA);
let lenB=getLength(curB);
// 让 curA 永远指向更长的链表
if(lenA<lenB){
[curA,curB]=[curB,curA];
[lenA,lenB]=[lenB,lenA];
}
// curA 先走 长度差 步
let gap = lenA - lenB;
while(gap-- > 0){
curA = curA.next;
}
// 然后一起走,直到相遇
while(curA&&curA!=curB){
curA=curA.next;
curB=curB.next;
}
return curA;
};
(3)C++(时间复杂度:O(n + m);空间复杂度:O(1))
cpp
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
ListNode* curA=headA;
ListNode* curB=headB;
int lenA=0,lenB=0;
while(curA!=NULL){
lenA++;
curA=curA->next;
}
while(curB!=NULL){
lenB++;
curB=curB->next;
}
curA=headA;
curB=headB;
if(lenA<lenB){
swap(lenA,lenB);
swap(curA,curB);
}
int gap=lenA-lenB;
while(gap--){
curA=curA->next;
}
while(curA!=NULL){
if(curA==curB){
return curA;
}
curA=curA->next;
curB=curB->next;
}
return NULL;
}
};
七、删除链表的倒数第N个节点
问题:
给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
进阶:你能尝试使用一趟扫描实现吗?
示例 1:

输入:head = [1,2,3,4,5], n = 2 输出:[1,2,3,5]
示例 2:
输入:head = [1], n = 1 输出:[]
示例 3:
输入:head = [1,2], n = 1 输出:[1]
(1)Java
java
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummyHead=new ListNode(0);
dummyHead.next=head;
ListNode left=dummyHead;
ListNode right=dummyHead;
for(int i=0;i<=n;i++){
right=right.next;
}
while(right!=null){
left=left.next;
right=right.next;
}
left.next=left.next.next;
return dummyHead.next;
}
}
(2)JavaScript
javascript
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} head
* @param {number} n
* @return {ListNode}
*/
var removeNthFromEnd = function(head, n) {
let dummyHead=new ListNode(0,head);
dummyHead.next=head;
let left=dummyHead;
let right=dummyHead;
while(n--){
right=right.next;
}
while(right.next!=null){
left=left.next;
right=right.next;
}
left.next=left.next.next;
return dummyHead.next;
};
(3)C++
cpp
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* dummyHead=new ListNode(0);
dummyHead->next=head;
ListNode* left=dummyHead;
ListNode* right=dummyHead;
while(n--&&right!=NULL){
right=right->next;
}
while(right->next!=NULL){
left=left->next;
right=right->next;
}
left->next=left->next->next;
return dummyHead->next;
}
};
八、环形链表II
问题:
给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
进阶:你能尝试使用一趟扫描实现吗?
示例 1:
(1)Java
java
public class Solution {
public ListNode detectCycle(ListNode head) {
ListNode slow = head;
ListNode fast = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
if (slow == fast) {// 有环
ListNode index1 = fast;
ListNode index2 = head;
// 两个指针,从头结点和相遇结点,各走一步,直到相遇,相遇点即为环入口
while (index1 != index2) {
index1 = index1.next;
index2 = index2.next;
}
return index1;
}
}
return null;
}
}
(2)JavaScript
javascript
var detectCycle = function(head) {
if(!head || !head.next) return null;
let slow =head.next, fast = head.next.next;
while(fast && fast.next && fast!== slow) {
slow = slow.next;
fast = fast.next.next;
}
if(!fast || !fast.next ) return null;
slow = head;
while (fast !== slow) {
slow = slow.next;
fast = fast.next;
}
return slow;
};
(3)C++
cpp
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
ListNode* fast=head;
ListNode* slow=head;
while(fast!=NULL&&fast->next!=NULL){
slow=slow->next;
fast=fast->next->next;
if(slow==fast){
ListNode* a=fast;
ListNode* b=head;
while(a!=b){
a=a->next;
b=b->next;
}
return b;
}
}
return NULL;
}
};