一:求链表倒数第k个结点
题目描述:输入一个单向链表,输出该链表中倒数第k个结点,链表的倒数第0个结点为链表的尾指针(NULL)。
思路:设置两个指针p1,p2,首先p1和p2都指向head,然后p2向前走k步,这样p1和p2之间就间隔k个节点,最后p1和p2同时向前移动,直至p2走到链表末尾。
代码:
ListNode* fun(ListNode *head,int k)
{
ListNode *pone, *ptwo;
assert(k>= 0);
pone= ptwo = head;
for(; k > 0 && ptwo != NULL; k--)
{
ptwo=ptwo->next;
}
if(k > 0)return NULL; //k大于链表总长度
while(ptwo != NULL)
{
pone=pone->next;
ptwo=ptwo->next;
}
return pone;
}
二:编程判断两个链表是否相交(不包括环)
题目描述:给出两个单向链表的头指针(如下图所示),比如h1、h2,判断这两个链表是否相交。这里为了简化问题,先假设两个链表均不带环。
思路:
1:直接循环判断第一个链表的每个节点是否在第二个链表中。但这种方法的时间复杂度为O(Length(h1) * Length(h2))。显然,我们得找到一种更为有效的方法。
2:针对第一个链表直接构造hash表,然后查询hash表,判断第二个链表的每个结点是否在hash表出现,如第二个链表的结点能在hash表中找到,即说明第二个链表与第一个链表有相同的结点。时间复杂度为为线性:O(Length(h1) + Length(h2)),同时为了存储第一个链表的所有节点,空间复杂度为O(Length(h1))。是否还有更好的方法呢,既能够以线性时间复杂度解决问题,又能减少存储空间?
3:进一步考虑“如果两个没有环的链表相交于某一节点,那么在这个节点之后的所有节点都是两个链表共有的”这个特点,我们可以知道,如果它们相交,则最后一个节点一定是共有的。而我们很容易能得到链表的最后一个节点,所以这成了我们简化解法的一个主要突破口。那么,我们只要判断俩个链表的尾指针是否相等。相等,则链表相交;否则,链表不相交。
所以,先遍历第一个链表,记住最后一个节点。然后遍历第二个链表,到最后一个节点时和第一个链表的最后一个节点做比较,如果相同,则相交,否则,不相交。这样我们就得到了一个时间复杂度为O((Length(h1)+ Length(h2)),而且只用了一个额外的指针来存储最后一个节点。这个方法时间复杂度为线性O(N),空间复杂度为O(1)。
代码如下:
int isListCross(ListNode *h1, ListNode *h2)
{
ListNode*p = h1;
ListNode*q = h2;
while(p->next!= NULL)
{
p = p->next;
}
while(q->next != NULL)
{
q = q->next;
}
if(p== q)return TRUE;
return FALSE;
}
三:判断单链表是否带环
题目描述:给出单向链表的头指针,判断这个链表是否包括环。
思路:设置两个指针(fast, slow),初始值都指向头,slow每次前进一步,fast每次前进二步,如果链表存在环,则fast必定先进入环,而slow后进入环,两个指针必定相遇。(当然,fast先行头到尾部为NULL,则为无环链表)
代码:
int isListHasLoop(ListNode *head)
{
ListNode *fast, slow;
fast = slow = head;
while(fast !=NULL && fast->next != NULL)
{
fast = fast->next;
fast = fast->next;
slow = slow->next;
if(fast == slow)
{
break;
}
}
if(fast == NULL || fast->next ==NULL)
{
return FALSE;
}
return TRUE;
}
上述快慢指针的方法,也可以快速找到无环链表的中间节点,同样是快指针走两步,慢指针走一步,当快指针走到链表尾部的时候,慢指针正好走到了链表的中间。
四:找出带环链表的环入口点
题目描述:给出单向链表的头指针,判断这个链表是否包括环,如果包括环的话,找出环的入口点。
思路:首先判断链表是否包括环,如果包括环的话,则判断的同时,也找到了快慢指针的碰撞点,可以证明:碰撞点p到入口点的距离等于头指针到入口点的距离,因此:分别从碰撞点、头指针开始走,相遇的那个点就是环入口点。
证明:链表形状类似字母“b”。假设从链表头到入口点的长度为 a(结点个数),环长度为 b 。则总长度(也是总结点数)为 a+b 。
从头开始对结点进行编号,将第 i 步访问的结点编号用 S(i) 表示。i = 0, 1 ...
当 i<a 时,S(i)=i ;
当 i≥a 时,S(i)=a+(i-a)%b 。
分析追赶过程:两个指针分别前进,假定经过 x 步后,快慢指针碰撞。此时快指针经过了2x个结点,慢指针经过了x个结点,所以:S(x)=S(2x)。
因为碰撞点肯定在环内,所以,x≥a,因而:S(x) = a+(x-a)%b,S(2x) = a+(2x-a)%b。
a+(x-a)% b = a+(2x-a)%b --> x=tb 。
入口点为从起点走 a 步,即 S(a)。
从碰撞点开始走a步,S(x+a) = S(tb+a) = S(a)。
得到结论:从碰撞点、头指针开始走,相遇的那个点就是环入口点。
代码:
ListNode *LoopPoint(ListNode *head)
{
ListNode *fast, slow;
fast = slow = head;
while(fast != NULL &&fast->next != NULL)
{
fast = fast->next;
fast = fast->next;
slow = slow->next;
if(fast == slow)
{
break;
}
}
if(fast == NULL || fast->next ==NULL)
{
return NULL;
}
slow = head;
while(slow !=fast)
{
slow =slow->next;
fast =fast->next;
}
return fast;
}
五:考虑两个链表是否相交,考虑有环的情况
上面的链表相交问题都是针对链表无环的,那么如果链表是有环的呢?上面的方法就变的无效了。
如果链表有环的话,那么如果两个链表相交,则环一定是链表共有的部分。所以,如果考虑的链表存在有环的情况,那么这个判断两个链表是否相交的问题就转化成了:
1.先判断带不带环
2.如果都不带环,就判断尾节点是否相等
3.如果都带环,判断其中之一的碰撞点,经过几步后能否碰到另外一个碰撞点。如果能,则相交,如果不能,则不相交。
代码:
int isListHasLoop2(ListNode *head, ListNode **lastnode, ListNode **meet)
{
ListNode *fast, *slow;
*lastnode = NULL;
*meet = NULL;
fast = slow = head;
while(fast != NULL)
{
if(fast->next!= NULL)
{
fast= fast->next;
}
if(fast->next== NULL)
{
*lastnode= fast;
break;
}
fast = fast->next;
slow = slow->next;
if(fast == slow)
{
*meet = fast;
break;
}
}
if(fast == NULL || fast->next ==NULL)
{
return FALSE;
}
return TRUE;
}
int isListCross2(ListNode *h1, ListNode *h2)
{
ListNode *lastnode1, *lastnode2;
ListNode *m1, *m2;
ListNode *flag;
int hasloop1, hasloop2;
hasloop1 = isListHasLoop2(h1,&lastnode1, &m1);
hasloop2 = isListHasLoop2(h1,&lastnode2, &m2);
if(hasloop1 !=hasloop2)
{
returnFALSE;
}
if(hasloop1 == FALSE)
{
return lastnode1 == lastnode2
}
else
{
flag = m1;
for(;;)
{
if(m1== m2)return TRUE;
m1= m1->next;
if(m1== flag)
{
returnFALSE;
}
}
}
}
六:两个链表相交的第一个节点
思路:这个问题有几种特殊情况,需要分别考虑
如果链表有环,则计算出环的长度looplen,以及两个链表的总长度len1和len2。
特殊情况:
如果len1 (或len2)== looplen,表明链表1就是链表2的环,所以,链表2的入口点就是相交的第一个节点。
如果len1 = len2 = looplen,表明链表1和链表2都是环,完全重合,则任意结点都可是相交的第一个节点。
排除特殊情况后,则分别计算从链表头到环入口点的长度,如果链表没有环,则计算链表总长度。分别记为len1和len2
假设为len1较大,则对于链表1,先从h1开始走len1-len2步,然后,链表2和链表1在同时走,如果碰到结点相同的时刻,则便是两个链表相交的第一个节点。
七:机器人相遇问题
题目描述:在一条左右水平放置的直线轨道上任选两个点,放置两个机器人,请用如下指令系统为机器人设计控制程序,使这两个机器人能够在直线轨道上相遇。(注意两个机器人用你写的同一个程序来控制)
指令系统:只包含4条指令,向左、向右、条件判定、无条件跳转。其中向左(右)指令每次能控制机器人向左(右)移动一步;条件判定指令能对机器人所在的位置进行条件测试,测试结果是如果对方机器人曾经到过这里就返回true,否则返回false;无条件跳转,类似汇编里面的跳转,可以跳转到任何地方。
思路:两个机器人用你写的同一个程序来控制,也就是说机器人需要运行同样的代码。
首先题目要求很简单,就是要你想办法让A最终能赶上B,A在后,B在前,都向右移动,如果它们的速度永远一致,那A是永远无法追赶上B的。但题目给出了一个“条件判断指令”,即如果A或B某个机器人向前移动时,若是某个机器人经过的点是第二个机器人曾经经过的点,那么程序返回true。就是抓住这一点,A 到达曾经B经过的点后,发现此后的路是B此前经过的,那么A开始提速两倍,B一直保持原来的一倍速度不变,那样的话,A势必会追上B。
简单伪代码如下:
start:
if(at the position other robots have not reached)
move_right
else
move_right
move_right
goto start
有个细节又出现了,上面这个分支不一定能提速的。why?因为如果if条件花的时间很少,而move指令发的时间很大(实际很可能是这样),那么两个机器人的速度还是基本是一样的。那作如何修改呢?
start:
if(at the position other robots have not reached)
move_right
move_left
move_right
else
move_right
goto start
(http://blog.csdn.net/v_JULY_v/article/details/6447013)