前天晚上臨睡覺前看到了公衆號腳本之家推送的一篇文章,文章內容是一道算法題,並給出了思路解釋,但沒有具體源碼實現,這讓我以爲少了點什麼,因而,趁週末,我補齊了缺失的內容,好了,no code, no bb,咱們開始吧。java
題目描述:node
有一個單向鏈表,鏈表中有可能出現「環」,就像下圖這樣。那麼,如何用程序來判斷該鏈表是否爲有環鏈表呢?(圖片來自公衆號)git
方法1:github
從頭開始遍歷整個單鏈表,每遍歷一個新節點,就把它與以前遍歷過的節點進行比較,若是值相同,那麼就認爲這兩個節點是一個節點,則證實鏈表有環,中止遍歷,不然繼續遍歷下一個節點,重複剛纔的操做,直到遍歷結束。結合上圖來講,流程是這樣的:算法
① 獲得 "3"節點,把它與第一個節點 「5」比較,值不相等,繼續遍歷下一個節點 「7」。(從第二個節點開始遍歷)ide
② 獲得 「7」節點,把它依次與 「5」、「3」比較,值不相等,繼續遍歷下一個節點 「2」學習
③ 重複以上操做,直到遍歷完節點 「1」this
④ 獲得節點 「2」,把它依次與 「5」、「3」、「7」、「2」、「6」、「8」、「1」進行比較,當比較到節點 「2」時,值相等,遍歷結束,證實該鏈表有環。spa
假設鏈表的節點數量爲n,則該解法的時間複雜度爲O(n2),因爲沒有建立額外的存儲空間,因此空間複雜度爲O(1)3d
鏈表的實現比較簡單,我只寫了一個add方法,一個display方法:
1 //單向鏈表 2 public class SingleLinkedList { 3 private Node head;//標識頭節點 4 private int size;//標識鏈表中節點個數 5 6 public SingleLinkedList() { 7 this.size = 0; 8 this.head = null; 9 } 10 11 //node類 12 private class Node{ 13 private Object data;//每一個節點的數據 14 private Node next;//指向下一個節點的連接 15 16 public Node(Object data) { 17 this.data = data; 18 } 19 } 20 21 /** 22 * 將節點插入鏈表 23 * @param data 帶插入的值 24 */ 25 public void add(Object data) { 26 Node temp = head; 27 if (size == 0) { 28 head = new Node(data); 29 size++; 30 return; 31 } 32 while (temp.next != null) { 33 temp = temp.next; 34 } 35 temp.next = new Node(data); 36 size++; 37 } 38 39 /** 40 * 從頭開始遍歷節點 41 */ 42 public void display() { 43 if (size > 0) { 44 Node node = head; 45 if (size == 1) { 46 System.out.println("[" + node.data + "]"); 47 return; 48 } 49 while (node != null) { 50 System.out.println(node.data); 51 node = node.next; 52 } 53 } else { 54 System.out.println("[]"); 55 } 56 } 57 }
方法1以下:
1 /** 2 * 根據索引獲得鏈表的某個節點的值 3 * @param key 4 * @return 5 */ 6 public Object getNode(int key) { 7 8 if (key < 0 || key > size - 1) { 9 throw new ArrayIndexOutOfBoundsException("越界!"); 10 } else { 11 Node temp = head; 12 int count = 0; 13 while (temp != null) { 14 if (count == key) { 15 return temp.data; 16 } 17 temp = temp.next; 18 count++; 19 } 20 21 } 22 return null; 23 } 24 25 26 /** 27 * 從頭開始,依次與給定索引位置的節點的值進行比較,若是相同,則返回true,不然false 28 * @param key 29 * @return 30 */ 31 public boolean havaSameElement(int key) { 32 boolean flag = false; 33 int count = 0; 34 Node temp = head; 35 while (temp != null && count < key) { 36 if (temp.data == getNode(key)) { 37 flag = true; 38 return flag; 39 } 40 count++; 41 temp = temp.next; 42 } 43 return flag; 44 45 } 46 47 /** 48 * 方式1 49 * @return 50 */ 51 public boolean isAnnulate1() { 52 boolean flag = false; 53 for (int i = 1; i < size; i++) { 54 if (havaSameElement(i)) { 55 flag = true; 56 break; 57 } 58 } 59 return flag; 60 }
方法2:
這種方法用到了HashSet中add方法去重的特色,思路是這樣的:
① new一個HashSet,用來存儲以前遍歷過的節點值
②從頭節點head開始,依次遍歷鏈表中的節點,並把它add到集合中
③ 若是在集合中已經有一個相同的值,那麼會返回false,這樣便證實鏈表有環,退出遍歷
方法2以下:
1 /** 2 * 方式2 3 * @return 4 */ 5 public boolean isAnnulate2() { 6 boolean flag = false; 7 Set<Object> set = new HashSet<>(); 8 Node temp = head; 9 while (temp != null) { 10 if (!set.add(temp.data)) { 11 flag = true; 12 break; 13 } 14 temp = temp.next; 15 } 16 return flag; 17 18 }
方法3:
定義兩個指針tortoise與rabbit,讓它們一開始均指向head頭節點,以後,tortoise每次向後移動一個節點,rabbit每次向後移動2個節點,只要這個鏈表是有環的,它們一定會在某一次移動完後相遇,什麼?你問我爲何?咱們來思考這樣一個問題,兩我的在運動場跑步,他們的起始位置都是同樣的,當開跑後,只有在兩種狀況下,他們的位置會重合,第一就是他們的速度始終一致,第二就是跑得快的那我的套圈,以下圖所示:
咱們假設兩位跑步的同窗速度始終不變,即tortoise以V的速度進行移動,rabbit以2V的速度進行移動,在通過了相同的時間T後,他們相遇了,此時tortoise移動的距離爲VT,而rabbit移動的距離爲2VT,他們移動的距離差VT,即爲這個鏈表中 「環」的周長,如上圖所示,節點A表示爲環入口,節點B表示他們第一次相遇,咱們將head頭節點至節點A的距離記爲a,將節點A至他們第一次相遇的節點B的距離記爲b,經過咱們剛纔的分析,不可貴出,tortoise移動的距離VT = a + b,等量代換,他們移動的距離差也爲 a+ b,因此鏈表中環的周長爲 a + b,如今已知節點A至節點B的距離爲b,那麼節點B至節點A的距離便爲a,至此,發現什麼了?head頭節點到節點A的距離一樣爲a,咱們創建一個指針 start 指向頭節點,它與B節點處的tortoise同時以一個節點的速度進行移動,一段時間後,它們將在環入口相遇。咱們不光能證實一個鏈表是否有環,還能找到環的入口。
方法3以下:
1 public Node getIntersect() { 2 Node temp = head; 3 Node tortoise = temp; 4 Node rabbit = temp; 5 while (rabbit != null && rabbit.next != null) { 6 tortoise = tortoise.next; 7 rabbit = rabbit.next.next; 8 if (tortoise == rabbit) { 9 return tortoise; 10 } 11 } 12 return null; 13 } 14 15 public Object isAnnulate3() { 16 Node temp = head; 17 if (temp == null) { 18 return null; 19 } 20 Node intersect = getIntersect(); 21 if (intersect == null) { 22 return null; 23 } 24 Node startNode = head; 25 while (startNode != intersect) { 26 startNode = startNode.next; 27 intersect = intersect.next; 28 } 29 return startNode.data; 30 31 }
我要說明的是,方法3中的代碼只是 「僞代碼」,它並不能真的證實鏈表有環,並返回環的入口節點值。至於爲何,個人理解是,由於單鏈表的結構特色,它並不會真的存在 「環」,咱們這裏說的環只是把它抽象出來,實際上,單鏈表中一個節點只保留有對它後面那個節點的引用,並無對它前面節點的引用,因此,並不存在真正的 「環」,不過,這種思路仍是值得學習的。假設鏈表的節點數量爲n,則該算法的時間複雜度爲O(n),除指針外,沒有佔用任何額外的存儲空間,因此空間複雜度爲O(1)。
完整代碼以下:
1 package judgeLinkedListCircle; 2 3 import java.util.HashSet; 4 import java.util.Set; 5 6 7 /** 8 * 單向鏈表 9 * @author Cone 10 * @since 2019年7月27日 11 * 12 */ 13 public class SingleLinkedList { 14 private Node head;//標識頭節點 15 private int size;//標識鏈表中節點個數 16 17 public SingleLinkedList() { 18 this.size = 0; 19 this.head = null; 20 } 21 22 //node類 23 private class Node{ 24 private Object data;//每一個節點的數據 25 private Node next;//指向下一個節點的連接 26 27 public Node(Object data) { 28 this.data = data; 29 } 30 } 31 32 /** 33 * 將節點插入鏈表 34 * @param data 帶插入的值 35 */ 36 public void add(Object data) { 37 Node temp = head; 38 if (size == 0) { 39 head = new Node(data); 40 size++; 41 return; 42 } 43 while (temp.next != null) { 44 temp = temp.next; 45 } 46 temp.next = new Node(data); 47 size++; 48 } 49 50 /** 51 * 從頭開始遍歷節點 52 */ 53 public void display() { 54 if (size > 0) { 55 Node node = head; 56 if (size == 1) { 57 System.out.println("[" + node.data + "]"); 58 return; 59 } 60 while (node != null) { 61 System.out.println(node.data); 62 node = node.next; 63 } 64 } else { 65 System.out.println("[]"); 66 } 67 } 68 69 /** 70 * 根據索引獲得鏈表的某個節點的值 71 * @param key 72 * @return 73 */ 74 public Object getNode(int key) { 75 76 if (key < 0 || key > size - 1) { 77 throw new ArrayIndexOutOfBoundsException("越界!"); 78 } else { 79 Node temp = head; 80 int count = 0; 81 while (temp != null) { 82 if (count == key) { 83 return temp.data; 84 } 85 temp = temp.next; 86 count++; 87 } 88 89 } 90 return null; 91 } 92 93 94 /** 95 * 從頭開始,依次與給定索引位置的節點的值進行比較,若是相同,則返回true,不然false 96 * @param key 97 * @return 98 */ 99 public boolean havaSameElement(int key) { 100 boolean flag = false; 101 int count = 0; 102 Node temp = head; 103 while (temp != null && count < key) { 104 if (temp.data == getNode(key)) { 105 flag = true; 106 return flag; 107 } 108 count++; 109 temp = temp.next; 110 } 111 return flag; 112 113 } 114 115 /** 116 * 方式1 117 * @return 118 */ 119 public boolean isAnnulate1() { 120 boolean flag = false; 121 for (int i = 1; i < size; i++) { 122 if (havaSameElement(i)) { 123 flag = true; 124 break; 125 } 126 } 127 return flag; 128 } 129 130 131 /** 132 * 方式2 133 * @return 134 */ 135 public boolean isAnnulate2() { 136 boolean flag = false; 137 Set<Object> set = new HashSet<>(); 138 Node temp = head; 139 while (temp != null) { 140 if (!set.add(temp.data)) { 141 flag = true; 142 break; 143 } 144 temp = temp.next; 145 } 146 return flag; 147 148 } 149 150 public Node getIntersect() { 151 Node temp = head; 152 Node tortoise = temp; 153 Node rabbit = temp; 154 while (rabbit != null && rabbit.next != null) { 155 tortoise = tortoise.next; 156 rabbit = rabbit.next.next; 157 if (tortoise == rabbit) { 158 return tortoise; 159 } 160 } 161 return null; 162 } 163 164 /** 165 * 方式3 166 * @return 167 */ 168 public Object isAnnulate3() { 169 Node temp = head; 170 if (temp == null) { 171 return null; 172 } 173 Node intersect = getIntersect(); 174 if (intersect == null) { 175 return null; 176 } 177 Node startNode = head; 178 while (startNode != intersect) { 179 startNode = startNode.next; 180 intersect = intersect.next; 181 } 182 return startNode.data; 183 184 } 185 186 }
若有錯誤,歡迎指正。
代碼已上傳至github: