1.移除链表元素
1.1在原链表上操作
先处理头结点,头结点处理的时候不需要考虑前面的节点,所以直接删掉就可以了,然后处理其他节点,这个时候要考虑前面的节点,所以当cur->next->val==val的时候,连接cur和cur->next->next就可以了;最后返回head
ListNode* removeElements(ListNode* head, int val) {
// 处理头节点需要删除的情况
while (head != nullptr && head->val == val) {
ListNode* temp = head;
head = head->next;
delete temp;
}
// 现在头节点的值不再等于val,开始处理剩下的节点
ListNode* cur = head;
while (cur != nullptr && cur->next != nullptr) {
if (cur->next->val == val) {
// 删除cur->next节点
ListNode* temp = cur->next;
cur->next =temp->next;
delete temp;
} else {
// 移动cur指针
cur = cur->next;
}
}
return head;
}
如果在head前面添加一个虚拟头结点,就不需要对头结点进行额外的处理,头结点的处理和其他节点的处理是相同的
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
// 创建一个虚拟头节点
ListNode* dummy = new ListNode(0);
dummy->next = head;
ListNode* cur = head;
ListNode* pre = dummy;
while (cur != nullptr) {
if (cur->val == val) {
pre->next = cur->next;
delete cur;
cur = pre->next;
} else {
pre = cur;
cur = cur->next;
}
}
ListNode* result = dummy->next;
delete dummy;
return result;
}
};
1.2也可以进行递归,先判断头节点是否为val,如果是就是递归处理head->next,接下来对剩余的节点进行处理
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
// 基础情况:空链表
if (head == nullptr) {
return nullptr;
}
// 递归处理
if (head->val == val) {
ListNode* newHead = removeElements(head->next, val);
delete head;
return newHead;
} else {
head->next = removeElements(head->next, val);
return head;
}
}
};
2.实现带头节点的链表的增删改查
在定义时候就包括链表的size,方便未来的处理
class MyLinkedList {
public:
struct LinkedNode {
int val;
LinkedNode* next;
LinkedNode(int x) : val(x), next(nullptr) {}
};
MyLinkedList() {
_dummyHead = new LinkedNode(0);
_size = 0;
}
int get(int index) {
if (index < 0 || index >= _size) {
return -1;
}
LinkedNode* cur = _dummyHead->next;
for (int i = 0; i < index; i++) {
cur = cur->next;
}
return cur->val;
}
void addAtHead(int val) {
addAtIndex(0, val);
}
void addAtTail(int val) {
addAtIndex(_size, val);
}
void addAtIndex(int index, int val) {
if (index > _size) {
return;
}
if (index < 0) {
index = 0;
}
LinkedNode* prev = _dummyHead;
for (int i = 0; i < index; i++) {
prev = prev->next;
}
LinkedNode* newNode = new LinkedNode(val);
newNode->next = prev->next;
prev->next = newNode;
_size++;
}
void deleteAtIndex(int index) {
if (index < 0 || index >= _size) {
return;
}
LinkedNode* prev = _dummyHead;
for (int i = 0; i < index; i++) {
prev = prev->next;
}
LinkedNode* toDelete = prev->next;
prev->next = toDelete->next;
delete toDelete;
_size--;
}
private:
LinkedNode* _dummyHead;
int _size;
};
3.反转链表
太经典了,不需过多赘述,三个指针解决
ListNode* reverseList(ListNode* head) {
if(head==nullptr){
return head;
}
ListNode*cur=head;
ListNode*pre=nullptr;
ListNode*last=head->next;
while(last!=nullptr){
cur->next=pre;
pre=cur;
cur=last;
last=last->next;
}
head=cur;
cur->next=pre;
return head;
}
4.两两交换链表中的节点
设置一个头结点,有利于以交换操作,不需要考虑头结点的特殊情况
0->1->2->3->4->
此时cur=0,我设置t1=1,t2=2;我先让cur->next指向2,然后让t1->next指向t2的next,最后让t2->next指向t1,注意,如果先更新t2的指针,就会导致断开
0->2-><-1 3->4->
ListNode* swapPairs(ListNode* head) {
ListNode* dummyHead = new ListNode(0);
dummyHead->next = head;
ListNode* cur = dummyHead;
while(cur->next!=nullptr&&cur->next->next!=nullptr){
ListNode*t1=cur->next;
ListNode*t2=t1->next;
cur->next=t2;
t1->next=t2->next;
t2->next=t1;
cur=cur->next->next;
}
return dummyHead->next;
}
5.删除倒数第n个节点
经典,快慢指针,快指针先走n个,然后一起走,最后指向的就是倒数第n个,画个图就明白了
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* dummyhead=new ListNode(0);
dummyhead->next=head;
ListNode*fast=dummyhead;
ListNode*slow=dummyhead;
while(n--&&fast){
fast=fast->next;
}
while(fast->next){
fast=fast->next;
slow=slow->next;
}
ListNode*temp=slow->next;
slow->next=temp->next;
delete temp;
return dummyhead->next;
}
5.链表相交
5.1朴素的方法,先知道两个链表的长度,然后让长链表与短的尾部对齐,然后一个一个比较就可以了。
注意!!!!!是比较指针是不是一个地址,而不是比较节点里面的值
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
ListNode*cura=headA;
ListNode*curb=headB;
int lengtha=0,lengthb=0;
while(cura!=nullptr){
cura=cura->next;
lengtha++;
}
while(curb!=nullptr){
curb=curb->next;
lengthb++;
}
cura=headA;
curb=headB;
if(lengtha<lengthb){
swap(lengtha,lengthb);
swap(cura,curb);
}
int delta=lengtha-lengthb;
while(delta--){
cura=cura->next;
}
while(cura!=nullptr){
if(cura==curb){
return cura;
}else{
cura=cura->next;
curb=curb->next;
}
}
return nullptr;
}
5.2更简洁但是不好理解的方法
画个图举个例子:
链表A: A1 -> A2 -> A3 -> C1 -> C2 -> C3
链表B: B1 -> B2 -> C1 -> C2 -> C3
pA从A1开始,pB从B1开始
初始位置
- pA 指向 A1
pB
指向 B1
第 1 步:
pA
从 A1 走到 A2pB
从 B1 走到 B2
第 2 步:
pA
从 A2 走到 A3pB
从 B2 走到 C1
第 3 步:
pA
从 A3 走到 C1pB
从 C1 走到 C2
第 4 步:
pA
从 C1 走到 C2pB
从 C2 走到 C3
第 5 步:
pA
从 C2 走到 C3pB
从 C3 走到nullptr
(B 链表到末尾了)
第 6 步:
pA
从 C3 走到nullptr
(A 链表到末尾了)pB
由于变成了nullptr
,现在根据算法逻辑,它“跳转”到headA
,所以pB
回到 A1
第 7 步:
pA
变成nullptr
后,根据算法逻辑,“跳转”到headB
,所以pA
回到 B1pB
从 A1 走到 A2
第 8 步:
pA
从 B1 走到 B2pB
从 A2 走到 A3
第 9 步:
pA
从 B2 走到 C1pB
从 A3 走到 C1
此时,pA
和 pB
都在 C1,它们相等,说明找到了交点。在这一刻 pA == pB == C1
。
如果两个链表根本就不相交,最后它们都会遍历到
nullptr
,然后pA
和pB
都会变成nullptr
,两者相等,返回nullptr
表示没有交点。
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
if (!headA || !headB) return nullptr;
ListNode* pA = headA;
ListNode* pB = headB;
// 当 pA 和 pB 相等的时候,或者都指向同一个节点,或者都是 nullptr
while (pA != pB) {
pA = (pA == nullptr) ? headB : pA->next;
pB = (pB == nullptr) ? headA : pB->next;
}
return pA; // 也可以 return pB,二者相等
}
6.环形链表
6.1最简单的思路就是用哈希表记录一下,然后看看节点有没有经历过,一旦发现经历过,直接返回,要是走到最后也没有,就说明没有环,直接返回空指针
ListNode *detectCycle(ListNode *head) {
ListNode*cur=head;
unordered_set<ListNode*> visited;
while(cur!=nullptr){
if(visited.count(cur)){
return cur;
}
visited.insert(cur);
cur=cur->next;
}
return nullptr;
}
6.2快慢指针
快指针每次走两次,慢指针每次走一次,如果有环,一定会在环中相遇,接下来就是解决如何求出入环处的问题了,假设环外是a,慢指针走了b被追上,环还剩下c的距离,这样可以得到快慢指针分别走的距离
慢指针:a+b
快指针:a+n(b+c)+b=a+(n+1)b+nc。
又因为快指针一定走的是慢指针的二倍,得到等式;
a+(n+1)b+nc=2(a+b)⟹a=c+(n−1)(b+c)
观察推导结果,发现,在head处设置一个ptr指针,如果让慢指针走c步+n圈, ptr正好走过a步到达入环处,此处借用力扣的图示进行解释
所以得到最终代码
ListNode *detectCycle(ListNode *head) {
ListNode*fast=head;
ListNode*slow=head;
while(fast!=nullptr){
slow=slow->next;
if(fast->next==nullptr){
return nullptr;
}
fast=fast->next->next;
if(fast==slow){
ListNode*ptr=head;
while(ptr!=slow){
ptr=ptr->next;
slow=slow->next;
}return ptr;
}
}
return nullptr;
}
今天的学习就结束啦!!!给大家推荐一个网站代码随想录 很适合初学者进行系统性的学习和刷题,有详细的讲解以及相关习题推荐。