有两个链表,它们头结点是已知的,相交之后是单链表,但是相交的结点位置和结点数未知,求出剩下相交的单链表
类似于这种题目有很多方法。
首先可以想到它们刚开始是未相交的,有两个链表。但是后面是相交成一个链表,所以结点是一样的,因此可以从后往前,找到即将分叉的地方,这是可以采取先进后出的栈来解决。
先将他们都遍历一遍,分别放入两个栈内,然后弹栈,直到两个结点不同时停止,弹栈的同时可以新建一个链表,使用头插法来存储弹出的结点。同样的思路还有好多方法
再换个思路,可以先将两个链表拼接起来,上图可以看到,无论是A连B还是B连A,他们最后的三个结点都是一样的,都是6,7,8,所以只要找到最后一样的结点即可。
将链表以公共结点分为左右,可以知道right_a=right_b,如上图,将两个链表拼接,最后一段就是所要求的。代码如下
struct ListNode *getIntersectionNode(struct ListNode *pHead1, struct ListNode *pHead2)
{
//若两个头结点都为空则退出
if(pHead1==NULL||pHead2==NULL)
return NULL;
//用两个指针分别指向两个链表
struct ListNode *p1 = pHead1;
struct ListNode *p2 = pHead2;
// 当两个结点不同时就循环,直到两个结点相同
while(p1!=p2)
{
p1=p1->next;
p2=p2->next;
//这里if(p1!=p2)是为了防止死循环,如果两个链表没有公共结点,则他们同时指向为null时就会退出
//这里条件还可以写成 if(p1==NULL||p2==NULL)
if(p1!=p2)
{
if(p1==NULL)
p1=pHead2;
if(p2==NULL)
p2=pHead1;
}
}
return p1;
}
还有一种利用快慢双指针的方法来做
如图,链表a长度为6,链表b长度为4,求出两者差值,使链表a的头指针前移2次,这样a和b的头指针就会从同一起跑线开始遍历后面的链表,方便找到相同的结点。
大体思路就是先求出两个链表长度,求出两个长度差值sub,长的链表的头指针就先移动sub个结点,然后在和短的链表同时遍历,访问到相同结点即可。
struct ListNode *getIntersectionNode(struct ListNode *pHead1, struct ListNode *pHead2)
{
if(pHead1==NULL||pHead2==NULL)
return NULL;
//先求出两个链表的长度
int l1=getLength(pHead1);
int l2=getLength(pHead2);
struct ListNode *p1=pHead1;
struct ListNode *p2=pHead2;
//求出两个链表的差值
int sub=l1>l2?l1-l2:l2-l1;
//先让长的先走sub步
if(l1>l2)
for(int i=1;i<=sub;i++)
p1=p1->next;
if(l1<=l2)
for(int i=1;i<=sub;i++)
p2=p2->next;
//此时两个指针处于同一起点
while(p1!=p2)
{
p1=p1->next;
p2=p2->next;
}
return p1;
}
注:上面getLength函数是自己定义的
这样一个问题就可以举一反三,锻炼多个角度思考问题。
看见这种问题仍然是可以想想常用的数据结构和算法。
首先可以想到利用栈先进后出的特点来做,将整个链表放入栈中,然后一边弹栈,一边从头遍历链表,判断两个结点是否相等。
再此基础上还可以优化一下,就是先获取链表长度,再压入一半的元素到栈中,接着一边遍历后续元素,一边弹栈,判断两者是否相等。
可以将整个链表反转,反转后和原链表比较。注意反转时候要利用头插法
优化一下,可以先遍历一遍,获取链表长度,然后反转一半时候,开始比较两个链表
还可以继续优化一下,遍历一遍即可。利用快慢指针,slow->next一下,fast->next->next一下,然后反转前面的链表。当fast走到表尾时,slow走到一半,此时一边遍历反转后的,一边移动slow
先将slow指向结点保存,再移动fast和slow指针,再将slow指向的结点保存到反转链表中
注意顺序不能返,如果顺序反了,如先保存到反转链表中,再移动指针,那么fast和slow指针会指向反转链表,而不是原链表
fast指针先指向第1个结点,每次移动两个位置,也就是第二次会指向第3个结点,第三次指向第5个结点,也就是1,3,5,7,9类似这样的规律,而不是2,4,6,8,10这种。
所以当结点个数为奇数和偶数时,fast指向不同,如果为偶数时,fast会指向NULL,否则会指向最后一个尾结点。此时循环条件要两者兼顾,即fast != NULL && fast->next !=NULL
还要注意,当偶数个结点时,fast指向NULL时,slow指向一半+1个结点
而当奇数个结点时,slow指向的是中间的结点,为了统一处理,这里要做个判断,判断节点个数为奇数时(也就是fast->next==NULL时),slow要再移1位
bool isPalindrome(struct ListNode *head)
{
if(head==NULL||head->next==NULL)
return true;
struct ListNode*slow=head;
struct ListNode*fast=head;
struct ListNode*pre=head;
struct ListNode*newhead=NULL;
while(fast!=NULL&&fast->next!=NULL)
{
pre=slow;
//下面两行代码不要放到最后面,要提前前移,否则pre会将链表指向null,使链表打断
slow=slow->next;
fast=fast->next->next;
pre->next=newhead;
newhead=pre;
}
if(fast==NULL)
slow=slow->next;
while(slow)
{
if(slow->val!=newhead->val)
return false;
slow=slow->next;
newhead=newhead->next;
}
return true;
}