移除链表元素
题目描述
题意:删除链表中等于给定值 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 输出:[]
解题思路
根据链表的特性,如果需要删除链表中的某个元素的话,可以将当前节点的上一个节点指向当前节点的下一个节点,然后释放当前节点即可。可以采用两种方式进行链表元素移出的目的:
-
直接使用原来的链表进行删除操作
- 使用一个循环来删除链表头部可能出现的一个或多个目标值节点,然后使用另一个循环来遍历链表的其余部分,删除遇到的每一个目标值节点。
-
设置一个虚拟头结点进行删除操作
- 设定一个虚拟头结点指向头结点,然后利用链表的特性依次遍历链表,删除指定节点,好处就是无需进行头节点的判断,删除操作均采用同一个逻辑。
注意:C++中需要手动释放节点,否则会影响内存空间占用率
代码实现
测试地址:https://leetcode.cn/problems/remove-linked-list-elements/description/
直接使用原来的链表:
class Solution {
public:
// 函数,用于移除链表中所有值为val的节点
ListNode* removeElements(ListNode* head, int val) {
// 循环删除头部是目标值val的节点
while (head != nullptr && head->val == val) {
ListNode* tmp = head; // 临时保存当前要删除的节点
head = head->next; // 移动头指针到下一个节点
delete tmp; // 释放被删除的节点的内存
}
// cur用来遍历链表,从新的头节点开始
ListNode* cur = head;
// 遍历链表直到cur为nullptr(链表尾部)
while (cur != nullptr && cur->next != nullptr) {
// 如果当前cur的下一个节点的值等于val,那么这个节点需要被删除
if (cur->next->val == val) {
ListNode* tmp = cur->next; // 临时保存当前要删除的节点
cur->next = cur->next->next; // 跳过当前要删除的节点,将cur的next指向当前要删除的节点的下一个节点
delete tmp; // 释放被删除的节点的内存
} else {
// 如果当前cur的下一个节点的值不是val,则移动cur到下一个节点
cur = cur->next;
}
}
// 返回处理后的链表头节点
return head;
}
};
设置虚拟头结点:
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
// 创建一个哑节点,也称为伪头节点
ListNode* dummyHead = new ListNode();
// 将哑节点的下一个节点指向原始链表的头节点
dummyHead->next = head;
// 使用cur指针遍历链表
ListNode* cur = dummyHead;
while (cur->next != nullptr) {
// 如果当前cur指向的节点的下一个节点的值等于目标值val
if (cur->next->val == val) {
// 临时保存需要删除的节点
ListNode* tmp = cur->next;
// 跳过需要删除的节点,即从链表中移除
cur->next = cur->next->next;
// 释放(删除)需要删除的节点
delete tmp;
} else {
// 否则,移动cur指针到下一个节点
cur = cur->next;
}
}
// 更新头节点指向,因为原始头节点可能被删除
head = dummyHead->next;
// 删除哑节点
delete dummyHead;
// 返回可能已更新的头节点
return head;
}
};
设计链表
题目描述
题意:
在链表类中实现这些功能:
- get(index):获取链表中第 index 个节点的值。如果索引无效,则返回-1。
- addAtHead(val):在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点。
- addAtTail(val):将值为 val 的节点追加到链表的最后一个元素。
- addAtIndex(index,val):在链表中的第 index 个节点之前添加值为 val 的节点。如果 index 等于链表的长度,则该节点将附加到链表的末尾。如果 index 大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。
- deleteAtIndex(index):如果索引 index 有效,则删除链表中的第 index 个节点。
解题思路
总体思路
- 使用结构体
LinkNode
定义链表节点,每个节点保存一个整数值和一个指向下一个节点的指针。 - 使用类
MyLinkedList
封装链表操作,其中包括一个私有变量_dummyHead
作为哑节以及一个_size
存储链表节点数。 - 在进行插入和删除操作时,首先检查索引的有效性,然后遍历到目标位置,执行相应的节点插入或删除操作。
- 在添加、删除节点或者获取节点值的函数中,都有对索引和链表边界的检查,确保操作的有效性。
时空复杂度分析:
- 时间复杂度: 涉及
index
的相关操作为 O(index), 其余为 O(1) - 空间复杂度: O(n)
代码实现
测试地址:https://leetcode.cn/problems/design-linked-list/
class MyLinkedList {
public:
// 定义链表节点结构
struct LinkNode {
int val; // 节点的值
LinkNode* next; // 指向下一个节点的指针
// 构造函数,初始化节点的值为val,下一个节点为nullptr
LinkNode(int val) : val(val), next(nullptr) {}
};
// 构造函数,初始化链表
MyLinkedList() {
_dummyHead = new LinkNode(0); // 创建哑节点
_size = 0; // 初始化链表大小为0
}
// 获取指定索引的节点的值
int get(int index) {
if (index < 0 || index > (_size - 1)) { // 检查索引是否有效
return -1; // 返回-1表示索引无效
}
LinkNode* cur = _dummyHead->next; // 从第一个实际节点开始
while (index--) { // 移动到指定索引的节点
cur = cur->next;
}
return cur->val; // 返回节点的值
}
// 在链表头部添加新节点
void addAtHead(int val) {
LinkNode* newNode = new LinkNode(val); // 创建新节点
newNode->next = _dummyHead->next; // 新节点的下一个节点指向当前头节点
_dummyHead->next = newNode; // 更新哑节点的next指向新节点
_size++; // 更新链表大小
}
// 在链表尾部添加新节点
void addAtTail(int val) {
LinkNode* newNode = new LinkNode(val); // 创建新节点
LinkNode* cur = _dummyHead; // 从哑节点开始
while (cur->next != nullptr) { // 移动到链表的最后一个节点
cur = cur->next;
}
cur->next = newNode; // 最后一个节点的next指向新节点
_size++; // 更新链表大小
}
// 在指定索引处添加新节点
void addAtIndex(int index, int val) {
if (index > _size) // 如果索引大于链表大小,则不添加
return;
if (index < 0) // 如果索引小于0,则添加到头部
index = 0;
LinkNode* newNode = new LinkNode(val); // 创建新节点
LinkNode* cur = _dummyHead; // 从哑节点开始
while (index--) { // 移动到要插入位置的前一个节点
cur = cur->next;
}
newNode->next = cur->next; // 新节点的next指向当前节点的next
cur->next = newNode; // 当前节点的next指向新节点
_size++; // 更新链表大小
}
// 删除指定索引的节点
void deleteAtIndex(int index) {
if (index >= _size || index < 0) { // 检查索引是否有效
return; // 如果无效则不执行删除操作
}
LinkNode* cur = _dummyHead; // 从哑节点开始
while (index--) { // 移动到要删除节点的前一个节点
cur = cur->next;
}
LinkNode* tmp = cur->next; // 临时保存要删除的节点
cur->next = cur->next->next; // 更新前一个节点的next指向
delete tmp; // 删除节点
tmp = nullptr; // 避免野指针
_size--; // 更新链表大小
}
// 打印链表内容
void printLinkedList() {
LinkNode* cur = _dummyHead; // 从哑节点开始
while (cur->next != nullptr) { // 遍历链表直到最后一个节点
cout << cur->next->val << " "; // 打印节点的值
cur = cur->next; // 移动到下一个节点
}
cout << endl; // 打印换行符
}
private:
int _size; // 链表大小
LinkNode* _dummyHead; // 哑节点,用于简化链表操作
};
翻转链表
题目描述
题意:反转一个单链表。
示例: 输入: 1->2->3->4->5->NULL 输出: 5->4->3->2->1->NULL
解题思路
双指针思路
总体思路:
- 定义一个cur指针,指向头结点,然后再定义一个pre指针,初始化为null
- 因为头结点经过翻转后会变成尾节点,指向空节点,也就是pre指针对应的节点位置,因此需要通过tmp节点保存原先节点指向的下一个节点
- 接下来就是翻转操作了,将cur->next指向pre节点,分别移动pre和cur节点到下一个节点维持
- 循环上述操作,最后cur指针会指向空节点,此时循环结束,此时pre指向的位置就是头结点的位置,返回pre节点即可。
时空复杂度分析:
- 时间复杂度: O(n)
- 空间复杂度: O(1)
递归思路
总体思路:
- 根据双指针法确定递归终止条件:当cur执行的元素为nullptr时,返回pre节点指向的元素
- 初始化双指针,将cur指向头结点,pre指向nullptr节点
- 递归赋值流程:使用tmp保存cur指向的下一个元素,将cur指向的下一个元素修改为pre,开始递归调用,由于此时pre是由cur决定的,cur由tmp决定的,因此递归参数传入的是cur和tmp对应的节点。
时空复杂度:
- 时间复杂度: O(n), 要递归处理链表的每个节点
- 空间复杂度: O(n), 递归调用了 n 层栈空间
代码实现
测试地址:https://leetcode.cn/problems/reverse-linked-list/description/
双指针代码实现:
/**
* 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* reverseList(ListNode* head) {
ListNode* tmp;
ListNode* cur = head;
ListNode* pre = nullptr;
while (cur) {
tmp = cur->next; // 通过临时指针保存cur的下一个节点
cur->next = pre; // 翻转操作
// 更新pre 和 cur 指针
pre = cur;
cur = tmp;
}
return pre;
}
};
递归代码实现:
/**
* 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* reverseList(ListNode* head) {
// 和双指针法初始化是一样的逻辑
// ListNode* cur = head;
// ListNode* pre = NULL;
return reverse(nullptr, head);
}
ListNode* reverse(ListNode* pre, ListNode* cur) {
if (cur == nullptr)
return pre;
ListNode* tmp = cur->next;
cur->next = pre;
// 对应双指针法的代码
// pre = cur
// cur = tmp
return reverse(cur, tmp);
}
};