目录
移除链表元素
设计链表
翻转链表
两两交换链表中的节点
删除链表的倒数第N个节点
链表相交
环形链表
对链表中常用的,虚拟头节点,增删改查,翻转,删除倒数节点,环形链表进行了介绍
用于单链表中删除指定元素,常用的方法包括:1虚拟节点操作,2原节点操作。
方法:
我们先以2原节点操作举例:
思路在于,我们需要做两次判断,第一种为被删除元素为头节点位置,第二种为非头节点位置。
头节点位置处理的思路是:
如果该头节点为目标值,那么我们让头节点的下一个节点为节点,
head = head->nex
之和我们在返回head头节点时,其实返回的是最开始的第二个节点,那么对于第一个节点,我们还需要释放内存。所以我们定义一个中间变量 tmp 保存 head 最开始的指针,当完成节点移动后,删除 tmp也就是删除了最开始的head。
while(head != NULL && head->val == val){
//需要删除头节点
ListNode* tmp = head;
head = head->next;//头节点在第二个节点位置了
delete 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* 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){
//数组还在边界内
if(cur->next->val == val){
ListNode* tmp = cur->next;
cur->next = cur->next->next;//让cur指向删除的位置指向被删除位置的下一个
delete tmp;//释放空间被删除的节点
}
else{
cur = cur->next;
}
}
return head;
}
};
我们以1虚拟节点操作举例:
这里的思路是:先建立一个虚拟节点,该节点指向第一个节点,那么在后续判断中就不需要再判断头节点了,直接按照删除非头节点的方式。
ListNode* dummyHead = new ListNode(0);//设置虚拟节点
dummyHead->next = head;
ListNode* cur = dummyHead;
然后判断后续非头节点是否需要删除,因此代码如下:
/**
* 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;
ListNode* cur = dummyHead;
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;
}
};
相关题目:
203. 移除链表元素 - 力扣(LeetCode)https://leetcode.cn/problems/remove-linked-list-elements/
对单链表进行头插,尾插,任意插,任意删,获取任意节点的值。我们将分别介绍这几个部分
方法:
头插:
定义一个虚拟节点指向头节点,定义一个新节点。同时,新节点指向头节点,虚拟节点指向新节点
void addAtHead(int val) {
LinkNode *newNode = new LinkNode(val);
newNode->next = _dummyHead->next;//把新节点指向头节点
_dummyHead->next = newNode;//把虚拟节点指向新节点
_size++;//节点数量增加
}
尾插:
定义一个指针指向虚拟节点,定义一个新节点被最后位置指向
void addAtTail(int val) {
LinkNode* newNode = new LinkNode(val);
LinkNode* cur = _dummyHead;
//这里cur不一样可以试着代值进去
while(cur->next != nullptr){
cur = cur->next;
}
cur->next = newNode;
_size++;
}
任意插:
void addAtIndex(int index, int val) {
if(index > _size || index < 0){
return ;
}
LinkNode* newNode = new LinkNode(val);
LinkNode* cur = _dummyHead;
while(index--){
cur = cur->next;
}
newNode->next = cur->next;;
cur->next = newNode;
_size++;
}
任意删:
void deleteAtIndex(int index) {
if(index >= _size || index < 0){
return ;
}
LinkNode* cur =_dummyHead;
while(index--){
cur = cur->next;
}
LinkNode* tmp = cur->next;
cur->next = cur->next->next;
delete tmp;
_size--;
}
相关题目:
707. 设计链表 - 力扣(LeetCode)https://leetcode.cn/problems/design-linked-list/
原理:
对于单链表进行元素翻转时,我们利用双指针方法,分别指向前后节点,交换节点指向顺序实现整个链表的翻转
方法:
包括:1双指针法,2递归法实现,两者思路一样,需要注意的是,在翻转之后移动指针时,先移动后指针再移动前指针。
我们现在以双指针举例:
定义一个cur指向前节点赋值为head,定义一个后节点赋值为NULL。因为翻转后,之前的头节点需要指向NULL
ListNode* cur = head;//后节点
ListNode* pre = NULL;//前节点
当前节点指向NULL,也就是之前的尾节点时结束循环,同时利用一个中间变量保存cur的下一个节点,因为我们需要把cur的下一个节点赋值给pre,没有中间变量将导致找不到下一个节点了。当完成一次翻转后,后节点,前节点依次前移
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode* cur = head;//后节点
ListNode* pre = NULL;//前节点
while(cur){
//当cur指向空结束
ListNode* temp = cur->next;//保留cur之前的下一个节点
cur->next = pre;//指针翻转;
//指针同时前移,先移pre,再cur
pre = cur;
cur = temp;
}
return pre;
}
};
我们现在以递归举例:
由双指针思路一样,我们递归函数为reverse(),其中传入为reverse(head,NULL),也就是之前的(cur,pre),然后返回pre
return reverse(head,NULL);
当cur=NULL时结束,返回pre
if(cur == NULL){
//结束
return pre;
}
定义中间变量和翻转节点,这里和双指针一样
ListNode* temp = cur->next;
cur->next = pre;//翻转指针
现在前移节点,按照双指针,先移动pre,再移动cur,因此传入的reverse按照这个样子,总代码如下:
class Solution {
public:
ListNode* reverse(ListNode* cur,ListNode* pre){
if(cur == NULL){
//结束
return pre;
}
ListNode* temp = cur->next;
cur->next = pre;//翻转指针
//移动指针
return reverse(temp, cur);
}
ListNode* reverseList(ListNode* head) {
return reverse(head,NULL);//cur,pre的顺序传入,返回pre
}
};
相关题目:
206. 反转链表 - 力扣(LeetCode)https://leetcode.cn/problems/reverse-linked-list/
创建虚拟头节点指向头节点,定义一个指针指向虚拟头节点,以及保存 1, 3节点的指针
ListNode* dummyHead = new ListNode(0);//虚拟头节点
dummyHead->next = head;
ListNode* cur = dummyHead;
//1.保存 1,3节点
ListNode* temp1 = cur->next;
ListNode* temp2 = cur->next->next->next;
判断链表遍历结束,一定要先判断cur->next。否则出现空指针报错
while(cur->next != nullptr && cur->next->next != nullptr){
//1.交换cur 和 2
cur->next = cur->next->next;
//2.交换1,2
cur->next->next = temp1;
//3.交换1,3
temp1->next = temp2;
//cur移动两个,总代码如下:
/**
* 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* swapPairs(ListNode* head) {
ListNode* dummyHead = new ListNode(0);//虚拟头节点
dummyHead->next = head;
ListNode* cur = dummyHead;
while(cur->next != nullptr && cur->next->next != nullptr){
//1.保存 1,3节点
ListNode* temp1 = cur->next;
ListNode* temp2 = cur->next->next->next;
//1.交换cur 和 2
cur->next = cur->next->next;
//2.交换1,2
cur->next->next = temp1;
//3.交换1,3
temp1->next = temp2;
//cur移动两个
cur = cur->next->next;
}
return dummyHead->next;
}
};
相关题目:
24. 两两交换链表中的节点 - 力扣(LeetCode)https://leetcode.cn/problems/swap-nodes-in-pairs/
方法:
要删除链表中的倒数第n个节点,我们使用:双指针方法。
定义一个虚拟头节点指向head,两个快慢指针指向虚拟节点
ListNode* dummyHead = new ListNode(0);
dummyHead->next = head;
ListNode* fast = dummyHead;
ListNode* slow = dummyHead;
先让快指针移动n+1个位置,再让快慢指针一起移动,当快指针到了NULL时,此时慢指针在被删除元素的前一个位置。
while(n-- && fast != NULL){
fast = fast->next;
//先让fast移动n
}
//fast移动再+1,这样slow在被删除元素前一个
fast = fast->next;
//fast slow一起移动,直到fast到NULL
while(fast != NULL){
fast = fast->next;
slow = slow->next;
}
现在进行元素删除,让slow的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* removeNthFromEnd(ListNode* head, int n) {
ListNode* dummyHead = new ListNode(0);
dummyHead->next = head;
ListNode* fast = dummyHead;
ListNode* slow = dummyHead;
while(n-- && fast != NULL){
fast = fast->next;
//先让fast移动n
}
//fast移动再+1,这样slow在被删除元素前一个
fast = fast->next;
//fast slow一起移动,直到fast到NULL
while(fast != NULL){
fast = fast->next;
slow = slow->next;
}
ListNode* temp = slow->next;
slow->next = slow->next->next;
delete temp;
return dummyHead->next;
}
};
相关题目:
19. 删除链表的倒数第 N 个结点 - 力扣(LeetCode)https://leetcode.cn/problems/remove-nth-node-from-end-of-list/
方法:
求两个链表相交的位置,如果没有就返回空,我们利用一个巧妙办法,先让比较长的链表的头指针移动两个链表的差距长度,然后以长链表开始,遍历两个链表的节点是否一样。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
ListNode* curA = headA;
ListNode* curB = headB;
int lenA = 0, lenB = 0;
//求A,B链表长度
while(curA != NULL){
lenA++;
curA = curA->next;
}
while(curB != NULL){
lenB++;
curB = curB->next;
}
//让curA,curB回到头节点
curA = headA;
curB = headB;
//找到最长链表,设置为A
if(lenB > lenA){
swap(lenA,lenB);
swap(curA,curB);
}
//求差距长度
int gap = lenA - lenB;
while(gap--){
curA = curA->next;
//curA移动gap个长度
}
//遍历A,B是否有相同的
while(curA != NULL){
if(curA == curB){
return curA;
}
curA = curA->next;
curB = curB->next;
}
return NULL;
}
};
相关题目:
面试题 02.07. 链表相交 - 力扣(LeetCode)https://leetcode.cn/problems/intersection-of-two-linked-lists-lcci/
方法:
利用双指针实现,题目满足当快慢指针相遇后,同时移动z个距离必定相遇。
根据这个要求:我们做如下工作:
让快慢指针一起移动,并且满足在边界范围内
while(fast != NULL && fast->next != NULL){
fast = fast->next->next;
slow = slow->next;
}
当移动过程中两者相遇,则让两者继续移动,两者必定会碰到
if(fast == slow){
ListNode* index1 = head;
ListNode* index2 = fast;
while(index1 != index2){
//直到两者在移动z个距离碰到
index1 = index1->next;
index2 = index2->next;
}
完整代码如下:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
ListNode* fast = head;
ListNode* slow = head;
while(fast != NULL && fast->next != NULL){
fast = fast->next->next;
slow = slow->next;
//一直移动快慢指针,直到相遇
if(fast == slow){
ListNode* index1 = head;
ListNode* index2 = fast;
while(index1 != index2){
//直到两者在移动z个距离碰到
index1 = index1->next;
index2 = index2->next;
}
return index1;
}
}
return NULL;
}
};
相关题目:
142. 环形链表 II - 力扣(LeetCode)https://leetcode.cn/problems/linked-list-cycle-ii/