链表快慢指针
快慢指针中的快慢指的是指针沿链表移动的步长,即每次向前移动速度的快慢。快指针每次沿链表向前移动两步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;
}
};
二、环形链表I
给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 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;
}
};
三、环形链表II
1、判断两个无环单向链表是否,并返回相交节点
思路: 如果链表都无环,则先判断链表的尾指针是否一样,如果不一样,则没有相交。如果一样,则找出两个链表的长度差,将两个链表从距离尾节点同样的距离进行扫描,如果相交,则必然有一处扫描节点相同。 见6.
2、判断有环单向链表是否相交,并返回相交节点
思路:
- 先找到每个链表和环的相交节点,若两个相交节点一致,则两个链表相交。
- 若两个链表和环的相交节点不一致,则判断从一个相交节点出发,沿环遍历是否能找到另一个相交节点,若找到则两个链表相交,若没找到则不相交。
四、删除链表倒数第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。
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/412c8593c35f514a1c99a8bb98e599e4.png)
分析:
- 先让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;
}
}
}
}
};
七、 K 个一组翻转链表
给你一个链表,每 k 个节点一组进行翻转,请你返回翻转后的链表。k 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。
示例:给你这个链表:1->2->3->4->5;当 k = 2 时,应当返回: 2->1->4->3->5;当 k = 3 时,应当返回: 3->2->1->4->5。
说明:你的算法只能使用常数的额外空间。你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* reverseKGroup(ListNode* head, int k) {
int length = 0;
for (ListNode* tmp = head; tmp != nullptr; tmp = tmp->next)
length++;
if (length < k||k==0)
return head;
int swapNum =0;
ListNode* pre = nullptr;
ListNode* begin = nullptr;
ListNode* end = nullptr;
ListNode* res = nullptr;
int num = 1;
for (ListNode* tmp = head; tmp != nullptr; ) {
if (num == 1) {
begin = tmp;
}
if (num == k)
{
end = tmp;
tmp = end->next;
reverselist(begin, end);//交换完链表后begin指向反转链表的尾节点,end指向反转完链表的首节点。
if (res == nullptr)
res = end;
if(pre!=nullptr)
pre->next = end;
pre = begin;
num = 1;
swapNum++;
if (swapNum == length / k) {
pre->next = tmp;
break;
}
continue;
}
num++;
tmp = tmp->next;
}
return res;
}
//反转从begin到end的链表 //交换完链表后begin指向反转链表的尾节点,end指向反转完链表的首节点。 1 2 3 4
void reverselist(ListNode* begin, ListNode* end) {
ListNode* s = begin;
ListNode* pre = nullptr;
ListNode* tmp = nullptr;
while (pre!= end) {
tmp = s->next;
s->next = pre;
pre = s;
s = tmp;
}
}
};
八、链表指定区间反转
将一个链表m 位置到n 位置之间的区间反转,要求时间复杂度 O(n),空间复杂度 O(1)。
例如:
给出的链表为 1→2→3→4→5→NULL,m=2,n=4,
返回1→4→3→2→5→NULL.
注意:
给出的 m,n 满足以下条件:1≤m≤n≤链表长度
ListNode *reverseBetween(ListNode *head, int m, int n) {
if(head == NULL || head->next == NULL) return head;
ListNode *q = NULL, *p = head;
for (int i = 0; i < m - 1; i++)
{
q = p;
p = p->next;
}
// p此时指向第m个结点
ListNode *end = p;
ListNode *pPre = p, *nxt = NULL;
p = p->next;
// m->n之间的结点逆序
for (int i = m + 1; i <= n; ++i)
{
nxt = p->next;
p->next = pPre;
pPre = p;
p = nxt;
}
// p指向原链表中第n个结点的下一个结点
// end表示逆序子链表的尾结点
end->next = p;
// pPre指向的是逆序后的子链表头
// q指向的是第m个结点前一个结点 注意有可能是NULL
if (q)
q->next = pPre; // 不是NULL 链接起来即可
else
head = pPre; // 是NULL 头结点即为子链表头
return head;
}