一、返回倒数第k个节点

解题思路:快慢指针。快指针和慢指针的距离始终是k,快指针走到最后的时候,慢指针刚好指向倒数第k个节点。
cpp
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
int kthToLast(struct ListNode* head, int k)
{
struct ListNode* fast = head,*slow = head;
//快指针先走k步
while(k--)
{
fast = fast->next;
}
while(fast != NULL)
{
fast = fast->next;
slow = slow->next;
}
return slow->val;
}
二、判断是否为回文链表

解题思路:用双指针法找到中间节点,把链表中间节点及以后的部分逆置,然后用双指针法一一比较节点值。
cpp
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
typedef struct ListNode ListNode;
struct ListNode* middleNode(struct ListNode* head)
{
//创建快慢指针
ListNode* slow = head;
ListNode* fast = head;
while(fast && fast->next)
{
slow = slow->next;
fast = fast->next->next;
//此时slow正好指向中间节点
}
return slow;
}
struct ListNode* reverseList(struct ListNode* head)
{
//判空
if(head == NULL)
{
return head;
}
//创建三个指针
ListNode*n1,*n2,*n3;
n1 = NULL,n2 = head,n3 = n2->next;
while(n2)
{
n2->next = n1;
n1 = n2;
n2 = n3;
if(n3)
n3 = n3->next;
}
return n1;
}
bool isPalindrome(struct ListNode* head)
{
ListNode* mid = middleNode(head);
ListNode* rmid = reverseList(mid);
while(rmid && head)
{
if(rmid->val != head->val)
return false;
rmid = rmid->next;
head = head->next;
}
return true;
}
这道题用到了寻找链表中间节点和反转链表的算法,不熟悉的可以看这里:
三、相交链表



解题思路:先判断两个链表是否相交,相交再寻找第一个交点。
判断是否相交,只要看两链表的最后一个节点是否相同,相同就是相交,不同就是不相交。
寻找第一个相交节点,要先计算两链表的长度。假设短链表的长度是k,则只需创建两个指针,分别别从短链表的第一个节点和长链表的倒数第k个节点开始同时往后走,遇到的第一个相同节点就是两个链表的第一个交点。这样写出来的算法时间复杂度为O(N)
cpp
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB)
{
struct ListNode* curA = headA,*curB = headB;
int lenA = 1,lenB = 1;
while(curA->next)
{
curA = curA->next;
++lenA;
}
while(curB->next)
{
curB = curB->next;
++lenB;
}
if(curA != curB)
{
return NULL;
}
//长的先走差距步,再同时走,第一个相等的就是交点
int gap = abs(lenA-lenB);
struct ListNode* longList = headA,*shortList = headB;
if(lenB > lenA)
{
longList = headB;
shortList = headA;
}
while(gap--)
{
longList = longList->next;
}
while(longList != shortList)
{
longList = longList->next;
shortList = shortList->next;
}
return shortList;
}
四、环形链表
4.1 判环


解题思路:快慢指针。fast指针的速度是slow指针的两倍。如果fast走到了NULL,说明链表不带环;如果fast追上了slow,说明链表带环。
cpp
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
bool hasCycle(struct ListNode *head)
{
struct ListNode* slow = head;
struct ListNode* fast = head;
while(fast && fast->next)
{
slow = slow->next;
fast = fast->next->next;
if(slow == fast)
{
return true;
}
}
return false;
}
这道题还有两个问题需要想清楚:
1、为什么环形链表中fast和slow一定会相遇?
假设slow进环时fast和slow的距离是N。fast追击slow的过程中,slow每走一步,fast就走两步,两者的距离缩短1。那么N次之后fast必然追上slow。
2、如果fast每次走的步数变为3步、4步、5步、n步,是否可行?
如果fast每次走3步,假设slow进环时fast和slow的距离是N。如果N是偶数,N/2次追上。如果N是奇数,第一次追击会错过,进入新一轮追击,假设环的长度是C,那么错过之后两者距离是C-1。如果C-1是偶数,这一轮追击中fast会追上slow。如果C-1是奇数,那么进入死循环,永远追不上。
也就是说,N是奇数和C是偶数同时满足的时候,就永远追不上。
那么这两个条件可能同时满足吗?
为了验证,我们需要找到C和N的关系式。
假设slow进环时,fast已经在环中走了x整圈。假设链表除了环之外的部分长度为L。根据fast走的距离是slow的三倍,我们可以列出如下等式:
L+x*C+(C-N) = 3L
整理得到
2L = (x+1)*C-N
此式等号左边为偶数。如果N为奇数,C为偶数,那么等号右边为奇数,等号不成立。由此可知这两个条件不可能同时满足,也就是fast一定会追上slow。
如果fast每次走4步,仍然假设slow进环时fast和slow的距离是N。有三种情况,如图:

接下来要讨论C-2、C-1 %3的三种可能结果。不再赘述。
结论是只要n是大于等于2的整数,都可以完成判环的功能。
4.2 寻找环的入口点


解题思路:开始的部分同上一题,让slow和fast在环中相遇。接下来,让head从链表的第一个节点开始走,meet从两指针在环内相遇的地方开始走,两者都每次走一步,那么两者会刚好在链表的入口处相遇。
为什么呢?分析一下:

设链表除环之外的部分长为L,slow和fast相遇点距离环入口处的距离为N,环长为C。假设fast
追上slow时fast已经转了完整的x圈。
slow走的距离为L + N
fast走的距离为L + N +x*C
根据fast指针走的距离是slow的两倍,可以列出等式:
2*(L + N) = L + N + x*C
化简得到L = x*C - N
可知两者总会在链表入口处相遇。
cpp
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode *detectCycle(struct ListNode *head)
{
struct ListNode* slow = head;
struct ListNode* fast = head;
while(fast && fast->next)
{
slow = slow->next;
fast = fast->next->next;
if(slow == fast)
{
struct ListNode* meet = slow;
while(meet != head)
{
meet = meet->next;
head = head->next;
}
return meet;
}
}
return NULL;
}
还有第二种解题思路:将meet的下一个节点设为newhead,然后将meet的next指针置为空。

这样就变成了相交链表求交点的问题了。直接CV上一题的代码:
cpp
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB)
{
struct ListNode* curA = headA,*curB = headB;
int lenA = 1,lenB = 1;
while(curA->next)
{
curA = curA->next;
++lenA;
}
while(curB->next)
{
curB = curB->next;
++lenB;
}
if(curA != curB)
{
return NULL;
}
//长的先走差距步,再同时走,第一个相等的就是交点
int gap = abs(lenA-lenB);
struct ListNode* longList = headA,*shortList = headB;
if(lenB > lenA)
{
longList = headB;
shortList = headA;
}
while(gap--)
{
longList = longList->next;
}
while(longList != shortList)
{
longList = longList->next;
shortList = shortList->next;
}
return shortList;
}
struct ListNode *detectCycle(struct ListNode *head)
{
struct ListNode* slow = head;
struct ListNode* fast = head;
while(fast && fast->next)
{
slow = slow->next;
fast = fast->next->next;
if(slow == fast)
{
struct ListNode* meet = slow;
struct ListNode* newhead = meet->next;
meet->next = NULL;
return getIntersectionNode(head,newhead);
}
}
return NULL;
}
虽然题目写了"不允许修改链表",但是这样写在leetcode里也能通过。
五、随机链表复制


解题思路:先复制每个节点,把每个新节点插入在原链表对应旧节点的后面,如图:

这样就在原节点和新节点之间建立了关系,可以接下来就可以解决random指针难以复制的问题了。我们让每个新节点的random指针指向对应旧指针所指节点的后面一个节点,就完成了random指针的复制。
最后,把新的节点尾插到新链表中即可。
cpp
/**
* Definition for a Node.
* struct Node {
* int val;
* struct Node *next;
* struct Node *random;
* };
*/
struct Node* copyRandomList(struct Node* head)
{
struct Node* cur = head;
//拷贝节点插入在原节点的后面
while(cur)
{
struct Node* copy = (struct Node*)malloc(sizeof(struct Node));
copy->val = cur->val;
copy->next = cur->next;
cur->next = copy;
cur = copy->next;
}
//控制random指针
cur = head;
while(cur)
{
struct Node* copy = cur->next;
if(cur->random == NULL)
{
copy->random = NULL;
}
else
{
copy->random = cur->random->next;//这一步是整道题目的关键
}
cur = copy->next;
}
//把拷贝节点取下来尾插成为新链表,然后恢复原链表
struct Node* copyhead = NULL;
struct Node* copytail = NULL;
cur = head;
while(cur)
{
struct Node* copy = cur->next;
struct Node* next = copy->next;
if(copytail == NULL)
{
copyhead = copytail = copy;
}
else
{
copytail->next = copy;
copytail = copytail->next;
}
cur->next = next;
cur = next;
}
return copyhead;
}