一.链表的中间结点
题目描述:
给定一个头结点为head的非空单链表,返回链表的中间结点。
如果有两个中间结点,则返回第二个中间结点
要求只能遍历一遍
解析:
定义一个快指针:fast,一个慢指针:slow
利用两个指针的速度差
个数为奇数时:
个数为偶数时:
代码实现:
struct ListNode
{
int val;
struct ListNode *next;
};
struct ListNode* middleNode(struct ListNode* head)
{
struct ListNode *fast,*slow;
slow = fast = head;
while(fast&&fast->next)
{
slow=slow->next;
fast = fast->next->next;
}
return slow;
}
二、链表中倒数第k个结点
题目描述:
输入一个链表,输出该链表中倒数第k个结点。只能遍历链表一遍。
示例:
输入:1,{1,2,3,4,5}
返回值:{5}
解析:
快慢指针并不是说一定是一个速度快 ,一个速度慢,而是用来拉开差距。
有两种方法:
当fast先走k步(令k=2),再同时走:
以此类推....倒数第二个如下:
代码实现:
struct ListNode
{
int val;
struct ListNode *next;
};
struct ListNode* FindKthToTail(struct ListNode* pListHead, int k ) {
struct ListNode *fast,*slow;
fast = slow = pListHead;
//fast先走k步
while(k--)
{
//链表可能没有k步长
if(fast == NULL)
{
return NULL;
}
fast = fast->next;
}
//fast和slow一起移动
while(fast)
{
slow = slow->next;
fast = fast->next;
}
return slow;
}
三、环形链表(一)
题目描述:
给你一个链表的头节点 head ,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。
如果链表中存在环 ,则返回 true 。 否则,返回 false 。
来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/linked-list-cycle
输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。
解析:
本质为快慢指针的追击问题,如果能追上,则为一个环。
代码实现:
struct ListNode {
int val;
struct ListNode *next;
};
bool hasCycle(struct ListNode *head) {
struct ListNode* slow = head,*fast = head;
while(fast && fast->next)//链表有可能不带环
{
slow = slow->next;
fast = fast->next->next;
if(slow == fast)//带环相遇
return true;
}
return false;
}
【扩展问题】
为什么快指针每次走两步,慢指针走一步可以?
假设链表带环,两个指针最后都会进入环,快指针先进环,慢指针后进环。当慢指针刚
进环时,可能就和快指针相遇了,最差情况下两个指针之间的距离刚好就是环的长度。
此时,两个指针每移动一次,之间的距离就缩小一步,不会出现每次刚好是套圈的情
况,因此:在满指针走到一圈之前,快指针肯定是可以追上慢指针的,即相遇。
快指针一次走3步,走4步,...n步行吗?
总结:速度差为1一定能追上
四、环形链表(二)
题目描述:
给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。
不允许修改 链表。
来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/linked-list-cycle-ii
输入:head = [3,2,0,-4], pos = 1
输出:返回索引为 1 的链表节点
解释:链表中有一个环,其尾部连接到第二个节点。
解析:
方法一:让一个指针从链表起始位置开始遍历链表,同时让一个指针从判环时相遇点的位置开始绕环运行,两个指针都是每次均走一步,最终肯定会在入口点的位置相遇
struct ListNode
{
int val;
struct ListNode *next;
};
struct ListNode *detectCycle(struct ListNode *head)
{
struct ListNode* slow = head,*fast = head;
while(fast&&fast->next)
{
slow = slow->next;
fast = fast->next->next;
if(slow==fast)
{
struct ListNode* meet = slow;
while(head!=meet)
{
head = head->next;
meet = meet->next;
}
return meet;
}
}
return NULL;
}
方法二:利用输入两个链表,找出它们第一个公共结点,这个结点即为入口点
struct ListNode
{
int val;
struct ListNode *next;
};
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB)
{
struct ListNode* curA =headA,*curB = headB;
int lenA = 0,lenB = 0;
while(curA)
{
++lenA;
curA = curA->next;
}
while(curB)
{
++lenB;
curB = curB->next;
}
//尾节点不相等就不相交
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 longList;
}
struct ListNode *detectCycle(struct ListNode *head)
{
struct ListNode* slow = head,*fast = head;
while(fast&&fast->next)
{
slow = slow->next;
fast = fast->next->next;
if(slow==fast)
{
struct ListNode* meet = slow;
struct ListNode* otherHead = meet->next;
meet->next = NULL;
return getIntersectionNode(head,otherHead);
}
}
return NULL;
}