(3/60)移除链表元素、设计链表、反转链表
移除链表元素
leetcode:203. 移除链表元素
原链表操作法
思路
在原链表上操作,分为头节点操作和非头结点操作。
- 循环检测head是不是val,是的话删除结点并后移head指针。
- 循环检测head之后的结点是不是val(用
cur指针),是的话删除结点;否则指针后移遍历链表。 - 重复1、2直至
head==NULL或head->val != val(头节点为空或头节点不等于val)、cur==NULL或cur->next==NULL(循环指针结点为空或者循环指针的下一结点为空)。
复杂度分析
时间复杂度:O(N)。两个并列while,而且其实总体只会有链表长度次操作。
空间复杂度:O(1)。常数级额外空间(1个ListNode)。
注意点
- 最后返回head。原链表操作,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* tmp = NULL;
// 处理头结点
while(head != NULL && head->val == val){
tmp = head;
head = head->next;
delete tmp;
}
// 处理非头结点
ListNode* cur = head; // 遍历指针
while(cur != NULL && cur->next != NULL ){
// 是目标则删除,否则指针后探
if(cur->next->val == val){
tmp = cur->next;
cur->next = cur->next->next;
delete tmp;
}else{
cur = cur->next;
}
}
return head; // 原链表操作,head就是头节点
}
};
虚拟头结点法
思路
在原链表基础上,新创建一个虚拟头结点,统一了结点操作。(不需要顾虑头指针问题,原链表操作要解决的最大问题删除头指针结点时如何不失去头指针,有了虚拟头结点后就不怕丢失头节点指针了,dummyHead->next永远是头节点)
复杂度分析
时间复杂度:O(N)。两个并列while。
空间复杂度:O(1)。常数级额外空间(1个ListNode)。
注意点
- cur要从dummyHead开始。因为实际的操作是从cur->next开始的,如果从head开始则会掠过head。(与原链表操作对应,左侧cur是开区间。在原链表操作中,操作分为head和非head,head处理过后,cur指向了head,实际处理时其实是从
cur->next开始的)
代码实现
/**
* 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(); // 虚拟头节点,方便统一节点操作
ListNode* cur = NULL; // 循环指针,用于遍历链表
ListNode* tmp = NULL; // 临时指针,用于释放结点内存
dummyHead->next = head;
cur = dummyHead;
while(cur != NULL && cur->next != NULL){
// 若等于val则删除该结点;否则向后遍历
if(cur->next->val == val){
tmp = cur->next;
cur->next = cur->next->next;
delete tmp;
}else{
cur = cur->next;
}
}
return dummyHead->next; // 虚拟头结点的next才是头节点
}
};
设计链表
leetcode:707. 设计链表
虚拟头节点法
思路
略
复杂度分析
略
注意点
-
初始化列表语法
LinkedNode(int val) : val(val),next(nullptr){/* 构造函数函数体 */}; -
用下标和_size控制边界。
-
查、改结点的方法,需要直接指向目标结点的指针。
get()方法cur指向下标为index的结点,初始化为dummyHead->next。 -
增、删结点的方法,需要得到目标结点前一个位置的指针。
addAtHead、addAtTail、addAtIndex、deleteAtIndex都是cur指向处理元素的前一个(头结点的前一个、尾结点的前一个、下标index结点的前一个)。(其实全都可以是到目标节点的上一个,然后需要查、改时往后伸就可以了)
代码实现
class MyLinkedList {
public:
struct LinkedNode{
int val;
LinkedNode* next;
LinkedNode(int val):val(val),next(nullptr){}; // 初始化列表语法,简便的写法
};
// 用的是index控制
MyLinkedList() {
_size = 0;
_dummyHead = new LinkedNode(0);
}
int get(int index) {
// cur指向下标为index的结点
int result = -1;
LinkedNode* cur = _dummyHead->next;
// index: 0 ~ size-1
if(index < _size){
for(int i = 0;i < index;i++){
cur = cur->next;
}
result = cur->val;
}
return result;
}
void addAtHead(int val) {
LinkedNode* tmp = _dummyHead->next;
LinkedNode* newNode = new LinkedNode(val); // * 这个不是vector,需要new对象
_dummyHead->next = newNode;
newNode->next = tmp;
_size++;
}
void addAtTail(int val) {
LinkedNode* cur = _dummyHead; // 直接指向dummyHead
for(int i = 0;i < _size;i++){
cur = cur->next;
}
LinkedNode* newNode = new LinkedNode(val);
cur->next = newNode;
_size++;
}
void addAtIndex(int index, int val) {
if(index <= _size){
LinkedNode* cur = _dummyHead; // 直接指向dummyHead
for(int i = 0;i < index;i++){
cur = cur->next;
}
LinkedNode* tmp = cur->next;
LinkedNode* newNode = new LinkedNode(val);
cur->next = newNode;
newNode->next = tmp;
_size++;
}
}
void deleteAtIndex(int index) {
// 删除结点,cur指向目标结点前一个
if(index < _size){ // 下标有效
LinkedNode* cur = _dummyHead;
for(int i = 0;i < index;i++ ){
cur = cur->next;
}
LinkedNode* tmp = cur->next;
cur->next = cur->next->next;
delete tmp;
_size--;
}
}
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);
*/
反转链表
leetcode:206. 反转链表
双指针法
思路
核心就是,从前往后反转指针的指向。cur->next = pre;
- 设置当前指针
cur、前指针pre,将cur下一结点改为指向pre。 cur、pre借助tmp向后移动,重复1,直至cur==NULL。
复杂度分析
时间复杂度:O(N)。
空间复杂度:O(1)。
注意点
- 需要引入"第三只杯子"tmp。
- 结束条件是
cur==NULL。 - 最后返回
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;
ListNode* tmp = NULL;
while(cur){
tmp = cur->next;
cur->next = pre;
pre = cur;
cur = tmp;
}
return pre;
}
};
递归法
思路
和双指针法类似,关注传入参数、函数体核心做的操作、返回值。
- 传入参数是cur、pre,初始值是head、NULL。
- 递归核心操作
复杂度分析
时间复杂度:O(N)。遍历链表一遍。
空间复杂度:O(N)。调用系统栈,最坏情况下系统栈深度为N。
注意点
- 递归首先写退出条件。
- 传入参数(形参)有cur、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) {
return reverse(head,NULL);
}
ListNode* reverse(ListNode* cur,ListNode* pre){
if(cur == NULL){ return pre; }
ListNode* tmp = cur->next;
cur->next = pre;
return reverse(tmp,cur);
}
};

浙公网安备 33010602011771号