203.移除链表元素
题意:删除链表中等于给定值 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
输出:[]
指针细节理解
概念上可以理解为:pre
和cur
是ListNode
类型的指针,写法上最好让星号跟着变量名,防止连续声明定义出错
// 2.添加pre指针记录前一个结点辅助删除
struct ListNode* cur = dummyHead;
---------------------------------
// 2.添加pre指针记录前一个结点辅助删除
struct ListNode *pre = dummyHead, *cur = dummyHead->next;
指针后移为什么要放在else
里?因为在if
里进行删除的时候,cur
已经后移了
else {
cur = cur->next;
}
链表判断条件和指针移动的关系
line 7
用到了cur->next
,则while
循环条件一定要对其进行判断;
line 4
用到了cur->next->next
,为什么可以不判断,因为cur->next->next
为nullptr
时只在末尾发生一次,且符合条件
while (cur->next != nullptr) {
if (val == cur->next->val) {
ListNode* tmp = cur->next;
cur->next = cur->next->next;
delete tmp;
} else {
cur = cur->next;
}
}
直接在原链表上进行删除
注意:
1.区分头结点和非头结点元素的删除操作
2.遍历条件要同时判断当前结点和下一个结点
cur != nullptr && cur->next != nullptr
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
while (head != nullptr && head->val == val) {
ListNode *tmp = head;
head = head->next;
delete tmp;
}
ListNode *cur = head;
while (cur != nullptr && cur->next != nullptr) {
if (cur->next->val == val) {
ListNode *tmp = cur->next;
cur->next = cur->next->next;
delete tmp;
} else {
cur = cur->next;
}
}
return head;
}
};
设置虚拟头结点进行删除
/**
* 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) {
// 1.添加一个辅助虚拟头结点 dummyHead
ListNode *dummyHead = new ListNode(0, head);
// 2.添加pre指针记录前一个结点辅助删除
ListNode *cur = dummyHead;
// 3.遍历单链表,找到目标结点删除,释放内存
while (cur->next) {
if (val == cur->next->val) {
ListNode *tmp = cur->next;
cur->next = cur->next->next;
delete tmp;
} else {
cur = cur->next;
}
}
// 4.正确返回头结点
head = dummyHead->next;
delete dummyHead;
return head;
}
};
707.设计链表
题意:
在链表类中实现这些功能:
- get(index):获取链表中第 index 个节点的值。如果索引无效,则返回-1。
- addAtHead(val):在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点。
- addAtTail(val):将值为 val 的节点追加到链表的最后一个元素。
- addAtIndex(index,val):在链表中的第 index 个节点之前添加值为 val 的节点。如果 index 等于链表的长度,则该节点将附加到链表的末尾。如果 index 大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。
- deleteAtIndex(index):如果索引 index 有效,则删除链表中的第 index 个节点。
设置虚拟头结点
这道题目设计链表的五个接口:
- 获取链表第index个节点的数值
- 在链表的最前面插入一个节点
- 在链表的最后面插入一个节点
- 在链表第index个节点前面插入一个节点
- 删除链表的第index个节点
// typedef 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) {}
// } ListNode;
class MyLinkedList {
private:
int _size;
ListNode *_dummyHead;
public:
MyLinkedList() {
this->_dummyHead = new ListNode(0);
this->_size = 0;
}
int get(int index) {
if (index < 0 || index >= _size) {
return -1;
}
ListNode *cur = _dummyHead->next;
while (index--) {
cur = cur->next;
}
return cur->val;
}
void addAtHead(int val) {
ListNode *newNode = new ListNode(val);
newNode->next = _dummyHead->next;
_dummyHead->next = newNode;
_size++;
}
void addAtTail(int val) {
ListNode *cur = _dummyHead->next;
while (cur->next) {
cur = cur->next;
}
ListNode *newNode = new ListNode(val);
cur->next = newNode;
_size++;
}
void addAtIndex(int index, int val) {
if (index < 0 || index >= _size) {
return ;
}
ListNode *newNode = new ListNode(val);
ListNode *cur = _dummyHead;
while (index--) {
cur = cur->next;
}
newNode->next = cur->next;
cur->next = newNode;
_size++;
}
void deleteAtIndex(int index) {
if (index < 0 || index >= _size) {
return ;
}
ListNode *cur = _dummyHead;
while (index--) {
cur = cur->next;
}
ListNode *tmp = cur->next;
cur->next = cur->next->next;
delete tmp;
_size--;
}
};
/**
* Your MyLinkedList object will be instantiated and called as such:
* MyLinkedList* obj = new MyLinkedList();
* int param_1 = obj->get(index);
* obj->addAtHead(val);
* obj->addAtTail(val);
* obj->addAtIndex(index,val);
* obj->deleteAtIndex(index);
*/
206.反转链表
给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
示例 1:输入:head = [1,2,3,4,5] 输出:[5,4,3,2,1]
双指针法
首先定义一个cur指针,指向头结点,再定义一个pre指针,初始化为null。
然后就要开始反转了,首先要把 cur->next 节点用tmp指针保存一下,也就是保存一下这个节点。
为什么要保存一下这个节点呢,因为接下来要改变 cur->next 的指向了,将cur->next 指向pre ,此时已经反转了第一个节点了。
接下来,就是循环走如下代码逻辑了,继续移动pre和cur指针。
最后,cur 指针已经指向了null,循环结束,链表也反转完毕了。 此时我们return pre指针就可以了,pre指针就指向了新的头结点。
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode *tmp; // 保存cur的下一个结点
ListNode *pre = nullptr;
ListNode *cur = head;
while (cur) {
tmp = cur->next; // 保存cur的下个结点防止丢链
cur->next = pre; // 核心操作!
pre = cur; // 指针后移
cur = tmp;
}
return pre;
}
};
递归法
递归法相对抽象一些,但是其实和双指针法是一样的逻辑,同样是当cur为空的时候循环结束,不断将cur指向pre的过程。
双指针法中初始化 cur = head,pre = NULL,在递归法中可以从如下代码看出初始化的逻辑也是一样的。
class Solution {
public:
ListNode* reverse(ListNode *pre, ListNode *cur) {
if (!cur) return pre;
ListNode *tmp = cur->next;
cur->next = pre;
// 可以和双指针法的代码进行对比,如下递归的写法,其实就是做了这两步
// pre = cur;
// cur = temp;
return reverse(cur, tmp);
}
ListNode* reverseList(ListNode* head) {
// 和双指针法初始化是一样的逻辑
// ListNode* cur = head;
// ListNode* pre = NULL;
return reverse(nullptr, head);
}
};