1.两两交换结点
题目链接:. - 力扣(LeetCode)
讲解链接:代码随想录 (programmercarl.com)
视频讲解:帮你把链表细节学清楚! | LeetCode:24. 两两交换链表中的节点_哔哩哔哩_bilibili
本题注意处理交换结点前后结点的关系,即前结点的前结点和后结点的后结点,每次交换完两个节点后,将所有工具指针向后移动两位即可。
而中止循环的时机是前结点存在,后结点不存在,或者二者都不存在。所以仅需判断后指针所指是否为空即可。
再来思考需要多少工具指针完成一次交换:前结点和后结点的指针,因为要前前结点指向后结点,所以需要pre指针指向前前结点,然后后结点指向前结点,前结点指向后后结点。而此时后结点的next已经不是后后结点了,所以还需要一个指向后后结点的指针,一共四个指针。
代码如下:
ListNode *swapPairs(ListNode *head) {
ListNode *dummyNode = new ListNode(0);
dummyNode->next = head;
if (head == nullptr || head->next == nullptr)
return head;
else {
ListNode *Pre = dummyNode;
ListNode *Left = dummyNode->next;
ListNode *Right = dummyNode->next->next;
while (Right != nullptr) {
ListNode* temp = Right->next;
Pre->next = Right;
Right->next = Left;
Left->next = temp;
if (temp == nullptr)
break;
Pre = Left;
Left = temp;
Right = temp->next;
}
}
head = dummyNode->next;
delete dummyNode;
return head;
}
tips:在while循环中创建的变量,在循环结束后会自动回收。新创建的不再使用的结点尽量使用delete回收内存空间。
2.删除倒数位置的结点
题目链接:. - 力扣(LeetCode)
讲解链接:代码随想录 (programmercarl.com)
视频讲解:链表遍历学清楚! | LeetCode:19.删除链表倒数第N个节点_哔哩哔哩_bilibili
本题直观的解法是遍历两遍,第一遍得到链表长度,第二遍找到指定位置。
而使用双指针法可以做到遍历一遍找到位置,方法是构造位置相差n(删除的是倒数第n位置)位的两个指针。前指针从虚头结点开始,两个指针同步向后移动,当后指针到达末尾,前指针所指位置就是目标位置的前一位(为了方便删除结点,所以定位到前一位)。
代码如下:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode *dummyNode = new ListNode(0);
dummyNode->next = head;
ListNode *slowIndex = dummyNode;
ListNode *fastIndex = dummyNode;
while (n-- && fastIndex != nullptr){
fastIndex = fastIndex->next;
}
while (fastIndex->next != nullptr){
slowIndex = slowIndex->next;
fastIndex = fastIndex->next;
}
ListNode* temp = slowIndex->next;
slowIndex->next = temp->next;
delete temp;
head = dummyNode->next;
delete dummyNode;
return head;
}
tips:指针的移动使用p=p->next而不是++p,++p会让指针在内存中前进一个所指内容大小的地址,而链表并不是连续存储的,所++p不适用于指针移动。
3.两链表的公共部分
题目链接:. - 力扣(LeetCode)
讲解链接:代码随想录 (programmercarl.com)
本题要要找到两链表的公共部分,而这个公共部分存在于两个链表的后半部分。要找到这个公共部分的起点,其实只要让两个指针分别遍历两个链表,直到他们所指内容相同即可(公共部分不是说两条链表后半部分拥有同样地两份结点,而是他们共用一套结点)。
那么问题是两个链表的长度不一样,两个指针应该如何移动。如果是两层循环,那么一定是O(n^2)的复杂度。既然我们得知他们后半部分一样,那么长一点的链表多出来的部分,一定不是公共部分。所以我们可以跳过这个部分,然后两个指针同步移动即可。那么如何跳过?我们只需要得知长链表比短链表长多少即可,所以要得到两个链表的长度,就能解决该问题。
代码如下:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
int lengthA = 1;
int lengthB = 1;
ListNode *curA = headA;
ListNode *curB = headB;
while (curA != nullptr){
curA = curA->next;
++lengthA;
}
while (curB != nullptr){
curB = curB->next;
++lengthB;
}
curA = headA;
curB = headB;
int offset;
if (lengthB > lengthA){
swap(curA,curB);
swap(lengthA,lengthB);
}
offset = lengthA - lengthB;
while (offset--){
curA = curA->next;
}
while (curA != nullptr){
if (curA == curB)
return curA;
else{
curA = curA->next;
curB = curB->next;
}
}
return nullptr;
}
tips:如果有两段结构完全一致的代码,可以思考是否可以简化。如上面比较两个链表长度的部分,正常我们要分两个情况分别写两段代码。可以使用如上述交换的方式将两种情况的后续处理变成同一种,这样就省去了一段相似的代码。
4.循环链表
题目链接:. - 力扣(LeetCode)
讲解链接:代码随想录 (programmercarl.com)
视频讲解:把环形链表讲清楚! 如何判断环形链表?如何找到环形链表的入口? LeetCode:142.环形链表II_哔哩哔哩_bilibili
本题难度较高,主要在数学逻辑的思考。这里贴上代码随想录的分析,说的很仔细,理解完成后该题就很好写代码了。
判断链表是否有环
可以使用快慢指针法,分别定义 fast 和 slow 指针,从头结点出发,fast指针每次移动两个节点,slow指针每次移动一个节点,如果 fast 和 slow指针在途中相遇 ,说明这个链表有环。
为什么fast 走两个节点,slow走一个节点,有环的话,一定会在环内相遇呢,而不是永远的错开呢
首先第一点:fast指针一定先进入环中,如果fast指针和slow指针相遇的话,一定是在环中相遇,这是毋庸置疑的。
那么来看一下,为什么fast指针和slow指针一定会相遇呢?
可以画一个环,然后让 fast指针在任意一个节点开始追赶slow指针。
会发现最终都是这种情况, 如下图:
fast和slow各自再走一步, fast和slow就相遇了
这是因为fast是走两步,slow是走一步,其实相对于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): x + y = n (y + z)
因为要找环形的入口,那么要求的是x,因为x表示 头结点到 环形入口节点的的距离。
所以要求x ,将x单独放在左面:x = n (y + z) - y
,
再从n(y+z)中提出一个 (y+z)来,整理公式之后为如下公式:x = (n - 1) (y + z) + z
注意这里n一定是大于等于1的,因为 fast指针至少要多走一圈才能相遇slow指针。
这个公式说明什么呢?
先拿n为1的情况来举例,意味着fast指针在环形里转了一圈之后,就遇到了 slow指针了。
当 n为1的时候,公式就化解为 x = z
,
这就意味着,从头结点出发一个指针,从相遇节点 也出发一个指针,这两个指针每次只走一个节点, 那么当这两个指针相遇的时候就是 环形入口的节点。
也就是在相遇节点处,定义一个指针index1,在头结点处定一个指针index2。
让index1和index2同时移动,每次移动一个节点, 那么他们相遇的地方就是 环形入口的节点。
动画如下:
那么 n如果大于1是什么情况呢,就是fast指针在环形转n圈之后才遇到 slow指针。
其实这种情况和n为1的时候 效果是一样的,一样可以通过这个方法找到 环形的入口节点,只不过,index1 指针在环里 多转了(n-1)圈,然后再遇到index2,相遇点依然是环形的入口节点。
代码如下:
ListNode *detectCycle(ListNode *head) {
ListNode* fast = head;
ListNode* slow = head;
while(fast != NULL && fast->next != NULL) {
slow = slow->next;
fast = fast->next->next;
// 快慢指针相遇,此时从head 和 相遇点,同时查找直至相遇
if (slow == fast) {
ListNode* index1 = fast;
ListNode* index2 = head;
while (index1 != index2) {
index1 = index1->next;
index2 = index2->next;
}
return index2; // 返回环的入口
}
}
return NULL;
}