接上贴http://lgsun592.iteye.com/admin/blogs/1066179 ,这也是其中的一道面试题
问题:一个链表可能包含环,如何检测并确定环的位置,如图:
方法有2:
1.记录法,通过某种方式把访问过的记录记录下来,然后访问下一个节点的时候查询下访问记录(我当时只想到了此方法,唉);
如果是外部标记的话,需要遍历1+2+...+(P+L-1)+P+(P+L)个节点,约O((max(P,L))^2),程序实现的就是此种方法
如果是内部标记的话,只需要遍历P+L个节点,速度最快的了
2.追赶法,类似在操场跑圈,两人同时起步,快的人和慢的人第一次相遇的时候,快的肯定比慢的多跑一圈,以此进行推算
第一次跑:两人同时从起点出发,第一个人P1速度为1,第二个人P2速度为2,相遇的时候P1跑的路程N=P+C,P2的路程=2N,可以推出此园的周长KL=P+C=N(K=1,2...)
第二次跑:P1的接上次位置,速度为1,P2从起点出发,速度为1,这次经过距离P后2人相遇,即在圆的起点相遇
推理:
KL = P + C
=>
KL - C = P
第二次相遇后,可以获得P的值,那么就可以确定此环形的起止位置了(程序实现的是K=1的情况)
废话不多说了,看代码吧
/**
* Class @CircleLinklist.java
* 环形链表检测
* @author lgsun
* Date: 2011-6-2
*/
package org.lgsun.linklist;
import java.text.MessageFormat;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
@SuppressWarnings("all")
public class CircleLinklist<E>
{
private static final int LENGTH = 16;
private Entry<E> header;
public static void main(String[] args)
{
CircleLinklist<String> circle = new CircleLinklist<String>();
circle.createLinkList();
circle.print();
circle.method1();
circle.method2();
}
/**
* 方法1
* 1.遍历链表
* 2.将访问过的节点的hashcode值保存起来
* 3.如果某节点的hashcode值已经存在,那么存在环
*/
public void method1()
{
int start = -1;
int curNum = 0;
Entry<E> curEntry = header;
List<Integer> codeList = new LinkedList<Integer>();
//遍历链表
while (curEntry.next != null)
{
if (codeList.size() == 0)
{
codeList.add(curEntry.hashCode());
}
else
{
//如果存在,则包含环
if (codeList.contains(curEntry.hashCode()))
{
start = codeList.indexOf(curEntry.hashCode());
break;
}
}
codeList.add(curEntry.hashCode());
curEntry = curEntry.next;
curNum++;
}
if (start != -1)
{
System.out.println(MessageFormat.format(
"方法1:此链表中包含环,从第{0}个节点到第{1}个节点。", start, curNum));
}
else
{
System.out.println("方法1:此链表中不包含环!");
}
}
/**
* 方法2
* 追赶法,数学啊,数学
*/
public void method2()
{
int start = 0;
int length = 0;
boolean isCircle = false;
Entry<E> firstEntry = header;
Entry<E> secondEntry = header;
//第一次,p1 速度为1,p2速度为2
while (secondEntry != null)
{
length++;
if (length > 1)
{
// 直接比较内存地址
if (firstEntry == secondEntry)
{
isCircle = true;
break;
}
}
if (firstEntry.next != null)
{
firstEntry = firstEntry.next;
}
else
{
break;
}
if (secondEntry.next != null)
{
secondEntry = secondEntry.next.next;
}
else
{
break;
}
}
if (isCircle)
{
secondEntry = header;
//第二次,p1速度为1,p2速度为1
for (int i = 0; i < length; i++)
{
start++;
// 直接比较内存地址
if (firstEntry == secondEntry)
{
// 从头到尾,交点总共算了3次,所以减2
length = length + start - 2;
break;
}
if (firstEntry.next != null)
{
firstEntry = firstEntry.next;
}
else
{
break;
}
if (secondEntry.next != null)
{
secondEntry = secondEntry.next;
}
else
{
break;
}
}
}
else
{
System.out.println("方法2:此链表中不包含环!");
}
System.out.println(MessageFormat.format(
"方法2:此链表中包含环,从第{0}个节点到第{1}个节点。", start, length));
}
/**
* 向链表中增加节点
*/
public void add(Entry<E> entry)
{
if (header == null)
{
header = entry;
}
else
{
entry.next = header;
header = entry;
}
}
/**
* 创建一个带环的链表
*/
public void createLinkList()
{
String disturb = "disturb";
Entry footer = null;
Entry node = new Entry<String>("node");
Random random = new Random();
for (int i = 0; i < LENGTH; i++)
{
// 在长度1/2处加入指定节点
if (i == (LENGTH / 2))
{
add(node);
}
else if (i == (LENGTH / 4) || i == (LENGTH * 3 / 4))
{
// 加入干扰节点
add(new Entry(disturb));
}
else
{
// 加入随机节点
String e = String.valueOf(random.nextInt(9999));
add(new Entry(e));
}
// 获取尾节点
if (i == 0)
{
footer = header;
}
}
// 给尾节点赋值为确定节点,产生环
footer.next = node;
}
/**
* 输出此链表
*/
public void print()
{
Entry<E> flag = header;
System.out.print("header:");
for (int i = 1; i < LENGTH + 4; i++)
{
if (i != 0)
{
System.out.print("=>");
}
System.out.print("["+i+"]"+flag.element.toString());
flag = flag.next;
}
System.out.println();
flag = header;
}
}
class Entry<E>
{
E element;
Entry<E> next;
Entry(E element)
{
this.element = element;
this.next = null;
}
}
不知道发此帖是对是错,发完上一帖已经有朋友M我说,他面试的时候也碰到的同样的题了,可见此公司的题库更新很慢吗 ,希望大家早日找到自己喜欢的工作哦。
我是在洗澡的时候完成的此题的编码构思,所以一不小心和地板来了个亲密接触
参考资料:http://blogold.chinaunix.net/u1/41845/showart_2019391.html