来看第二题-两个链表的交叉
题目连接:https://www.lintcode.com/problem/intersection-of-two-linked-lists/description
要求我们找到两链表最开始的交叉节点。
为了方便解释,我们以样例1为例,设两个链表分别为
a1->a2->c1->c2-c3
b1->b2->b3->c1->c2->c3
由于最开始的交叉节点在c1,所以会输出c1
这里我们的做法是在先分别计算两个链表的长度,计算长度差值,然后较长的字符串走一段长度,该长度等于链表长度之差。然后两个链表同时向后遍历即可
这里由”//”表示的注释是针对代码的,/**/表示的注释是针对实例的。
将代码写入后,进行测试
输出为accepted,说明成功解决了该问题。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
int list_length(struct ListNode *phead)//计算链表长度
{
int length=0;
struct ListNode *pnode=phead;
while(pnode != NULL)//通过next遍历链表,直到为null说明链表遍历完成,length的值即为链表长度
{
length++;
pnode=pnode->next;
}
return length;
}
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
int A_length=list_length(headA);
int B_length=list_length(headB);
int gap_length=A_length>B_length?A_length-B_length:B_length-A_length;//计算两链表长度差值
struct ListNode *plong=headA;
struct ListNode *pshort=headB;
if(A_length<B_length)//plong指向长链表,pshort指向短链表
{
plong=headB;
pshort=headA;
}
for(int i=0;i<gap_length;i++) plong=plong->next; //长链表多走length步
/*以实例1而言,b1->b2->b3->c1->c2->c3多走1步,此时plong指向b2*/
while(plong && pshort && plong->val != pshort->val)//两个链表同时向后遍历比较
/*b1->b2->b3->c1->c2->c3从b2开始,a1->a2->c1->c2-c3从a1开始,如果plong的数值域不等于pshort的数值域则继续通过next遍历*/
{
plong=plong->next;
pshort=pshort->next;
}/*plong,pshort的数值域为c1时相同,故返回c1*/
return plong;
}
第三题我们来学习lintcode上的一道题目-旋转链表。
题目链接:
https://www.lintcode.com/problem/rotate-list/description
题目给定一个链表,旋转链表,使得每个结点向右移动k个位置,其中k是一个非负数。
使用很简单,我们在下图红框处写入代码就可以了
这种oj形式的好处在于更注重算法与数据结构的本质,不需要我们写其他无关的函数,宏,结构体等等,而只需要我们注重解决问题。而且,oj给出的一些特殊的输入例子来测试代码时,有助于我们在编程时培养严密谨慎的思维。在后续的课程中,大部分题目都会以oj的形式,和大家一起学习,选择有特色的、典型的题目进行分析讲解。
oj的背景介绍好了,接下来我们分析这道题目。
以题目给出的1->2->3->4->5->null,k=2,输出为4->5->1->2->3->NULL
这里的关键在于看出:输出的链表实际上是由两部分组成的,4->5,1->2->3->NULL
那么我们直观的想法就是找到第一个字符串4->5的头结点,尾结点;而4的前驱结点的后继指向null,此为第二个字符串
然后将这两个字符串连接在一起即可。
那么关键就是如何找到第一个字符串的头结点。
实际上,我们注意到这和k以及链表长度len有关。K mod n,如果结果为0,则旋转链表后实际上是不变的,直接返回即可。否则的话我们需要计算从原字符串头结点走到新的第一个字符串的头结点的步数,其值为链表长度-(k mod n)-1。
我们来看代码及注释:
这里由”//”表示的注释是针对代码的,/**/表示的注释是针对实例的。
代码在1-2.c
将代码写在红框中后,点击右下角的“运行测试数据”
右侧显示通过,说明我们的代码是没问题的
struct ListNode* rotateRight(struct ListNode* head, int k) {
struct ListNode* p1;
struct ListNode* p2;
struct ListNode* p3;
int len=1;
p1=head;//p1,p2,p3初始化都指向头结点
p2=head;
p3=head;
//判断如果链表为空或者链表只有一个节点,则直接返回
if(!head||!head->next)
return head;
while(p1->next)//该while循环用于计算链表长度,得到长度为len
{
len++;
p1=p1->next;
}
k=k%len; //k对len取模
//如果k为0,直接返回head,即旋转链表前后实际上是不变的
if(k==0)
return head;
//否则,首先计算出链表分界点所要走的步数
len=len-k-1;/*以1->2->3->4->5为例,经过计算后len=2*/
while(len--)/*while终止时,p2指向3*/
p2=p2->next;
//记录第一部分的头结点
p3=p2->next;/*p3指向4,此即新的第一个字符串的头结点*/
p1=p3;/*p1也指向4*/
//找出第一部分的尾节点
while(p1->next)/*while循环结束时,p1指向5,此即的第一个字符串的尾结点*/
p1=p1->next;
p2->next=NULL;/*让3指向null*/
p1->next=head;/*让5指向了head,实际上就将两个4->5和1->2->3->null连在了一起,从而实现了字符串的旋转*/
return p3;
}