题目 | 标签 |
---|---|
[[leetcode 203 移除链表元素]] | |
[[leetcode 707 设计链表]] | |
[[leetcode 206 反转链表]] | |
[[leetcode 24 两两交换链表中的节点]] | |
[[leetcode 19 删除链表的倒数第 N 个结点]] | |
[[hot100-22 相交链表 f|leetcode 160 链表相交]] | |
[[leetcode 142 环形链表II]] |
0. 定义
-
链表是一种通过指针串联在一起的线性结构,每一个节点由两部分组成,一个是数据域一个是指针域(存放指向下一个节点的指针),最后一个节点的指针域指向null(空指针的意思)。
-
链表的入口节点称为链表的头结点也就是head。
0.1 概念
0.2 实现
// 单链表
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) {}
};
- 在链表中每个节点都会有data数据域和next指针域
- data数据域可以存放该节点对应的任何数据类型值,比如
int
,char
- next指针域是指向链表中下一个节点的引用指针
1. 单链表
- 常用的操作是邻接表——n个单链表。应用于存储图和树
1.1 用数组模拟单链表的实现
- 数组模拟链表也成为静态链表,算法题使用静态链表。
优势:
- 相比指针实现链表,数组实现链表的速度更快。(new的操作很慢)
方法:
- 每一个节点有两个值,一个值是节点的val,另一个值指向下一个节点。
- 在数组模拟单链表的方法中,节点之间通过数组的下标来关联。
- 最开始头节点指向空节点,空节点的下标为-1。所以初始化时
head = -1
- 每次向头节点和空节点之间插入新的元素。
- 定义两个数组
e[N]
,ne[N]
。e[N]
中存储链表中当前节点的值,ne[N]
中存储当前节点中指向下一个结点的指针(也就是在下标)
代码实现:
// head存储链表头,e[]存储节点的值,ne[]存储节点的next指针,idx表示当前用到了哪个节点
int head, e[N], ne[N], idx;
// 初始化
void init() {
head = -1;
idx = 0;
}
// 在链表头插入一个数a
void insert(int a) {
e[idx] = a, ne[idx] = head, head = idx ++ ;
}
// 将头结点删除,需要保证头结点存在
void remove() {
head = ne[head];
}
2. 双链表
- 多用于优化问题上
2.1 用数组模拟双链表的实现
方法
- 定义下标时0的节点为头节点,1为尾节点
- 定义
l[N]
,r[N]
,分别指向节点的左边和右边
代码实现
// e[]表示节点的值,l[]表示节点的左指针,r[]表示节点的右指针,idx表示当前用到了哪个节点
int e[N], l[N], r[N], idx;
// 初始化
void init()
{
//0是左端点,1是右端点
r[0] = 1, l[1] = 0;
idx = 2;
}
// 在节点a的右边插入一个数x
void insert(int a, int x)
{
e[idx] = x;
l[idx] = a, r[idx] = r[a];
l[r[a]] = idx, r[a] = idx ++ ;
}
// 删除节点a
void remove(int a)
{
l[r[a]] = l[a];
r[l[a]] = r[a];
}
3. 循环链表
4. 链表的操作
4.1 删除节点
- 如果节点为空,则不能进行操作,否则会报错
- 虚拟头节点法
- 注意:这里删除节点没有释放节点
代码实现
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
ListNode* dummyHead = new ListNode(-1); // 设置一个虚拟头结点
dummyHead->next = head; // 将虚拟头结点指向head,这样方便后面做删除操作
// 遍历链表
ListNode* cur = dummyHead;
while (cur->next != NULL) {
if(cur->next->val == val) {
ListNode* tmp = cur->next;
cur->next = cur->next->next;
delete tmp;
} else {
cur = cur->next;
}
}
head = dummyHead->next;
delete dummyHead;
return head;
}
};
- 原链表法(特判头节点删除)
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
// 删除头结点
while (head != NULL && head->val == val) { // 注意这里不是if
ListNode* tmp = head;
head = head->next;
delete tmp;
}
// 删除非头结点
ListNode* cur = head;
while (cur != NULL && cur->next!= NULL) {
if (cur->next->val == val) {
ListNode* tmp = cur->next;
cur->next = cur->next->next;
delete tmp;
} else {
cur = cur->next;
}
}
return head;
}
};
4.2 添加节点
-
头部(Head):让新节点的
next
指针指向原来的头节点。 -
特定位置(Specific Position):遍历到链表到特定位置的前一个节点,然后修改其
next
指针指向新节点,并设置新节点的next
指针指向原本节点的后一个节点。 -
尾部(Tail):如果链表为空,新节点将成为头节点;否则,需要遍历到链表的最后一个节点,并设置其
next
指针指向空节点。
// 插入头部
void prepend(T val) {
ListNode<T> *newNode = new ListNode<T>(val);
newNode->next = head;
head = newNode;
}
// 插入特定位置
void insertAt(int position, T val) {
if (position < 0) {
// 错误处理:位置无效
return;
}
ListNode<T> *newNode = new ListNode<T>(val);
ListNode<T> *current = head; // 遍历的当前节点
ListNode<T> *prev = nullptr; // 当前节点的前一个节点
int index = 0;
// 找到插入位置的前一个节点
while (current != nullptr && index < position) {
prev = current;
current = current->next;
index++;
}
// 如果位置超出了链表长度,则在尾部插入
if (current == nullptr) {
prev->next = newNode;
} else {
// 否则,在找到的位置插入新节点
newNode->next = current;
if (prev == nullptr) {
head = newNode; // 插入位置是头部
} else {
prev->next = newNode; // 插入位置在中间或尾部
}
}
}
// 插入尾部
void append(T val) {
ListNode<T>* newNode = new ListNode<T>(val); // 创建新节点
if (head == nullptr) {
// 如果链表为空,新节点既是头节点也是尾节点
head = newNode;
} else {
// 如果链表不为空,找到尾节点并插入新节点
ListNode<T>* current = head;
while (current->next != nullptr) {
current = current->next; // 遍历到最后一个节点
}
current->next = newNode; // 将尾节点的next指向新节点
}
}
// 注意这里的链表的定义已经包含了next指向空
template <typename T>
struct ListNode {
T val; // 存储的数据
ListNode *next; // 指向下一个节点的指针
ListNode(T x) : val(x), next(nullptr) {} // 构造函数
};
4.3 遍历链表
// 遍历链表并打印每个节点的值
void traverse() const {
ListNode<T>* current = head; // 从头节点开始遍历
while (current != nullptr) { // 当当前节点不为空时继续遍历
std::cout << current->value << " "; // 打印当前节点的值
current = current->next; // 移动到下一个节点
}
std::cout << std::endl; // 打印换行符以结束输出
}
4.4 反转链表
- 见上方表格《反转链表》