203.移除链表元素
题目链接:203.移除链表元素
给你一个链表,移除链表中节点等于某个 target 的所有的节点,然后返回这个链表的头节点。
在实现这道题目的代码逻辑时,就要做一个判断:我们要删除的这个节点是不是头节点?
如果是头节点就按照这个方式删除,不是头节点就按照这种方式删除。
这样就有一个小问题:删除节点的方式不统一。
因此有了虚拟头节点法:就是在链表中再加入一个头节点 dummy head。
这样删除链表中的节点的方法就可以得到统一:即让虚拟头节点直接指向该节点的下一个节点,然后释放掉该节点的内存。
这样代码会更加简洁。
本题可以采用如下2种链表操作的方式:
- 直接使用原来的链表来进行删除操作。
- 设置一个虚拟头结点再进行删除操作。
直接使用原来的链表来进行移除节点操作:
head != NULL && head->val == target
首先要判断头结点一定要不为空;
因为接下来要取头结点的值,如果这个头结点是空的话,我们相当于是操作空指针,(编译出错)。
同时,当头结点指向的数值等于要删除的值,
我们就可以进行删除操作。
思考:上面这个条件该使用
if还是while? (⚠️很多人经常在这犯错!)A:使用
while循环。示例:[1, 1, 1, 1, 1, 1] target = 1
先判断头结点不为空,头结点的数值是1,然后进行移除头结点的操作。
移到第二个发现还是1,那么写
if是不符合题目要求的。移除头结点是一个持续移除的过程。
剩下的就是删除非头结点的情况了:
⚠️注意:cur要从head开始,而不是从head的next开始。
设置一个虚拟头结点再进行移除节点操作:
头结点的指针是不能改的,我们要遍历链表的时候,需要定义一个临时的指针cur,用来遍历这个链表。
AC代码: (核心代码模式)
/**
* 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* dummyHead = new ListNode(0); //设置一个虚拟头结点
dummyHead->next = head; //让虚拟头结点的下一个节点指向链表的头节点head
ListNode* cur = dummyHead; //cur指针用来遍历链表
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;
}
};
⚠️本题最关键是要理解 虚拟头结点的使用技巧,这个对链表题目很重要。
707.设计链表
题目链接:707.设计链表
建议:这是一道考察 链表综合操作的题目,不算容易,可以练一练 使用虚拟头结点。
1️⃣获取第n个节点的数值
n的合法判断:n < 0 || n > size - 1 均不合法。
Q:如何获取第n个节点? (这是遍历链表最基础的一个操作)
A:定义一个(临时的)指针cur,让它指向虚拟头结点。
说一嘴:为什么在遍历链表的时候,要定义一个指针来遍历,而不是直接操作头指针?
A:因为我们操作完链表之后,需要返回头结点。如果你上来就操作头结点,那么头结点的值都改了,我们就不能够返回链表的头结点。
因此,需要定义一个临时的指针cur
cur = dummyHead->next;
我们要操作的就是获取当前这个节点的数值,让cur直接指向这里。
while (n--) {
cur = cur->next;
}
return cur->val;
2️⃣头部插入节点
⚠️坑点:
dummyHead->next = newNode;
newNode = ❌
顺序不对!!!
应该是:
newNode->next = dummyHead->next;
dummyHead->next = newNode;
size++;
3️⃣尾部插入节点
4️⃣第n个节点前插入节点
这个思路很重要: 只有保证第n个节点是
cur->next,我们才能用cur来操作在cur->next之前添加一个节点。
while (n) {
cur = cur->next;
n--;
}
newNode = cur->next;
cur->next = newNode;
size++;
5️⃣删除第n个节点
n < 0 || n >= size
首先对n进行合法性的判断, 判断完成之后进行删节点的操作。
🦄要清楚这个思路:第n个节点一定是cur->next,第n - 1个节点才是cur。
我们通过操作cur来删掉cur->next这个节点。
cur = dummyHead;
while (n) {
cur = cur->next;
n--;
}
tmp = cur->next;
cur-> next = cur->next->next;
size--;
这个写法能不能保证第n个节点是cur->next?
举一个极端的例子:如果n = 0,即这个链表只有一个节点,
关键点:我们要明白假如操作的是第n个点,第n个点一定是cur->next,这也才能用cur来操作这个点,是增加还是删除。
本题总结:
在插入节点的时候,一定要注意先更新哪条边,后更新哪条边,只有清楚这一点指针才不会指乱。
⚠️本题在1️⃣获取第n个结点的数值,以及5️⃣删除第n结点,均要先对n进行合法性判断。
AC代码: (核心代码模式)
class MyLinkedList {
public:
//定义链表节点结构体
struct LinkedNode {
int val; //节点上存储的元素
LinkedNode* next; //指向下一个节点的指针
LinkedNode(int val) : val(val), next(nullptr) {} //节点的构造函数
};
//初始化链表
MyLinkedList() {
_dummyHead = new LinkedNode(0); //虚拟头结点
_size = 0;
}
//获取第n个节点的数值
int get(int index) {
if (index < 0 || index > (_size - 1)) {
return -1;
}
LinkedNode* cur = _dummyHead->next;
while (index--) { //--index会死循环
cur = cur->next;
}
return cur->val;
}
//头部插入节点
void addAtHead(int val) {
LinkedNode* newNode = new LinkedNode(val);
newNode->next = _dummyHead->next;
_dummyHead->next = newNode;
_size++;
}
//尾部插入节点
void addAtTail(int val) {
LinkedNode* newNode = new LinkedNode(val);
LinkedNode* cur = _dummyHead;
while (cur->next != nullptr) {
cur = cur->next;
}
cur->next = newNode;
_size++;
}
//第n个节点前插入节点
void addAtIndex(int index, int val) {
if (index < 0 || index > _size) {
return;
}
LinkedNode* newNode = new LinkedNode(val);
LinkedNode* 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;
}
LinkedNode* cur = _dummyHead;
while (index--) {
cur = cur->next;
}
LinkedNode* tmp = cur->next;
cur->next = cur->next->next;
delete tmp;
_size--;
}
//打印链表
void PrintLinkedList() {
LinkedNode* cur = _dummyHead;
while (cur->next != nullptr) {
cout << cur->next->val << " ";
cur = cur->next;
}
cout << endl;
}
private:
int _size;
LinkedNode* _dummyHead;
};
/**
* 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.翻转链表
题目链接:206.反转链表
考查对基础数据结构操作的一道很好的题目。
我们可以先看看双指针的解法是如何实现这个翻转的过程,然后对着双指针解法的代码,写出一个递归的版本。
优先掌握双指针写法
双指针解法:
动画:
算法的大概思路:
首先,要有一个指针 cur 指向头结点,还需要一个指针 pre (定义在 cur 的前面,方便 cur 指向后一位改成指向前一位),然后 prv 和 cur 移动到下一个点。
因此,需要对上述2个指针进行初始化:cur = head; pre = NULL; (很多同学对这个初始化掌握得并不是很好)
初始化就是为了让head能够直接指向它的前一位,指向的就应该是个 null。
接下来就是遍历的过程:while ( )
遍历到最后,当pre指向了尾结点,cur指向了null,这个遍历操作(while循环)就结束了,循环条件:while (cur)
⚠️需要一个临时指针tmp,趁还没赋值(即cur指向的结点和下一个结点还存在连接),把cur的下一个结点保存下来: tmp = cur->next;
然后就可以赋值了:cur->next = pre; ,这样就改变了2个结点之间指针的方向。
改变完方向后需要分别将 pre、cur 指针向后移一位:pre = cur;,cur = tmp; (先移动pre,后移动cur)
注意:⚠️顺序不能反! 一旦顺序互换,cur指向tmp(cur指向的值被改了),导致pre不能移动到先前cur的位置。
最后,返回新链表的头结点:return pre;
梳理一下:while循环控制遍历的终止位置,一次循环改变一次结点的方向,然后将pre、cur指向下一个位置。如此循环操作,直到cur指向null,循环结束。
AC代码: (核心代码模式)
/**
* 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* temp;
ListNode* cur = head;
ListNode* pre = nullptr;
while (cur) {
temp = cur->next; //保存cur的下一个结点
cur->next = pre; //翻转操作
//更新pre和cur指针,分别向后移一位 (顺序不能反)
pre = cur;
cur = temp;
}
return pre;
}
};
递归解法:
Carl板书:
AC代码: (核心代码模式)
/**
* 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* reverse(ListNode* cur, ListNode* pre) {
if (cur == nullptr) {
return pre;
}
ListNode* temp = cur->next;
cur->next = pre;
return reverse(temp, cur);
}
ListNode* reverseList(ListNode* head) {
return reverse(head, nullptr);
}
};