每日一道Leetcode演算法——如何判斷一個連結串列是否有環,並求出環的入口和環的長度——
阿新 • • 發佈:2019-01-14
判斷一個連結串列是否有環有兩種辦法:
一種最經典是定義兩個指標,一個指標每次向前走一步,一個指標每次向前走兩步,如果兩個指標最終重合。則證明有環。
一種是建立一個hash表,將每次走過的結點放入hash表中,如果結點在hash表中,則表示存在環。
判斷連結串列的入口:
假設兩個指標第一次相遇點為m,此時令一個指標從頭結點向下走,每次走一步,令另一個指標從相遇點往下走,每次走一步,兩個指標相遇的位置,為入口處。
判斷環的長度:
設定一個指標q指向環的入口,讓q往後移動直到q再次等於環的入口結點,此時q所走的步數就是環的長度。
package cn.leetcode.linkedlist; import cn.kimtian.linkedlist.Node; import java.util.HashMap; /** * 1.如何判斷一個連結串列是否有環 * 2.如果有環,找到環入口 * 3.如果有環,求環的長度 * * @author kimtian */ public class LinkLoop { public static void main(String[] args) { //建立一個連結串列,為A->B->C->D->E->B Node nA = new Node(1); Node nB = new Node(2); Node nC = new Node(3); Node nD = new Node(4); Node nE = new Node(5); nA.next = nB; nB.next = nC; nC.next = nD; nD.next = nE; //這句使其成為一個帶環的連結串列 nE.next = nB; System.out.println(hasLoop(nA)); System.out.println(searchEntranceNode(nA).getData()); System.out.println(circleLength(nA)); } /** * 方法一:建立兩個指標,一個走一步,一個走兩步,如果兩個指標最後重合,說明這個連結串列有環 * * @param n 連結串列頭結點 * @return 是否有環 */ public static boolean hasLoop(Node n) { //單鏈表為空時,單鏈表沒有環 if (n == null) { return false; } //單鏈表中只有頭結點,而且頭結點的next為空,單鏈表沒有環 if (n.next == null) { return false; } //定義兩個指標tmp1,tmp2,一個走一步,一個走兩部 Node tmp1 = n.next; Node tmp2 = n.next.next; while (tmp2 != null) { if (tmp1.getData() == tmp2.getData()) { //如果兩個指標最後重逢,說明存在環,否則不存在 return true; } //每次迭代指標一走一步,指標二走兩步 tmp1 = tmp1.next; //如果下一個都為空了,就不要找下下一個的值了,防止空指標異常 if (tmp2.next != null) { tmp2 = tmp2.next.next; } //沒有下一個指向了,說明不是環 else { return false; } } return false; } /** * 方法二:將每次走過的結點儲存到hash表中,如果結點在hash表中,則表示存在環 * * @param n 連結串列頭結點 * @return 是否有環 */ public static boolean hasLoop2(Node n) { Node temp1 = n; HashMap<Node, Node> ns = new HashMap<Node, Node>(); while (n != null) { if (ns.get(temp1) != null) { return true; } else { ns.put(temp1, temp1); } temp1 = temp1.next; if (temp1 == null) { return false; } } return true; } /** * 如果有環,求環的入口結點 * * @param n 連結串列頭結點 * @return 入口結點 */ public static Node searchEntranceNode(Node n) { //單鏈表為空時,單鏈表沒有環 if (n == null) { return null; } //單鏈表中只有頭結點,而且頭結點的next為空,單鏈表沒有環 if (n.next == null) { return null; } //定義兩個指標tmp1,tmp2,一個走一步,一個走兩部 Node tmp1 = n.next; Node tmp2 = n.next.next; while (tmp2 != null) { if (tmp1.getData() == tmp2.getData()) { //如果兩個指標最後重逢,說明存在環,否則不存在 break; } //每次迭代指標一走一步,指標二走兩步 tmp1 = tmp1.next; //如果下一個都為空了,就不要找下下一個的值了,防止空指標異常 if (tmp2.next != null) { tmp2 = tmp2.next.next; } //沒有下一個指向了,說明不是環 else { return null; } } //這裡直接等於頭結點,沒有向下走一步,因為頭結點有可能就是環的入口結點 tmp2 = n; //假設兩個指標第一次相遇點為m,此時令一個指標從頭結點向下走,每次走一步,令另一個指標從相遇點往下走,每次走一步,兩個指標相遇的位置,為入口處 while (tmp2 != null) { if (tmp1.getData() == tmp2.getData()) { return tmp1; } tmp1 = tmp1.next; tmp2 = tmp2.next; } return null; } /** * 如果有環,求環的長度 * 設定一個指標q指向環的入口,讓q往後移動直到q再次等於環的入口結點,此時q所走的步數就是環的長度 * * @param n 連結串列頭結點 * @return 入口結點 */ public static int circleLength(Node n) { Node p = searchEntranceNode(n); //不存在環時,返回0 if (p == null) { return 0; } Node q = p.next; int length = 1; while (p != q) { length++; q = q.next; } //返回環的長度 return length; } }