题目描述:有一个单链表,其中可能有一个环,也就是某个节点的next指向的是链表中在它之前的节点,这样在链表的尾部形成一环。问题:
1、如何判断一个链表是不是这类链表?
2、如果链表为存在环,如何找到环的入口点?
一、判断链表是否存在环
设置两个指针(fast, slow),初始值都指向头,slow每次前进一步,fast每次前进二步,如果链表存在环,则fast必定先进入环,而slow后进入环,两个指针必定相遇。(当然,fast先行头到尾部为NULL,则为无环链表)。
boolean isExsitLoop() {
2: Node<T> slow = head;
3: Node<T> fast = head;
4:
while (fast != null && fast.next != null) {
6: slow = slow.next;
7: fast = fast.next.next;
8:
if (slow == fast)
return true;
11: }
return false;
13: }
二、找到环的入口点
当fast若与slow相遇时,slow肯定没有走遍历完链表,而fast已经在环内循环了n圈(1<=n)。假设slow走了s步,则fast走了2s步(fast步数还等于s 加上在环上多转的n圈),设环长为r,则:
2s = s + nr
s= nr
设整个链表长L,入口环与相遇点距离为x,起点到环入口点的距离为a。
a + x = nr
a + x = (n – 1)r +r = (n-1)r + L - a
a = (n-1)r + (L – a – x)
(L – a – x)为相遇点到环入口点的距离,由此可知,从链表头到环入口点等于(n-1)循环内环+相遇点到环入口点。于是我们从链表头、与相遇点分别设一个指针,每次各走一步,两个指针必定相遇,且相遇第一点为环入口点。
1: Node<T> getLoopEntry() {
2: Node<T> slow = head;
3: Node<T> fast = head;
4:
while (fast != null && fast.next != null) {
6: slow = slow.next;
7: fast = fast.next.next;
8:
if (slow == fast)
break;
11: }
12:
if (fast == null || fast.next == null)
return null;
15:
// slow指向链表头节点
// fast指向slow和fast相遇的节点
18: slow = head;
19:
while (slow != fast) {
21: slow = slow.next;
22: fast = fast.next;
23: }
24:
return fast;
26: }
2、两个链表是否相交
题目描述:给出两个单向链表的头指针(如下图所示),
比如h1、h2,判断这两个链表是否相交。
直接循环判断第一个链表的每个节点是否在第二个链表中。但,这种方法的时间复杂度为O(Length(h1) * Length(h2))。显然,我们得找到一种更为有效的方法,至少不能是O(N^2)的复杂度。
- 针对第一个链表直接构造hash表,然后查询hash表,判断第二个链表的每个结点是否在hash表出现,如果所有的第二个链表的结点都能在hash表中找到,即说明第二个链表与第一个链表有相同的结点。时间复杂度为为线性:O(Length(h1) + Length(h2)),同时为了存储第一个链表的所有节点,空间复杂度为O(Length(h1))。是否还有更好的方法呢,既能够以线性时间复杂度解决问题,又能减少存储空间?
- 进一步考虑“如果两个没有环的链表相交于某一节点,那么在这个节点之后的所有节点都是两个链表共有的”这个特点,我们可以知道,如果它们相交,则最后一个节点一定是共有的。而我们很容易能得到链表的最后一个节点,所以这成了我们简化解法的一个主要突破口。那么,我们只要判断俩个链表的尾指针是否相等。相等,则链表相交;否则,链表不相交。
所以,先遍历第一个链表,记住最后一个节点。然后遍历第二个链表,到最后一个节点时和第一个链表的最后一个节点做比较,如果相同,则相交,否则,不相交。这样我们就得到了一个时间复杂度,它为O((Length(h1) + Length(h2)),而且只用了一个额外的指针来存储最后一个节点。这个方法时间复杂度为线性O(N),空间复杂度为O(1),显然比解法三更胜一筹。 - 上面的问题都是针对链表无环的,那么如果现在,链表是有环的呢?还能找到最后一个结点进行判断么?上面的方法还同样有效么?显然,这个问题的本质已经转化为判断链表是否有环。那么,如何来判断链表是否有环呢?
所以,事实上,这个判断两个链表是否相交的问题就转化成了:
1、先判断带不带环。
2、如果都不带环,就判断尾节点是否相等。
3、如果都带环,判断一链表上俩指针相遇的那个节点(问题1中的相遇节点),在不在另一条链表上。如果在,则相交,如果不在,则不相交。
这里为了简化问题,我们假设两个链表均不带环。
一、判断两链表链表是否有公共点
如果都不带环,就判断尾节点是否相等即可。如果都带环,判断一链表上俩指针相遇的那个节点,在不在另一条链表上。
boolean isCommonNode(LinkList<Integer> link, LinkList<Integer> link2) {
2: Node<Integer> last;
3: Node<Integer> last2;
// 两链表均无环
if (link.isExsitLoop() == false && link2.isExsitLoop() == false) {
// 获取最后一个节点
7: last = (Node<Integer>) link.getLastNode();
8: last2 = (Node<Integer>) link2.getLastNode();
+last2.value);
return last.value == last2.value;
11: }
//一个有环,一个无环
if(link.isExsitLoop()!=link2.isExsitLoop())
14: {
return false;
16: }
//两个都有环,判断环里的节点是否能到达另一个链表环里的节点
else
19: {
// 相遇节点
21: Node<Integer> meetNode = link.getMeetNode();
22: Node<Integer> meetNode2 = link2.getMeetNode();
23: Node<Integer> p = meetNode.next;
24:
// 环中遍历
while(p!=meetNode)
27: {
if(p == meetNode2)
return true;
30: p = p.next;
31: }
return false;
33: }
34: }
二、求两个链表相交的第一个节点
如果两个尾结点是一样的,说明它们有重合;否则两个链表没有公共的结点。 在上面的思路中,顺序遍历两个链表到尾结点的时候,我们不能保证在两个链表上同时到达尾结点。 这是因为两个链表不一定长度一样。但如果假设一个链表比另一个长L个结点,我们先在长的链表上遍历L个结点,之后再同步遍历,这个时候我们就能保证同时到达最后一个结点了。
由于两个链表从第一个公共结点开始到链表的尾结点,这一部分是重合的。因此,它们肯定也是同时到达第一公共结点的。于是在遍历中,第一个相同的结点就是第一个公共的结点。
static Node<Integer> getFirstCommonNode(LinkList<Integer> link, LinkList<Integer> link2)
2: {
3: Node<Integer> l = link.getHeadNode();
4: Node<Integer> s = link2.getHeadNode();
int len1 = link.getLength();
int len2 = link2.getLength();
int diff = len1 - len2;
8:
if(len1<len2)
10: {
11: l = link2.getHeadNode();
12: s = link.getHeadNode();
13: diff = len2 - len1;
14: }
15:
// 长的先走diff长度
int i = 0; i < diff; i++ )
18: l = l.next;
19:
while(l!=null&&s!=null&&l.value!=s.value)
21: {
22: l = l.next;
23: s = s.next;
24: }
25:
26: Node<Integer> comNode = null ;
if(l.value==s.value)
28: comNode = l;
return comNode;
30: }
具体代码为:
class Node<T> {
2: T value;
3: Node<T> next = null;
4:
boolean equals(Node<T> node) {
if (value.equals(node.value)) {
return true;
8: }
return false;
10: }
11:
int hashCode() {
return value.hashCode();
14: }
15:
public String toString() {
if (value == null)
;
return value.toString();
20: }
21: }
22:
class LinkList<T> {
private Node<T> head;
int len;
26:
public LinkList() {
28: initLinkList();
29: }
30:
void initLinkList() {
// 空链表
33: len = 0;
34:
35: head.next = null;
36: }
37:
// 链表插入操作
void insertAfterHead(Node<T> node) {
40: node.next = head.next;
41: head.next = node;
42: len++;
43: }
44:
void insertAtLast(Node<T> node) {
46: Node<T> p = getLastNode();
47:
// null
49: p.next = node;
50: len++;
51:
52: }
53:
// 产生环
void insertFormLoop(Node<T> node) {
56: Node<T> p = getLastNode();
57:
// 产生环
59: p.next = node;
60: len++;
61: }
62:
63: Node<T> getHeadNode() {
return head;
65: }
66:
67: Node<T> getLastNode() {
68: Node<T> p = head;
// 获取最后一个节点
while (p.next != null) {
71: p = p.next;
72: }
return p;
74: }
75:
int getLength() {
return len;
78: }
79:
// 遍历
void traversal() {
82: Node<T> node = head.next;
while (node != null) {
);
85: node = node.next;
86: }
87: System.out.println();
88: }
89:
/* http://www.cppblog.com/humanchao/archive/2008/04/17/47357.html
设置两个指针(fast, slow),初始值都指向头,slow每次前进一步,fast每次前进二步,如果链表存在环,则fast必定先进入环,而slow后进入环,两个指针必定相遇。
(当然,fast先行头到尾部为NULL,则为无环链表)*/
// 判断是否有环
boolean isExsitLoop() {
95: Node<T> slow = head;
96: Node<T> fast = head;
97:
while (fast != null && fast.next != null) {
99: slow = slow.next;
100: fast = fast.next.next;
101:
if (slow == fast)
return true;
104: }
return false;
106: }
107:
// 有环的情况下,获取相遇节点
109: Node<T> getMeetNode()
110: {
111: Node<T> slow = head;
112: Node<T> fast = head;
113:
while (fast != null && fast.next != null) {
115: slow = slow.next;
116: fast = fast.next.next;
117:
if (slow == fast)
return slow;
120: }
return null;
122: }
123:
// 找出环的入口
/*
* 当fast若与slow相遇时,slow肯定没有走遍历完链表,而fast已经在环内循环了n圈(1<=n)。假设slow走了s步,则fast走了2s步(fast步数还等于s 加上在环上多转的n圈),设环长为r,则:
2s = s + nr
s= nr
设整个链表长L,入口环与相遇点距离为x,起点到环入口点的距离为a。
a + x = nr
a + x = (n – 1)r +r = (n-1)r + L - a
a = (n-1)r + (L – a – x)
(L – a – x)为相遇点到环入口点的距离,由此可知,从链表头到环入口点等于(n-1)循环内环+相遇点到环入口点,于是我们从链表头、与相遇点分别设一个指针,每次各走一步,两个指针必定相遇,且相遇第一点为环入口点。
* */
135: Node<T> getLoopEntry() {
136: Node<T> slow = head;
137: Node<T> fast = head;
138:
while (fast != null && fast.next != null) {
140: slow = slow.next;
141: fast = fast.next.next;
142:
if (slow == fast)
break;
145: }
146:
if (fast == null || fast.next == null)
return null;
149:
// slow指向链表头节点
// fast指向slow和fast相遇的节点
152: slow = head;
153:
while (slow != fast) {
155: slow = slow.next;
156: fast = fast.next;
157: }
158:
return fast;
160: }
161:
162: }
163:
class LinkBasic {
// java内部类的初始化
class StaticInnerTest {
void innerClass() {
);
169: }
170: }
171:
/* http://blog.csdn.net/v_JULY_v/article/details/6447013
判断是否有公共点
1、两个链表都无环的情况:判断尾节点是否相等
2、如果都带环,判断一链表上俩指针相遇的那个节点,在不在另一条链表上。
*/
boolean isCommonNode(LinkList<Integer> link, LinkList<Integer> link2) {
178: Node<Integer> last;
179: Node<Integer> last2;
// 两链表均无环
if (link.isExsitLoop() == false && link2.isExsitLoop() == false) {
// 获取最后一个节点
183: last = (Node<Integer>) link.getLastNode();
184: last2 = (Node<Integer>) link2.getLastNode();
+last2.value);
return last.value == last2.value;
187: }
//一个有环,一个无环
if(link.isExsitLoop()!=link2.isExsitLoop())
190: {
return false;
192: }
//两个都有环,判断环里的节点是否能到达另一个链表环里的节点
else
195: {
// 相遇节点
197: Node<Integer> meetNode = link.getMeetNode();
198: Node<Integer> meetNode2 = link2.getMeetNode();
199: Node<Integer> p = meetNode.next;
200:
// 环中遍历
while(p!=meetNode)
203: {
if(p == meetNode2)
return true;
206: p = p.next;
207: }
return false;
209: }
210: }
211:
// 求两个链表相交的第一个节点
/*
* 思路:如果两个尾结点是一样的,说明它们有重合;否则两个链表没有公共的结点。
在上面的思路中,顺序遍历两个链表到尾结点的时候,我们不能保证在两个链表上同时到达尾结点。
这是因为两个链表不一定长度一样。但如果假设一个链表比另一个长L个结点,我们先在长的链表上遍历L个结点,
之后再同步遍历,这个时候我们就能保证同时到达最后一个结点了。
由于两个链表从第一个公共结点开始到链表的尾结点,这一部分是重合的。因此,它们肯定也是同时到达第一公共结点的。
于是在遍历中,第一个相同的结点就是第一个公共的结点。
* */
221:
static Node<Integer> getFirstCommonNode(LinkList<Integer> link, LinkList<Integer> link2)
223: {
224: Node<Integer> l = link.getHeadNode();
225: Node<Integer> s = link2.getHeadNode();
int len1 = link.getLength();
int len2 = link2.getLength();
int diff = len1 - len2;
229:
if(len1<len2)
231: {
232: l = link2.getHeadNode();
233: s = link.getHeadNode();
234: diff = len2 - len1;
235: }
236:
// 长的先走diff长度
int i = 0; i < diff; i++ )
239: l = l.next;
240:
while(l!=null&&s!=null&&l.value!=s.value)
242: {
243: l = l.next;
244: s = s.next;
245: }
246:
247: Node<Integer> comNode = null ;
if(l.value==s.value)
249: comNode = l;
return comNode;
251: }
252:
/**
* @param args
*/
void main(String[] args) {
// TODO Auto-generated method stub
new StaticInnerTest();
259: test.innerClass();
260:
/************************
* 形成初始单链表
* **********************/
new LinkList<Integer>();
new LinkList<Integer>();
int[] arr = { 1, 2, 3, 4, 5, 6 };
int[] arr2 = { 7, 1, 2, 3, 6, 8, 9, 10 };
int i = 0; i < arr.length; i++) {
new Node<Integer>();
270: node.value = arr[i];
271: link.insertAfterHead(node);
272: }
273:
int i = 0; i < arr2.length; i++) {
new Node<Integer>();
276: node.value = arr2[i];
277: link2.insertAfterHead(node);
278: }
279:
/************************
* 在链表尾部插入新的结点
* **********************/
new Node<Integer>();
284: node.value = 7;
285: link.insertAtLast(node);
286:
+ link.getLength());
+ link.getHeadNode().value);
);
290: link.traversal();
291:
+ link2.getLength());
);
294: link2.traversal();
295:
/************************
* 判断两个链表是否有公共节点
* **********************/
+isCommonNode(link,link2));
+getFirstCommonNode(link,link2).value);
301:
/************************
* 在链表尾部插入结点,形成环
*
* 即在链表的最后一个元素p后插入节点node,使node的next指针域指向p,形成环结构
* **********************/
new Node<Integer>();
308: node2.value = 19;
309: link.insertFormLoop(node2);
if (link.isExsitLoop()) {
);
+ link.getLoopEntry().value);
313: }
314:
315: }
316:
317: }
参考资料:
1、《编程之美》