上一篇博客我们已经完成了链表的所有内容,那么这一篇博客我们来看一下三个特别有意思的链表题目。
**第一个题目如下:**
相信不少朋友看到这题目就已经晕了,那就简单说明下这个题目,题目就是创建一个链表,其中每个节点有两部分–指针域与数值域,指针域里也有两个指针next(指向下一个节点),random(随机指向),要求我们拷贝一份这个链表,使原链表和复制链表中的这些指针能够表示相同的链表状态。复制链表中的指针都不应指向原链表中的节点 。
也就是上图的形式,这个题目如果单纯的只拷贝一份链表和每个节点的val,相信大家可以很快的做出,也就是遍历一下原链表,遍历一个节点创建一个新的节点并拷贝值,最后将节点连接形成链表即可。但现在增加了一个random指针,我们该如何解决这个问题呢?
受限于当前的知识,我们可用的方法很少,下面是一个很巧妙的思路:
1、创建新的节点,构造新链表
我们可以遍历一个节点拷贝一个节点,并且将这个新的节点插入至原节点的后面。
2、完成random的指向
当我们把这一步以图像的形式展现,我们就可以发现新链表当前节点的random指针是不是指向原链表原节点的random->next指向的节点。
3、取下拷贝的节点,形成题目要求的链表
把这个节点连接形成新的链表,我们可以使用之前学过的尾插,头插都是可以的。
最后还有一个小细节,就是如果我们只取下节点,那原链表是不是被我们断开了,我们可以恢复原链表的结构也可以不恢复,题目里没有说明就不用管。
代码如下:
/**
* Definition for a Node.
* struct Node {
* int val;
* struct Node *next;
* struct Node *random;
* };
*/
struct Node* copyRandomList(struct Node* head) {
struct Node*pcur=head;//创建遍历原链表的指针pcur
while(pcur)
{
struct Node*newnode=(struct Node*)malloc(sizeof(struct Node));
newnode->val=pcur->val;
newnode->next=pcur->next;
pcur->next=newnode;
pcur=newnode->next;
}//这一部分就是拷贝节点,并插入原节点后面
struct Node*cur=head;//同样为遍历链表的节点
while(cur)
{
struct Node*newnode=cur->next;
if(cur->random==NULL)
{
newnode->random=NULL;
}
else{
newnode->random=cur->random->next;
}
cur=newnode->next;
}//这一部分完成的是新节点的random的指向
struct Node*phead=NULL;//创建一个新的链表phead为头指针,ptail为尾指针
struct Node*ptail=NULL;
struct Node*Cur=head;
while(Cur)
{
struct Node*node=Cur->next;//node指向的是原链表的新节点
struct Node*next=node->next;//next指向的是原链表的原节点
if(ptail==NULL)//如果新链表为空
{
phead=ptail=node;
}
else//链表不为空
{
ptail->next=node;
ptail=ptail->next;
}
Cur=next;
}
return phead;//返回新链表的头
}
**第二个题目如下:**
这道题目代码比较简单,想要理解透彻还是有一定难度的。
思路如下:
就是使用快慢指针,快指针一步走两个节点,慢指针一步走一个节点,让慢指针去追这个快指针,当追上的时候也就是fast==slow,那就证明带环,如果一直追不上那就不带环。
那一定追的上吗?有没有可能出现错过的情况?快指针可以一步走三个节点吗?走三个节点会不会错过呢?这几个问题就是我们现在需要解决的。
第一个问题:那一定追的上吗?有没有可能出现错过的情况?
从现在开始,fast走两步,slow走一步,那么两指针每走一步,它们之间的距离减小1,所以N取任何值slow与fast指针都会相遇,故不存在错过的情况。
第二个问题:快指针可以一步走三个节点吗?走三个节点会不会错过呢?
由上面的图我们可以推出:
那么我们可以总结一下:
当N为偶数时,无论R为奇数偶数,都能够追上,也就是不会错过。
当N为奇数,R为奇数时,能够追上;
当N为奇数,R为偶数时,永远追不上。
但是存不存在N为奇数,R为偶数这种情况?
由上面得出的结论就是fast一步走三个节点,一定能追上。
根据上面的推导过程,fast一步走4个节点、5个节点都是可以推出来的。
代码实现很简单:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
bool hasCycle(struct ListNode *head) {
struct ListNode*fast=head;
struct ListNode*slow=head;
while(fast&&fast->next)
{
fast=fast->next->next;
slow=slow->next;
if(fast==slow)
{
return true;
}
}
return false;
}
**第三个题目:**
这个题目就是在上一个题目的基础上更加深入一点,需要我们返回这个循环的初始节点;
第一步:判断环的有无
因为这一步与上一个题目基本一样,所以我这里就不再重复了;
第二步:让slow走到fast的位置,也就是让两个指针相遇定义为meet,返回当前节点,这一步也可以通过前一个题目实现;
第三步:让pcur从链表的头开始走,同时meet也开始在环内走,当两者相等时,两指针指向的节点就是开始循环的节点。
下面我们就来讨论为什么:
代码实现:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode*fastnode(struct ListNode*head)
{
struct ListNode*fast=head;
struct ListNode*slow=head;
while(fast&&fast->next)
{
fast=fast->next->next;
slow=slow->next;
if(slow==fast)
{
return fast;//这里我没有创建新的meet指针,当两者相等时,返回其中一个效果相同
}
}
return NULL;//判断是否有环
}
struct ListNode *detectCycle(struct ListNode *head) {
struct ListNode*fast=fastnode(head);
if(fast==NULL)//如果链表没有环就直接返回NULL
{
return NULL;
}
struct ListNode*slow=head;
while(slow!=fast)//这一步就是让两指针相遇,返回相遇时的节点,也就是环开始的节点
{
slow=slow->next;
fast=fast->next;
}
return fast;
}
最后一个题还有一种做法,那就是将meet指向的节点断开,那现在整个题目就变为了相交链表求相交节点的问题了,这里只简单的说明一下思路,有兴趣的可以下来实现一下;