一、链表基础
链表的基础操作包括链表的创建和增删查改。其中链表的索引index
一般从0
开始。这里有以下几点需要注意:
- 链表是一个类,节点结构体是类中的一个私有结构体
- 对于一个链表,其私有数据需要包括节点结构体,虚拟头结点,节点数量
- 对于节点结构体,需要在定义结构体的时候需要定义构造函数,且不要忘记结构体定义最后的
;
号 - 对于链表,需要在public中定义链表的初始化构造函数
class MyLinkedList {
private:
/---链表依赖的基础结构:节点,节点数量,虚拟头结点---/
struct LinkedNode{
int val;
LinkedNode* next;
LinkedNode() = default;
LinkedNode(int x) : val(x), next(nullptr) {}
}; /定义结构体的;不要忘记
int _size;
LinkedNode* _dummyhead;
public:
/---链表初始化函数---/
MyLinkedList() {
_size = 0;
_dummyhead = new LinkedNode(0);
}
/---给定节点index,如果index合法,返回节点数值;如果index非法,返回-1---/
int get(int index) {
if(index >= _size || index < 0) return -1;
else{
LinkedNode* cur = _dummyhead;
while(index--){
cur = cur->next;
}
return cur->next->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++;
}
/---给定节点index,在给定index节点前添加一个节点---/
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++;
}
/---给定节点index,删除index对应的节点---/
void deleteAtIndex(int index) {
if(index < 0 || index >= _size) return;
LinkedNode* cur = _dummyhead;
while(index--){
cur = cur->next;
}
LinkedNode* delnode = cur->next;
cur->next = delnode->next;
delete delnode;
_size--;
}
};
二、反转链表
反转链表写对的核心在于记住:当前节点next
指向前一个节点的时候,前一个节点的next
已经完成了修改,其四要素在于:
- 定义null节点完成头结点的反向
- 记录当前节点的下一节点
- 反转当前节点的next指针
- 移动当前节点和前一个节点
1、循环法
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode* cur = head;
ListNode* pre = nullptr; /初始化使用null完成头节点反转
while(cur){
ListNode* tmp = cur->next; /记录当前节点下一节点
cur->next = pre; /反转当前节点next指针
pre = cur; /移动当前处理节点及其前一个节点
cur = tmp;
}
return pre;
}
};
2、递归法
相较于循环法,递归法使用递归调用隐藏了当前节点next指针反转后当前处理节点的移动
class Solution {
private:
ListNode* reverse(ListNode* pre, ListNode* cur){
if(cur == nullptr) return pre;
ListNode* tmp = cur->next;
cur->next = pre;
return reverse(cur, tmp); /使用递归调用实现当前处理节点移动
}
public:
ListNode* reverseList(ListNode* head) {
return reverse(nullptr, head);
}
};
三、环形链表判定
使用快慢指针法, 分别定义 fast 和 slow指针,从头结点出发,fast指针每次移动两个节点,slow指针每次移动一个节点。
- 如果
fast
和slow
指针在途中相遇 ,说明这个链表有环。 - 如果
fast
走到了nullptr
,则链表没有环 - 注意
while
条件为fast != nullptr && fast->next != nullptr
class Solution {
public:
bool hasCycle(ListNode *head) {
ListNode* slow = head;
ListNode* fast = head;
while(fast != nullptr && fast->next != nullptr){
fast = fast->next->next;
slow = slow->next;
if(fast == slow) return true;
}
return false;
}
};
四、环形链表入口判断
按照环形链表判定后,当fast和slow指针项与后:从头结点出发一个指针,从相遇节点 也出发一个指针,这两个指针每次只走一个节点, 那么当这两个指针相遇的时候就是环形入口的节点。
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
/---判定链表是否为环形链表---/
ListNode* fast = head;
ListNode* slow = head;
while(fast != nullptr && fast->next != nullptr){
fast = fast->next->next;
slow = slow->next;
/---如果链表是环形链表,则查找链表入口---/
if(fast == slow){
ListNode* index = head;
while(index != fast){
index = index->next;
fast = fast->next;
}
return index;
}
}
return nullptr;
}
};
五、链表虚拟头结点dummyhead的作用
- 当链表的头结点可能发生变化时候(原有头结点被删除或更改),使用
dummyhead->next
返回正确的头结点 - 当链表进行循环处理时候,添加
dummyhead
可以使得头结点和其他节点保持一致,即均有一个节点指向自己