这篇内容我将介绍几个关于链表的经典问题,很多都是来自互联网(相信绝大部分来自于经典书籍),特在本篇中收集和整理(持续更新,欢迎提供链表相关问题)。
以下问题,简单的我都会列出解题的大体思路,复杂点的我会详细的说明。
问题1:给定一个单向链表,设计一个算法,找到该链表的倒数第m个元素,当m=0时,返回链表最后一个元素。
解法一:
要遍历链表两次,第一次遍历链表取得长度n,然后计算要沿链表移动的步数s=n-m(当然这里假设n>m);第二次遍历链表拿到在s处的链表结点元素。
解法二:
要拿到倒数第m个元素,我们可以设计两个指针p1,p2,它们的在链表中的间距为m(这里间距是通过p1,p2相对于头结点的位序计算),同时移动两个指针,当后一个指针p2到链表末尾时,前一个指针p1就是要取的结点。
问题2:判断一个的单向链表是否有环。
解法:
最经典的解法就是快慢指针法,定义指针p,q,在链表中,p每次前进一步,q每次前进两步,如果p能和q重合,那么具有环。可以这么理解,在每次移动的过程中,可以看做p相对q是静止的,q每次移动一步,所以如果有环,那么q总能赶上p的。
下面我们给出上面问题的证明:
假设假设快慢指针的移动速度分别为vp,vq,并且环的长度为l,在某一时刻t,p和q指针距离环的入口结点相距分别为dp,dq,那么,假设在经过m次移动后,p和q指针重合,即有环,那么满足下面的等式:
dp+vp*m mod(l) = dq+vq*m mod(l);
-> (vp-vq)*m mod(l)=dq-dp
可进一步推导出 (vp-vq)*m - p*l = dq-dp (p为取模的商)
这里假设 A=vp-vq, B=-l,C=dq-dp
那么等式为 A*m+pB=C ,其中,A,B,C已知。这里对我们的问题来说,要求取一对数(m,p)使得可以取得m为一正整数即可,当然对于算法来说越小越好。其实,这里这个问题就是经典的拓展欧几里问题,即ax+by=c,若c mod gcd(a,b)=0,则一定存在解x,y满足等式。这里,我们证明的前提是vp=1,vq=2,则A=-1,B=-l,C=vp*t-vp*t,显然gcd(A,B)等于1,
则一定满足C mod gcd(a,b) = 0,所以一定存在解m,p使得等式满足,即当我们用步进分别为1和2的指针循环时,如果链表有环就一定会发生碰撞。关于这个拓展欧几里得算法的详细证明,请参考<MAT-欧几里得及拓展欧几里得算法>一篇。
这里我们给出算法的伪代码:
bool has_loop(head)
begin:
p=head; q=head;
while(q&&q->next){
p=p->next;
q=q->next->next;
if(p==q) reuturn true;
}
return false;
end;
问题3:将一个单向链表逆序
解法:
算法开始的条件应该至少有两个以上的结点数,需要用到三个指针,分别指向前驱结点,当前结点以及后继结点,操作看下面一组图:
1初始p=head->next; q=head->next->next ; t=NULL;
2.判断q是否到链表尾,如果是,结束循环,否则执行循环体,将t指向q的下个元素,同时将q的下个元素修改为前驱结点p,分别向前移动p,q指针,此时q,t指向同一个结点