文章目录
- 203.移除链表元素
- 直接操作原链表
- 虚拟头结点
- 707.设计链表
- 定义链表结构体
- `MyLinkedList()` 初始化 `MyLinkedList` 对象。
- `int get(int index)`
- `void addAtHead(int val)`
- `void addAtTail(int val)`
- `void addAtIndex(int index, int val)`
- `void deleteAtIndex(int index)`
- `void printLinkedList()`
- 206.反转链表
- 双指针
- 迭代法
203.移除链表元素
力扣题目链接
文章链接:203.移除链表元素
视频链接:手把手带你学会操作链表 | LeetCode:203.移除链表元素
状态:循环条件的设置while(cur)和while(cur->next != nullptr)
虚拟头结点在链表内容中非常实用,这里分别用两种方法解决该问题。
循环条件的设置while(cur)和while(cur->next != nullptr)有什么不同呢?
主要是关注我们在操作链表时,cur应该出现在哪里,如果我们待删除的结点是targetNode,那么我们的cur应该在targetNode的前一个位置,所以循环条件必须是while(cur->next != nullptr)
直接操作原链表
不使用虚拟头结点的话,我们就要考虑如果要删除的结点就是头结点怎么办?这个时候我们需要分情况讨论。
class Solution {
public:ListNode* removeElements(ListNode* head, int val) {// 删除头结点while (head != NULL && head->val == val) { // 注意这里不是ifListNode* 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;}
};
有以下几个重要的点:
- 一定要先检测头结点,并且,很有可能从头开始连续好几个结点都是val,所以我们一定要用
while
循环来删除头结点 - 注意删除非头结点时
while
的条件:首先要保证cur不等于空然后cur的下一个结点也不是空,因为这代表了最后一个结点,我们是无法进行操作的。
虚拟头结点
class Solution {
public:ListNode* removeElements(ListNode* head, int val) {ListNode* dummyHead = new ListNode(0);dummyHead->next = head;ListNode* cur = dummyHead;while (cur->next != nullptr) // 更改循环条件为cur->next不为nullptr{if (cur->next->val == val) {ListNode* temp = cur->next; // 保存当前要删除的节点cur->next = cur->next->next; // 删除操作delete temp; // 释放内存} else {cur = cur->next; // 当前节点不需要删除,cur指针前进}}ListNode* resultHead = dummyHead->next;delete dummyHead; // 删除虚拟头节点return resultHead;}
};
需要注意的是循环体内的if…else逻辑,因为如果如果执行完删除操作,cur应该位置不变,再看下一个结点是否为要删除的元素。所以一定要接else cur=cur->next
if ()
{
}
else{
}
707.设计链表
力扣题目链接
文章链接:707.设计链表
视频链接:帮你把链表操作学个通透!LeetCode:707.设计链表
状态:如何定义链表结构体不知道,初始化链表没有定义_size。然后许多加减操作都忘记操作_size了。
关于函数
void deleteAtIndex(int index)
其中对于temp = nullptr
的操作非常重要
定义链表结构体
struct LinkedNode
{int val;LinkedNode* next;LinkedNode(int val):val(val), next(nullptr){}
}
MyLinkedList()
初始化 MyLinkedList
对象。
MyLinkedList()
{_dummyhead = new LinkedNode(0); // 这里定义的头结点 是一个虚拟头结点,而不是真正的链表头结_size = 0;
}
int get(int index)
获取链表中下标为 index
的节点的值。如果下标无效,则返回 -1
。
int get(int index)
{//下标无效if (index > (_size - 1) || index < 0)return -1;LinkedNode* cur = _dummyhead->next; //指向cur指向真正的头结点,因为真正头结点的下标为0while(index--)// 如果--index 就会陷入死循环{cur = cur->next;}return cur->val;
}
void addAtHead(int val)
将一个值为 val
的节点插入到链表中第一个元素之前。在插入完成后,新节点会成为链表的第一个节点。
void addAtHead(int val)
{LinkedNode* newNode = new LinkedNode(val);//并不需要tempListNode* temp = _dummyhead->next;newNode->next = _dummyhead->next;_dummyhead->next = newNode;_size++;
}
void addAtTail(int val)
将一个值为 val
的节点追加到链表中作为链表的最后一个元素。
void addAtTail(int val)
{LinkedNode* newNode = new LinkedNode(val);LinkedNode* cur = _dummyhead;while (cur->next != null){cur = cur->next;}//该句话可以省略newNode->next = nullptr;cur->next = newNode;_size++;
}
void addAtIndex(int index, int val)
将一个值为 val
的节点插入到链表中下标为 index
的节点之前。如果 index
等于链表的长度,那么该节点会被追加到链表的末尾。如果 index
比长度更大,该节点将 不会插入 到链表中。
void addAtIndex(int index, int val)
{LinkedNode* newNode = new LinkedNode(val);LinkedNode* cur = _dummyHead;//判断插入条件if (index > _size)return ;if (index < 0) index = 0;while (index--){cur = cur->next;}newNode->next = cur->next;cur->next = newNode;_size++;
}
void deleteAtIndex(int index)
如果下标有效,则删除链表中下标为 index
的节点。
void deleteAtIndex(int index)
{//下标无效的情况if (index >= _size || index < 0)return;LinkedNode* cur = _dummyHead;while(index--) {cur = cur ->next;}LinkedNode* tmp = cur->next;cur->next = cur->next->next;delete tmp;//delete命令指示释放了tmp指针原本所指的那部分内存,//被delete后的指针tmp的值(地址)并非就是NULL,而是随机值。也就是被delete后,//如果不再加上一句tmp=nullptr,tmp会成为乱指的野指针//如果之后的程序不小心使用了tmp,会指向难以预想的内存空间tmp=nullptr;_size--;
}
void printLinkedList()
打印链表
// 打印链表
void printLinkedList() {LinkedNode* cur = _dummyHead;while (cur->next != nullptr) {cout << cur->next->val << " ";cur = cur->next;}cout << endl;
}
206.反转链表
力扣题目链接
文章链接:206.反转链表
视频链接:帮你拿下反转链表 | LeetCode:206.反转链表
状态:双指针基本思路都没啥问题,主要就是pre指针的指向出了大问题,不是它指向null,而是它就是null这样才能。进阶的迭代法学习…
双指针
class Solution {
public:ListNode* reverseList(ListNode* head) {ListNode* temp; // 保存cur的下一个节点ListNode* cur = head;ListNode* pre = NULL;while(cur) {temp = cur->next; // 保存一下 cur的下一个节点,因为接下来要改变cur->nextcur->next = pre; // 翻转操作// 更新pre 和 cur指针pre = cur;cur = temp;}return pre;}
}
迭代法
迭代法应该怎么去想呢?就是说,我们双指针法的本质,也不过就是不停得尽兴交换,这个时候想直接写出迭代函数最好的方法其实就是模拟一下前几个的操作。
假设有一个不停迭代操作pre、cur的函数。这个函数应该怎么写呢?先写出它的终止条件
reverse(pre, cur)//先写明迭代法的终止条件if (cur == NULL) return pre;//这是由于此时pre已经是新的头结点了ListNode* temp = cur->next;cur->next = pre;//之前我们做的操作是pre = cur; cur = temp;来进行指针的移动//如果利用迭代函数,其实就是把这两个参数按照对应位置传进去就好了return reverse(cur, temp)
C++代码如下
class Solution {
public:ListNode* reverse(ListNode* pre,ListNode* cur){if(cur == NULL) return pre;ListNode* temp = cur->next;cur->next = pre;// 可以和双指针法的代码进行对比,如下递归的写法,其实就是做了这两步// pre = cur;// cur = temp;return reverse(cur,temp);}ListNode* reverseList(ListNode* head) {// 和双指针法初始化是一样的逻辑// ListNode* cur = head;// ListNode* pre = NULL;return reverse(NULL, head);}};