题目
24.两两交换链表中的节点
虚拟头节点法
- 当对整个链表进行操作时,就需要想想是不是得用到虚拟头节点,因为要对头节点进行(交换、删除等)操作,要知道它的前一个节点。
- 本题要注意链表奇数和偶数的结束循环条件,当链表长为偶数时,cur->next为空则结束;当链表长为奇数时,cur->next->next为空则结束。且必须先判断偶数情况,再判断奇数情况,否则会出现空指针的情况。即当cur->next已经为空了,但我们取cur->next->next则变成取 空->next 就会报错。
思路如下图:
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
//虚拟头节点,下一个节点为head
ListNode *dummyhead = new ListNode(0,head);
//cur指针指向dummyhead
ListNode *cur = dummyhead;
//result指针也指向dummyhead
ListNode *result = dummyhead;
//用于存储的指针
ListNode *temp1;
ListNode *temp2;
if(head==nullptr){return head;}
//偶数:cur->next!=nullptr
//奇数:cur->next->next!=nullptr
while(cur->next!=nullptr && cur->next->next!=nullptr){
//记录前一个断开的节点
temp1 = cur->next;
cur->next = cur->next->next;//1交换
//记录后一个断开的节点
temp2 = cur->next->next;
cur->next->next = temp1;//2交换
temp1->next = temp2;//3交换
//cur+2
cur = cur->next->next;
}
result = dummyhead->next;
delete dummyhead;
return result;
}
};
19. 删除链表的倒数第 N 个结点
二刷整合自己想法和双指针法
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
//虚拟头,因为可能删除的就是头节点
ListNode* myhead = new ListNode(0,head);
ListNode* res = myhead;
ListNode* fast = myhead;
ListNode* slow = myhead;
//思路1:fast遍历链表,得到长度len,之后进行删除操作
#define _use_Think1 1
#if _use_Think1
{
int len = 0;
while(fast->next!=nullptr){
fast = fast->next;
len++;
}
int jumpcount = len - n;
while(jumpcount--){
slow = slow->next;
}
if(slow->next->next!=nullptr)
slow->next = slow->next->next;
else
slow->next = nullptr;
res = myhead->next;
delete myhead;
return res;
}
#endif
//思路2:先fast走n步,再fast和slow同时走,直到fast走到最后
#define _use_Think2 0
#if _use_Think2
{
while(n--){
fast=fast->next;
}
while(fast->next!=nullptr){
fast = fast->next;
slow = slow->next;
}
if(slow->next->next!=nullptr)
slow->next = slow->next->next;
else
slow->next = nullptr;
res = myhead->next;
delete myhead;
return res;
}
#endif
}
};
自己的想法
思路:使用虚拟头,用于对head操作。遍历整个链表(加虚拟头)长度,得到count,move=count-n-1得到要删除的节点的前一个的位置。之后进行删除,返回头节点。
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
int count = 0;
ListNode *dummyhead = new ListNode(0,head);
ListNode *cur = dummyhead;
// //如果为空链表,返回这个空链表
// if(dummyhead->next==nullptr){return head;}
//遍历整个链表
while(cur!=nullptr){
cur = cur -> next;
count++;
}
cout<<count<<endl;
//循环结束后cur指向null,count为链表长度+1,因为有虚拟头节点
// //如果要删除的节点大于链表长度,返回整个链表
// if(n>count){return head;}
int move = count - n - 1;
ListNode *cur2 = dummyhead;
ListNode *result = dummyhead;
while(move){
cur2 = cur2 -> next;
move--;
}
//循环结束,cur2指向要被删的节点的前一个
//如果不是删除最后一个节点
if(cur2->next!=nullptr && cur2->next->next!=nullptr){
cur2->next = cur2->next->next;
}
//是删除最后一个节点
else{
cur2->next = nullptr;
}
result = dummyhead->next;
delete dummyhead;
return result;
}
};
双指针方法
双指针指针经典应用:
如果要删除倒数第n个节点,让fast移动n步,然后让fast和slow同时移动,直到fast指向链表末尾。删掉slow所指向的节点(但一般是指向要删的前一个节点,所以fast一般走n+1步)就可以了。
思路:
- 定义虚拟头节点
- 快慢指针,快指针走n+1步(为什么是n+1呢,因为只有这样同时移动的时候slow才能指向删除节点的上一个节点),之后快慢指针同时走,直到快指针指向null,则此时慢指针指向要删除的节点前一个。
- 进行删除。
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
//定义双指针
ListNode* fast;
ListNode* slow;
//定义虚拟头节点
ListNode* dummy = new ListNode(0);
dummy->next = head;
fast = dummy;
slow = dummy;
for(int i=0;i<n+1;i++){
//fast指针走n+1步
fast = fast->next;
}
while(fast){
//fast再走n步到末尾NULL
fast = fast->next;
//slow走n步到要删除节点的前一个节点
slow = slow->next;
}
slow->next = slow->next->next;
return dummy->next;
}
};
面试题 02.07. 链表相交
思路:
求出A和B的两个链表长度,比较两个链表长度差值,然后把较长链表的开始指向和较短链表的开始指向对齐,之后进行移动,如果遇到指针指向的节点相同,则返回这个节点,否则返回NULL。
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
ListNode *curA1 = headA;//用于遍历
ListNode *curA2 = headA;//用于查找相交
ListNode *curB1 = headB;
ListNode *curB2 = headB;
int numA = 0;//记录长度
int numB = 0;
//遍历A和B长度
while(curA1){
curA1 = curA1 -> next;
numA++;
}
while(curB1){
curB1 = curB1 -> next;
numB++;
}
//A比B长
if(numA>numB){
// 求长度差
int gap = numA - numB;
// 让curA和curB在同一起点上(末尾位置对齐)
while (gap--) {
curA2 = curA2->next;
}
while(curB2){
if(curB2 == curA2){
return curB2;
}
curA2 = curA2 -> next;
curB2 = curB2 -> next;
}
}
//B比A长或相等长
else{
// 求长度差
int gap = numB - numA;
// 让curA和curB在同一起点上(末尾位置对齐)
while (gap--) {
curB2 = curB2->next;
}
while(curA2){
if(curB2 == curA2){
return curB2;
}
curA2 = curA2 -> next;
curB2 = curB2 -> next;
}
}
return NULL;
}
};
142.环形链表 II
思路
判断有环
使用快慢指针法,分别定义 fast 和 slow 指针,从头结点出发,fast指针每次移动两个节点,slow指针每次移动一个节点,如果 fast 和 slow指针在途中相遇 ,说明这个链表有环。
- fast指针一定先进入环中,如果fast指针和slow指针相遇的话,一定是在环中相遇。
- fast是走两步,slow是走一步,在环内把slow看作静止,则fast是一个节点一个节点的靠近slow的,所以fast一定可以和slow重合。
- fast和slow相遇一定有环,但不能说fast和slow一定在入口相遇,即fast==slow,此时的fast或slow位置不一定是环的入口位置。
找入口位置
假设从头结点到环形入口节点 的节点数为x。 环形入口节点到 fast指针与slow指针相遇节点 节点数为y。 从相遇节点 再到环形入口节点节点数为 z。 如图所示:
那么相遇时: slow指针走过的节点数为: x + y, fast指针走过的节点数:x + y + n (y + z),n为fast指针在环内走了n圈才遇到slow指针, (y+z)为 一圈内节点的个数A。
因为fast指针是一步走两个节点,slow指针一步走一个节点, 所以 fast指针走过的节点数 = slow指针走过的节点数 * 2:
(x + y) * 2 = x + y + n (y + z)
---> x + y = n (y + z)
---> x = n (y + z) - y
---> x = (n - 1) (y + z) + z
当
n=1的时候,公式就化解为x = z,这就意味着,从头结点出发一个指针(index2),从相遇节点 也出发一个指针(index1),这两个指针每次只走一个节点, 那么当这两个指针相遇的时候就是 环形入口的节点。即让index1和index2同时移动,每次移动一个节点, 那么他们相遇的地方就是 环形入口的节点。
当
n>1是fast指针在环形转n圈之后才遇到 slow指针。 这种情况和n为1的时候 效果是一样的,一样可以通过这个方法找到 环形的入口节点,只不过,index1 指针在环里 多转了(n-1)圈,然后再遇到index2,相遇点依然是环形的入口节点。
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
ListNode* fast = head;
ListNode* slow = head;
//Q:为什么while里面是判断fast!=NULL&&fast->next!=NULL?
//A:while中执行fast=fast->next->next;如果不判断fast!=NULL&&fast->next!=NULL则可能因为fast->next==NULL,导致fast=fast->next->next报错。
while(fast!=NULL && fast->next!=NULL){
fast = fast->next->next;
slow = slow->next;
//说明有环,但不一定是入口
//且当fast == slow时,此时的fast或slow的位置即是相遇的位置
if(fast == slow){
ListNode *index1 = fast;//相遇的位置
ListNode *index2 = head;//开头的位置
//没在入口相遇
while(index1!=index2){
index1 = index1->next;
index2 = index2->next;
}
//循环结束则代表找到入口了,返回index1或index2都可
return index1;
}
}
return NULL;
}
};
总结
今日学习时长:4h+40min