一、LeeCode 203 移除链表元素
题目链接:203. 移除链表元素
文档讲解:来自代码随想录
视频讲解:卡子哥视频讲解系列——移除链表元素
思路:我采用虚拟头结点法(图片来自代码随想录)
(如果没有虚拟头结点,那么在该链表中,删除非头结点和删除头结点的操作会不同)
设置一个虚拟头结点,那么包括原头结点在内,每个节点都有一个前节点和一个后节点,移除元素的操作可以按照同一的方式进行。我在代码中给出了尽可能详细的注释。
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
// 创建虚拟头结点
ListNode* dummyHead = new ListNode(0);
// 使虚拟头结点指向原头结点
dummyHead->next = head;
// 创建cur指针从虚拟头结点依次向后指,用于对链表进行操作
ListNode* cur = dummyHead;
// 如果要操作一个节点,则必须让cur指向该节点的前节点,这样才能用cur->next来操作该节点
// 要操作的节点肯定不能为空,因此循环条件为 cur->next != NULL
while (cur->next != NULL) {
// 发现目标值时,进行删除操作
if (cur->next->val == val) {
// 用tmp暂时存放要移除的此节点,用于一会释放内存的操作
ListNode* tmp = cur->next;
// 此节点为cur->next, 前节点为cur, 后节点为cur->next->next
// 让前节点直接指向后节点,就把此节点从链表中移除了
cur->next = cur->next->next;
// 释放移除的节点的内存
delete tmp;
} else {
// 让cur移向下一个节点,再做一遍以上操作
cur = cur->next;
}
}
// 新链表的头结点为虚拟节点的下一个节点
head = dummyHead->next;
// 释放用于辅助操作的虚拟头结点
delete dummyHead;
// 根据要求,返回新链表的头结点
return head;
}
};
二、LeeCode 707 设计链表
题目链接:707. 设计链表
文档讲解:来自代码随想录
视频讲解:卡子哥视频讲解系列——设计链表
这道题目设计链表的五个接口:
- 获取链表第index个节点的数值
- 在链表的最前面插入一个节点
- 在链表的最后面插入一个节点
- 在链表第index个节点前面插入一个节点
- 删除链表的第index个节点
思路:仍采用虚拟头节点的方法,在代码中给出我理解的尽可能详细的注释
class MyLinkedList {
public:
// 定义链表节点结构体
struct LinkedNode {
int val;
LinkedNode* next;
LinkedNode(int val):val(val), next(nullptr) {} // 在声明时会初始化节点结构体
};
// 初始化链表
MyLinkedList() {
_dummyHead = new LinkedNode(0); // 定义虚拟头结点
_size = 0; // 初始链表长度为 0
}
// 获取链表中下标为 index 的节点的值。如果下标无效,则返回 -1
int get(int index) {
if (index < 0 || index > (_size - 1)) {
return -1;
}
// 创建指针cur指向头结点
LinkedNode* cur = _dummyHead->next;
// 遍历到下标为 index 的节点
while (index--) {
cur = cur->next;
}
// 返回下标为 index 的节点值
return cur->val;
}
// 将一个值为 val 的节点插入到链表中第一个元素之前。在插入完成后,新节点会成为链表的第一个节点
void addAtHead(int val) {
LinkedNode* newNode = new LinkedNode(val); // 创建要插入的节点
LinkedNode* cur = _dummyHead;
// 此时cur指向虚拟头结点,cur->next表示头结点
// 让新插入的节点指向头结点,那么插入的节点就变成了新的头结点
newNode->next = cur->next;
// 再让虚拟头结点指向新的头节点,完成插入
cur->next = newNode;
// 链表长度+1
_size++;
}
// 将一个值为 val 的节点追加到链表中作为链表的最后一个元素
void addAtTail(int val) {
LinkedNode* newNode = new LinkedNode(val);
// 创建指针cur指向虚拟头结点
LinkedNode* cur = _dummyHead;
// 遍历到链表尾部
while (cur->next != NULL) {
cur = cur->next;
}
// 此时cur指向链表最后一个元素,让cur->next = newNode
cur->next = newNode;
// 链表长度+1
_size++;
}
// 将一个值为 val 的节点插入到链表中下标为 index 的节点之前
// 如果 index 等于链表的长度,那么该节点会被追加到链表的末尾
// 如果 index 比长度更大,该节点将不会插入到链表中
void addAtIndex(int index, int val) {
if (index > _size || index < 0) {
return;
}
LinkedNode* newNode = new LinkedNode(val);
// cur初始指向虚拟头结点
LinkedNode* cur = _dummyHead;
// 遍历到cur指向index节点的前一个节点
// 只有这样才能通过cur->next操作index节点
while (index--) {
cur = cur->next;
}
// 此时cur指向index节点的前节点
newNode->next = cur->next; // 让新节点指向index节点
cur->next = newNode; // 让前节点指向新节点,完成插入
// 链表长度+1
_size++;
}
// 如果下标有效,则删除链表中下标为 index 的节点
void deleteAtIndex(int index) {
if (index < 0 || index >= _size) {
return;
}
LinkedNode* cur = _dummyHead;
while (index--) {
cur = cur->next;
}
// 遍历后,cur指向index节点的前节点
LinkedNode* tmp = cur->next; // tmp暂存要移除的index节点,方便一会释放
cur->next = cur->next->next; // index节点的前节点直接指向后节点
delete tmp;
tmp = NULL; // 释放tmp后防止tmp变成野指针,故赋值空
_size--; // 链表长度-1
}
private:
// 在类里使用的变量
int _size; // 链表长度
LinkedNode* _dummyHead; // 虚拟头结点
};
注意:(1)(链表节点编号从0开始的情况下)如果cur初始指向虚拟头结点,那么while (index--) {cur = cur->next;}遍历后,cur就指向index节点的前节点;如果cur初始指向头结点,那么while (index--) {cur = cur->next;}遍历后,cur就指向index节点;(2)要想操作index节点,必须让指针cur指向index节点的前节点,这样才能用cur->next来操作index节点;
三、LeeCode 206 反转链表
题目链接:206. 反转链表
文档讲解:来自代码随想录
视频讲解:卡子哥视频讲解系列——反转链表
思路:链表是按顺序一个节点的next指针指向下一个节点,想要反转链表,只需要改变链表的next指针的指向,如图所示截取自代码随想录。
1. 双指针法
定义指针 cur 初始指向头节点,定义指针 pre 初始指向 NULL
将 cur->next 指向 pre,就达到了翻转的目的
然后 cur 和 pre 依次向下移一位:在翻转前 cur->next 是指向节点1的(从0开始记),用 tmp 先保存 cur->next,用于cur向下移一位,然后再翻转。
(代码中给出我理解的尽可能详细的注释)
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode* cur = head; // cur初始指向头结点
ListNode* pre = NULL; // pre初始指向NULL
ListNode* tmp; // 暂存cur的下一个节点
while (cur != NULL) {
// 先存cur的下一个节点,否则等到cur->next改变后,就找不到cur的下一个节点了
tmp = cur->next;
// 改变cur指向前一位(头节点指向NULL变为尾节点)
cur->next = pre;
// cur和pre都向下移一位
pre = cur; // 先更新pre
cur = tmp; // 再更新cur
}
return pre; // 最终是pre指向新链表的头,cur指向NULL
}
};
2. 递归法
思路:递归法我理解的比较慢,和双指针法是一样的逻辑,当cur为空的时候循环结束,不断将cur指向pre。
class Solution {
public:
ListNode* reverse(ListNode* pre, ListNode* cur) {
if (cur == NULL) {
return pre;
}
ListNode* tmp;
tmp = cur->next;
cur->next = pre;
return reverse(cur, tmp);
}
ListNode* reverseList(ListNode* head) {
return reverse(NULL, head);
}
};