目录
203.移除链表元素
前言:
对链表的增删查等操作一般有两种思路,一种是直接对原链表进行处理,另一种是在原链表的头结点前增加一个“虚拟头结点”,使得很多时候在处理头结点和其余结点时能做到统一,本题分别利用原链表和虚拟头结点的方式进行处理,可以比较虚拟头结点的应用带来的好处。
方式一:对原链表进行处理:
直接对原链表进行处理,代码如下:
/**
* 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) {
while (head != NULL && head->val == val){ //对链表头结点单独进行讨论
ListNode* tmp = head;
head = head->next;
delete tmp;
}
ListNode* cur = head;
while (cur != NULL && cur->next != NULL){ //要同时考虑当前cur指针和下一位置是否为空
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) {
ListNode* vhead = new ListNode(0);
vhead->next = head;
ListNode* cur = vhead;
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 = vhead->next;
delete vhead; //释放虚拟头结点内存
return head;
}
};
使用虚拟头结点后,头结点有了前趋,对于头结点的处理可以和其他节点统一,在对结点的处理流程上可以实现简化。
注意点:
直接对原链表进行处理时,循环条件中cur指针不仅要和设置虚拟头结点中一样满足 cur->next != NULL,还要满足 cur != NULL 的条件,因为原料表的起始节点是头结点,头结点非空是必要的条件。
707.设计链表
前言:
本题对链表的基础操作提出了较高要求,让我们设计链表的五个接口:
- 获取链表第index个节点的数值
- 在链表的最前面插入一个节点
- 在链表的最后面插入一个节点
- 在链表第index个节点前面插入一个节点
- 删除链表的第index个节点
下面来看具体的算法实现:
算法实现:
class MyLinkedList {
public:
//定义链表结构体
struct LinkNode{
int val;
LinkNode* next;
LinkNode (int val): val(val), next(nullptr) {} //构造函数
};
MyLinkedList() {
vhead = new LinkNode(0);
size = 0;
}
int get(int index) {
if (index < 0 || index > (size - 1)) return -1;
LinkNode* cur = vhead->next;
while (index--) cur = cur->next;
return cur->val; //返回 cur 对应的数据域
}
void addAtHead(int val) {
LinkNode* node = new LinkNode(val);
node->next = vhead->next;
vhead->next = node;
size++;
}
void addAtTail(int val) {
LinkNode* node = new LinkNode(val);
LinkNode* cur = vhead;
while (cur->next != nullptr) cur = cur->next;
cur->next = node;
size++;
}
void addAtIndex(int index, int val) {
if (index > size) return;
if (index < 0) index = 0;
LinkNode* node = new LinkNode(val);
LinkNode* cur = vhead;
while (index--) cur = cur->next;
node->next = cur->next;
cur->next = node;
size++;
}
void deleteAtIndex(int index) {
if (index < 0 || index > (size - 1)) return;
LinkNode* cur = vhead;
while (index--) cur = cur->next;
LinkNode* tmp = cur->next;
cur->next = tmp->next; // cur->next = cur->next->next;
delete tmp;
tmp = nullptr;
size--;
}
private:
int size;
LinkNode* vhead;
};
/**
* 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);
*/
先自己定义链表结构体,并采用虚拟头结点的方式对链表的增删操作进行处理,本题思想上不难,但是有许多需要注意的细节处理需要把握,比如 index 有效性的判断,遍历指针cur 初始位置的设定,究竟是指向虚拟头结点还是指向头结点,以及循环终止条件的确定等都需要仔细分析。
206.反转链表
前言:
本题分别使用双指针和递归的方法进行实现。
方法一:双指针法:
具体代码如下:
/**
* 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* pre = NULL;
ListNode* cur = head;
ListNode* tmp;
while (cur){
tmp = cur->next;
cur->next = pre;
pre = cur; //更新 pre 指针
cur = tmp; //更新 cur 指针
}
return head = pre;
}
};
本题难点主要有两点:
- 双指针初值的设定;
- 临时指针tmp的设置用以接收下一个要移动的位置,防护指针指向更改后失去联系。、
方法二:递归实现
具体思想与双指针法类似,具体代码如下:
/**
* 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* pre, ListNode* cur){
if (cur == NULL) return pre; // 递归终止条件
ListNode *tmp = cur->next;
cur->next = pre;
return reverse(cur, tmp);
}
ListNode* reverseList(ListNode* head) {
return reverse(NULL, head);
}
};
依然是通过不断更替pre、cur的位置实现链表的翻转,并且也设置了临时指针用以临时记录位置。相比双指针法显得较为抽象。
总结:
正式进入链表章节,今天的练习让我再次加深了链表的操作原理和实现流程以及具体的一些注意细节,受益匪浅!明天继续加油!