链表快慢指针
快慢指针中的快慢指的是指针沿链表移动的步长,即每次向前移动速度的快慢。快指针每次沿链表向前移动两步fast=fast->next->next
,慢指针每次向前移动一步slow=slow->next
。
一、回文链表
请判断一个链表是否为回文链表。用 O(n) 时间复杂度和 O(1) 空间复杂度解决此题。
示例 1:输入: 1->2 ;输出: false
示例 2:输入: 1->2->2->1 ;输出: true
分析:使用快慢指针来找到链表的中点: 首先我们设置两个指针slow和fast,slow指针每次移动一步,fast指针每次移动两步;如果链表中节点个数为奇数时,当快指针无法继续移动时,慢指针刚好指向中点;如果链表中节点个数为偶数时,当快指针走完,慢指针指向中点前一个节点。然后反转中间节点后的链表与中间节点前未反转的链表进行比较。
// 使用快慢指针来找到链表的中点:
ListNode *slow=head,*fast=head;
while(fast->next&&fast->next->next)
{
slow=slow->next;//slow指针每次走一步
fast=fast->next->next;//fast指针每次走两步
}
class Solution {
public:
bool isPalindrome(ListNode* head) {//O(n)、O(1)
ListNode* slow = head, *fast = head, *prev = nullptr;
while(fast->next&&fast->next->next)
{
slow=slow->next;//slow指针每次走一步
fast=fast->next->next;//fast指针每次走两步
}
while (slow){//reverse
ListNode* ovn = slow->next;
slow->next = prev;
prev = slow;
slow = ovn;
}
while (head && prev){//check
if (head->val != prev->val){
return false;
}
head = head->next;
prev = prev->next;
}
return true;
}
};
二、环形链表
给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。说明:不允许修改给定的链表。
分析:快慢指针中,因为每一次移动后,快指针都会比慢指针多走一个节点,所以他们之间在进入环状链表后,不论相隔多少个节点,慢指针总会被快指针赶上并且重合,此时就可以判断必定有环。快指针每次走两步,慢指针每次走一步,那么存不存在一种情况,快指针直接“越过了”慢指针,到达了慢指针的下一个格子呢?答案是不可能的,我们假设这种状态存在,也就是快指针越过了慢指针,领先慢指针一个格子,逆推一步,慢指针倒退一格,快指针倒退两格,那么快慢指针依然相遇,也就是说,快指针永远不可能直接越过慢指针,到达后一格。
利用快慢指针来判断链表中是否有环的思路就是:
- 设置两个指针,慢指针每次走一步,快指针每次走两步;
- 如果快慢指针相遇,那么链表含有环;
- 如果快指针到达了链表尾部且没有与慢指针相遇,那么链表不含有环。如不存在环,fast遇到NULL退出。
class Solution {
public:
bool hasCycle(ListNode *head) {
if (head == nullptr)
return false;
ListNode* slow = head;
ListNode* fast = head;
while(fast->next&&fast->next->next)
{
slow = slow->next;
fast = fast->next->next;
if (slow == fast)
return true;
}
return false;
}
};
利用双指针来找出环形链表的环入口指针的思路就是:
- 先定义两个指针p1和p2指向链表的头结点
- 如果链表中有n个节点,则指针p1先在链表上向前移动n步,然后两个指针以相同的速度向前移动;
- 当第二个指针指向环的入口节点时,第一个指针已经围绕着环走了一圈,又回到了入口节点。
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
//验证是否是环形链表
if (head == nullptr)
return head;
ListNode* slow = head;
ListNode* fast = head;
ListNode *meetNode= nullptr;
while(fast->next&&fast->next->next)
{
slow = slow->next;
fast = fast->next->next;
if (slow == fast) {
meetNode = slow;
break;
}
}
slow = head;
fast = head;
if (meetNode == nullptr)
return nullptr;
//得到环的数目
int cyclenum = 1;
ListNode* temp = meetNode;
while (temp->next!=meetNode) {
cyclenum++;
temp = temp->next;
}
//先移动fast节点cysclenum步
for (int i = 0; i < cyclenum; i++)
fast = fast->next;
//同时移动slow和fast
while (slow != fast) {
slow = slow->next;
fast = fast->next;
}
return slow;
}
};
三、删除链表倒数第N个节点
给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。
示例:给定一个链表: 1->2->3->4->5, 和 n = 2。当删除了倒数第二个节点后,链表变为 1->2->3->5
分析:利用双指针找到链表倒数第N个节点: 一个快指针fast,一个慢指针slow,让快指针先走n步,再让快慢指针一起走,当快指针走到链表最后,慢指针正好走在链表的倒数第n+1个位置,此时删除slow指针的后一个值,即删除了链表第n个位置的值。
再考虑一些特殊情况,比如链表只有一个值或无值时,任意有效删除之后,这时链表为空。还有要注意的是可能是要删除第一个节点,这时提前走n步的快指针会最后变为空指针,这个时候可以直接返回head -> next。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
if(!head || !head -> next) return NULL;
ListNode* slow = head;
ListNode* fast = head;
for (int i = 0; i < n; i++)
{
if (!fast->next)
{
return head -> next; //要注意的是可能是要删除第一个节点,这个时候可以直接返回head -> next
}
fast = fast->next;
}
while (fast->next){
slow = slow->next;
fast = fast->next;
}
slow->next = slow->next->next;
return head;
}
};
四、反转链表
反转一个单链表。
示例:输入: 1->2->3->4->5->NULL; 输出: 5->4->3->2->1->NULL
分析:采用双指针迭代的方法反转链表。 慢指针prev作为快指针反转后的next节点,同时每次反转完一个节点后,更新快慢指针分别向前移动一步。直到快指针为nullptr,返回prev作为反转后的头节点。
class Solution {
public:
ListNode* reverseList(ListNode* head) {
if (head == nullptr || head->next == nullptr)
return head;
ListNode* fast = head;
ListNode* prev = nullptr;
while (fast)
{
ListNode* temp = fast->next;
fast->next = prev;
prev = fast;
fast = temp;
}
return prev;
}
};
五、相交链表
编写一个程序,找到两个单链表相交的起始节点。
如下面的两个链表,相交的起始节点为c1。
分析:
- 先让A链表和B链表分别走的各自链表的结尾计算两个链表的长度。
- 若两个链表的最后的节点不相等的话,则说明链表不重合,返回nullptr。
- 否则存在链表相交。计算两个链表的长度差为s,先让长链表走s步。
- 长链表和短链表同时向前移动,若节点指针相同时,即找到了相交节点。
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
int lenA=0;
int lenB=0;
ListNode* curA = headA;
ListNode* curB = headB;
while(curA && curA->next)
{
lenA++;
curA=curA->next;
}
while(curB && curB->next)
{
lenB++;
curB=curB->next;
}
//此时curA 与curB均走到最后,若两个链表相交则curA等于curB
//1、不相交,返回NULL
//2、相交,求交点
if(curA != curB)
{
return NULL;
}
else
{
int gap = abs(lenA-lenB);//求两个链表长度差值的绝对值
ListNode* longlist = headA;
ListNode* shortlist = headB;
if(lenB > lenA)
{
longlist = headB;
shortlist = headA;
}
while(gap--)
{
longlist = longlist->next;
}
while(1)
{
if(longlist == shortlist)
{
return longlist;
}
else
{
longlist = longlist->next;
shortlist = shortlist->next;
}
}
}
}
};