● 24. 两两交换链表中的节点 ● 19.删除链表的倒数第N个节点 ● 面试题 02.07. 链表相交 ● 142.环形链表II ● 总结
两两交换链表中的节点
C++
首先明确操作的循环区间,cur指向要交换的两个节点的前一个节点才能保证循环不变性,可以理解成链表的左闭右开区间,虚拟头结点就是为了满足循环不变性。终止条件考虑奇偶cur->next,cur->next->next都不为空。如果按图里面1,2,3的顺序可以省一个临时变量。另外循环条件有两层next,那循环内部取三次next就是合法的,只不过取出来的有可能是null。
/**
* 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* virtualHead = new ListNode(-1);
ListNode* cur = virtualHead;
while(cur->next != nullptr && cur->next->next != nullptr) {
ListNode* tmp = cur->next;
cur->next = cur->next->next;
tmp->next = cur->next->next;
cur->next->next = tmp;
cur = cur->next->next;
}
return virtualHead->next;
}
};
C#
按照1->3->2顺序,但得多用一个临时变量。
/**
* Definition for singly-linked list.
* public class ListNode {
* public int val;
* public ListNode next;
* public ListNode(int val=0, ListNode next=null) {
* this.val = val;
* this.next = next;
* }
* }
*/
public class Solution {
public ListNode SwapPairs(ListNode head) {
ListNode virtualHead = new ListNode(-1);
virtualHead.next = head;
ListNode cur = virtualHead;
while(cur.next != null && cur.next.next != null) {
ListNode tmp = cur.next;
ListNode tmp1 = cur.next.next.next;
cur.next = cur.next.next;
cur.next.next = tmp;
tmp.next = tmp1;
cur = cur.next.next;
}
return virtualHead.next;
}
}
删除链表的倒数第 N 个结点
C+
双指针,快的线先动,慢的再一起动,注意从虚拟头结点开始,fast要先走n+1步这样slow指向的是倒数第N + 1个节点,这样才能删除第N个,一起动的时候判断条件为fast->next!=null就可以提前停下了。
public class Solution {
public ListNode RemoveNthFromEnd(ListNode head, int n) {
ListNode virtualHead = new ListNode(-1);
virtualHead.next = head;
ListNode slow = virtualHead;
ListNode fast = virtualHead;
while(n-- > 0) {
fast = fast.next;
}
while(fast.next != null) {
fast =fast.next;
slow = slow.next;
}
slow.next = slow.next.next;
return virtualHead.next;
}
}
C#
链表相交
C++
题第一眼看挺怪的,注意相交指的是指针相同(引用完全相同,即:内存地址完全相同的交点),不是指针的值相同,并且后面的节点都一样。先获取长度,后对齐遍历即可。
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
ListNode* curA = headA;
ListNode* curB = headB;
int lenA = 0;
int lenB = 0;
while(curA != nullptr) {
lenA++;
curA = curA->next;
}
while(curB != nullptr) {
lenB++;
curB= curB->next;
}
curA = headA;
curB = headB;
int cnt = 0;
if(lenA >= lenB) {
cnt = lenA - lenB;
while(cnt--) {
curA = curA->next;
}
} else {
cnt = lenB - lenA;
while(cnt--) {
curB = curB->next;
}
}
while(curA != nullptr) {
if(curA == curB) {
return curA;
}
curA = curA->next;
curB = curB->next;
}
return nullptr;
}
};
C#
环形链表
slow(每次移动一步)和fast(每次移动两步)快慢指针判断是否有环,根据数学公式推理出相遇点和链表头同时移动,再次相遇时就是链表入口。
- 只有有环,快慢指针才会相遇。
- 快指针相当于慢指针每次移动一个节点,一定会相遇,不会跳过。
- 快慢指针相遇时,满指针一定还在第一圈,应为慢指针走过一圈,快指针就走了两圈套娃恰好相遇,此临界状态慢指针也在第一圈,更不要说如果快指针提前走了,追的会更快,肯定会提前相遇。
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
ListNode* slow = head;
ListNode* fast = head;
while(fast != nullptr && fast->next != nullptr) {
fast = fast->next->next;
slow = slow->next;
if(fast == slow) {
ListNode* L1 = head;
ListNode* L2 = fast;
while(L1 != L2) {
L1 = L1->next;
L2 = L2->next;
}
return L1;
}
}
return nullptr;
}
};
C#
public class Solution {
public ListNode DetectCycle(ListNode head) {
ListNode slow = head;
ListNode fast = head;
while(fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
if(fast == slow) {
ListNode L1 = fast;
ListNode L2 = head;
while(1 > 0) {
if(L1 == L2) {
return L1;
}
L1 = L1.next;
L2 = L2.next;
}
}
}
return null;
}
}
二刷
两两交换链表中的绩点
使用虚拟头结点,注意while循环条件。
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* virtualHead = new ListNode(-1);
virtualHead->next = head;
ListNode* cur = virtualHead;
while (cur->next != nullptr && cur->next->next != nullptr) {
ListNode* temp = cur->next;
cur->next = temp->next;
temp->next = cur->next->next;
cur->next->next = temp;
cur = temp;
}
return virtualHead->next;
}
};
删除倒数第N个节点
#include<iostream>
using namespace std;
struct ListNode {
int val;
ListNode* next;
ListNode(int val): val(val), next(nullptr){ }
};
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* vH = new ListNode(-1);
vH->next = head;
ListNode* fast = vH;
ListNode* slow = vH;
for (int i = 0; i <= n; i++) {
fast = fast->next;
}
while (fast != nullptr) {
fast = fast->next;
slow = slow->next;
}
ListNode* temp = slow->next;
slow->next = slow->next->next;
delete temp;
return vH->next;
}
};
ListNode* ListNodeIn() {
ListNode* head = new ListNode(-1);
int n = 0;
cin >> n;
ListNode* cur = head;
for (int i = 1; i <= n; i++) {
int temp = 0;
cin >> temp;
ListNode* tt = new ListNode(temp);
cur->next = tt;
cur = cur->next;
}
return head->next;
}
void ListNodeOut(ListNode* head) {
while (head != nullptr) {
cout << head->val << " ";
head = head->next;
}
}
int main() {
ListNode* head = ListNodeIn();
Solution s1;
ListNode* ans = s1.removeNthFromEnd(head, 2);
ListNodeOut(ans);
return 0;
}
链表相交
注意下相交是指指针相等,而不是指针指向的对象值相等。
/**
* 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) {
int lenA = 0;
ListNode* curA = headA;
while (curA != nullptr) {
lenA++;
curA = curA->next;
}
int lenB = 0;
ListNode* curB = headB;
while(curB != nullptr) {
lenB++;
curB = curB->next;
}
curA = headA;
curB = headB;
//对齐起始位置
if (lenA <= lenB) {
int cnt = lenB - lenA;
//B长,移动B指针
for (int i = 0; i < cnt; i++) {
curB = curB->next;
}
}
else {
int cnt = lenA - lenB;
//A长,移动A指针
for (int i = 0; i < cnt; i++) {
curA = curA->next;
}
}
while (curA != nullptr) {
if (curA== curB) {
return curA;
}
curA = curA->next;
curB = curB->next;
}
return nullptr;
}
};
环形链表
哈希表耍赖。
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
unordered_set<ListNode*> data;
ListNode* cur = head;
while (cur != nullptr) {
if (data.find(cur) == data.end()) {
data.insert(cur);
}
else {
return cur;
}
cur = cur->next;
}
return nullptr;
}
};
快慢指针
struct ListNode {
int val;
ListNode* next;
ListNode(int val): val(val), next(nullptr){ }
};
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
ListNode* slow = head;
ListNode* fast = head;
while (fast != nullptr && fast->next != nullptr) {
slow = slow->next;
fast = fast->next->next;
if (fast == slow) {
ListNode* cur = head;
while (slow != cur) {
cur = cur->next;
slow = slow->next;
}
return cur;
}
}
return nullptr;
}
};
链表总结
-
很重要的一点,遍历条件终止是cur还是cur->next看要不要对最后一个节点进行操作,如果不需要操作最后一个节点,仅仅是让节点索引停在最后一个点,那用cur->next,因为索引虽然到了最后一个节点,但已经不符合条件跳出循环,并不会对节点进行操作。
-
循环中如果有用到cur->next->next->next,则要保证cur->next->next不为空,如果cur每次循环跳两步,则先要保证cur->next不为空,快慢指针经常用到。
-
cur可以从虚拟头结点出发,这样遍历可以控制停在待修改点的前驱节点,方便进行操作,比如删除节点,cur就要停在前一个。
虚拟头可以避免对头结点修改和链表为空情况的单独讨论,只有当对头结点进行操作时需要用,遍历则不需要虚拟头结点。