5 面试中的算法
有环链表的判断
有一个单向链表,链表中有可能出现“环”,就像下图这样。

使用程序判断该链表是否为有环链表。
方法: 首先创建两个指针 p1 和 p2,让他们同时指向这个链表的头节点。然后开始一个大循环,让 p1 每次向后移动 1 个节点,p2 每次向后移动 2 个节点,然后比较两个指针指向的节点是否相同。如果相同,则可以判断出链表有环,如果不同,则继续下一次循环。


学过小学奥数的读者,一定听说过数学上的追及问题。此方法就类似于一个追及问题。在一个环形跑道上,两个运动员从同一地点起跑,一个运动员速度快,另一个运动员速度慢。当两人跑了一段时间后,速度快的运动员必然会再次追上并超过速度慢的运动员,原因很简单,因为跑道是环形的。假设链表的节点数量为n,则该算法的时间复杂度为O(n)。除两个指针外,没有使用任何额外的存储空间,所以空间复杂度是O(1)。
原书的 java 代码:

改写为 python 代码实现:

扩展: 如果链表有环,如何求出环的长度? 当两个指针首次相遇,证明链表有环的时候,让两个指针从相遇点继续循环前进,并统计出前进的循环次数,知道两个指针第二次相遇,此时,统计出来的前进次数就是环长。
因为指针p1每次走1步,指针p2每次走2步,两者的速度差是1步。当两个指针再次相遇时,p2比p1多走了整整1圈。
因此,环长=每一次速度差×前进次数=前进次数。
按照这个思路自己写了一个 python 的实现:

如果链表有环,如何求出入环节点? 注意:入环点不等于首次相遇点。
在求入环节点的时候可以做一个抽象的判断如下:

上图是对有环链表所做的一个抽象示意图。假设从链表头节点到入环点的距离是D,从入环点到两个指针首次相遇点的距离是S1,从首次相遇点回到入环点的距离是S2。那么,当两个指针首次相遇时,各自所走的距离是多少呢?
指针p1一次只走1步,所走的距离是D+S1。指针p2一次走2步,多走了n(n>=1)整圈,所走的距离是D+S1+n(S1+S2)。由于p2的速度是p1的2倍,所以所走距离也是p1的2倍,因此:2(D+S1)=D+S1+n(S1+S2)
等式经过整理得出:D=(n1)(S1+S2)+S2也就是说,从链表头结点到入环点的距离,等于从首次相遇点绕环n1圈再回到入环点的距离。这样一来,只要把其中一个指针放回到头节点位置,另一个指针保持在首次相遇点,两个指针都是每次向前走1步。那么,它们最终相遇的节点,就是入环节点。
画个图:

Python 代码实现:

有环链表判断的完整代码: gist.github.com/furuiyang07…
栈
面试题目: 实现一个栈,该栈带有出栈(pop)、入栈(push)、取最小元素(getMin)3个方法。要保证这3个方法的时间复杂度都是O(1)。
解法方案:
- 设原有的栈为 A,此时额外创建一个备胎栈 B,用于辅助 A。
- .当第1个元素进入栈A时,让新元素也进入栈B。这个唯一的元素是栈A的当前最小值。
- 之后,每当新元素进入栈A时,比较新元素和栈A当前最小值的大小,如果小于栈A当前最小值,则让新元素进入栈B,此时栈B的栈顶元素就是栈A当前最小值。
- .每当栈A有元素出栈时,如果出栈元素是栈A当前最小值,则让栈B的栈顶元素也出栈。此时栈B余下的栈顶元素所指向的,是栈A当中原本第2小的元素,代替刚才的出栈元素成为栈A的当前最小值。(备胎转正)
- 当调用getMin方法时,返回栈B的栈顶所存储的值,这也是栈A的最小值
使用 Python 实现该思路如下:

测试:
