【題目】node
給定兩個有序鏈表的頭指針head1和head2,打印兩個鏈表的公共部分。數組
【分析】數據結構
假設有以下兩個有序鏈表dom
整個流程是這樣的:誰小動誰,一開始1<2,因此head1來到3的位置函數
此時2<3,因此head2來到3的位置oop
head1=head2,打印3,而且head1和head2共同往下走一步this
重複上述步驟,直到一個指針來到終點位置,整個流程就中止。spa
總結:誰小誰就往右移動,若是相等,打印該數,並共同往右移動;head1和head2只要有1個來到終點位置,整個流程就中止。3d
【代碼實現】指針
public class PrintCommonPart { public static class Node { public int value; public Node next; public Node(int data) { this.value = data; } } public static void printCommonPart(Node head1, Node head2) { System.out.print("Common Part: "); while (head1 != null && head2 != null) { if (head1.value < head2.value) { head1 = head1.next; } else if (head1.value > head2.value) { head2 = head2.next; } else { System.out.print(head1.value + " "); head1 = head1.next; head2 = head2.next; } } System.out.println(); } public static void printLinkedList(Node node) { System.out.print("Linked List: "); while (node != null) { System.out.print(node.value + " "); node = node.next; } System.out.println(); } public static void main(String[] args) { Node node1 = new Node(2); node1.next = new Node(3); node1.next.next = new Node(5); node1.next.next.next = new Node(6); Node node2 = new Node(1); node2.next = new Node(2); node2.next.next = new Node(5); node2.next.next.next = new Node(7); node2.next.next.next.next = new Node(8); printLinkedList(node1); printLinkedList(node2); printCommonPart(node1, node2); } }
【題目】
給定一個鏈表的頭節點head,請判斷該鏈表是否爲迴文結構。 例如: 1->2->1,返回true。 1->2->2->1,返回true。15->6->15,返回true。 1->2->3,返回false。
【方法一】——額外空間複雜度O(N)
假設有以下鏈表
每遍歷一個數都往棧壓入相應的數
由於棧是先進後出的,因此從棧頂到棧底其實就是原來鏈表順序的逆序。
而後再從頭開始遍歷鏈表,每遍歷一個數,都從棧中取一個數出來比較,至關於原始順序跟逆序依次比較,若是比到最後每一步都相等,則該鏈表是迴文的;若是其中有任何一步不相等,就不是迴文。
這個由於要準備一個棧,因此額外空間是O(N)。
public class IsPalindromeList { public static class Node { public int value; public Node next; public Node(int data) { this.value = data; } } /** * 須要N的額外空間,空間複雜度爲O(N) * @param head * @return */ public static boolean isPalindrome1(Node head) { Stack<Node> stack = new Stack<>(); Node cur = head; while (cur != null) { stack.push(cur); cur = cur.next; } while (head != null) { if (head.value != stack.pop().value) { return false; } head = head.next; } return true; }
public static void printLinkedList(Node node) { System.out.print("Linked List: "); while (node != null) { System.out.print(node.value + " "); node = node.next; } System.out.println(); } public static void main(String[] args) { Node head = null; printLinkedList(head); System.out.print(isPalindrome1(head) + " | "); printLinkedList(head); System.out.println("========================="); head = new Node(1); printLinkedList(head); System.out.print(isPalindrome1(head) + " | "); printLinkedList(head); System.out.println("========================="); head = new Node(1); head.next = new Node(2); printLinkedList(head); System.out.print(isPalindrome1(head) + " | "); printLinkedList(head); System.out.println("========================="); head = new Node(1); head.next = new Node(2); head.next.next = new Node(3); printLinkedList(head); System.out.print(isPalindrome1(head) + " | "); printLinkedList(head); System.out.println("========================="); head = new Node(1); head.next = new Node(2); head.next.next = new Node(2); head.next.next.next = new Node(1); printLinkedList(head); System.out.print(isPalindrome1(head) + " | "); printLinkedList(head); System.out.println("========================="); } }
【方法二】——額外空間複雜度O(N)
設置兩個指針,一個快指針(一次走2步),一個慢指針(一次走1步)。
快指針走完的時候,慢指針會來到中點的位置;慢指針來到中點位置後,將右半部分壓到棧裏
快指針走完的時候,慢指針來到3的位置,而後將後面的部分(2,1)壓到棧裏。
而後前面的部分(1,2)和棧中元素比較,若是每一步都相等,就是迴文。
這個方法的實質就是:假設有一段線,從中點開始,讓右半部分折回去,而後每一個數再比較,若是每一步都相等,就是迴文。
用這個方法的好處是:棧會少一半空間,雖然額外空間仍是O(N),但實際的結果是省了一半的空間
public class IsPalindromeList { public static class Node { public int value; public Node next; public Node(int data) { this.value = data; } }/** * 須要n/2的額外空間,可是空間複雜度還是O(N) * @param head * @return */ public static boolean isPalindrome2(Node head) { if (head == null || head.next == null) { return true; } //慢指針——指向第2個數 Node slow = head.next; //快指針——指向第1個數 Node fast = head; while (fast.next != null && fast.next.next != null) { //慢指針一次走一步 slow = slow.next; //快指針一次走兩步 fast = fast.next.next; } // 上面過程結束以後,slow就是指向中點的後一位.若是是偶數個數的話,那麼就是指向後半段的第一個位置 Stack<Node> stack = new Stack<>(); while (slow != null) { stack.push(slow); slow = slow.next; } while (!stack.isEmpty()) { if (head.value != stack.pop().value) { return false; } head = head.next; } return true; } public static void printLinkedList(Node node) { System.out.print("Linked List: "); while (node != null) { System.out.print(node.value + " "); node = node.next; } System.out.println(); } public static void main(String[] args) { Node head = null; printLinkedList(head); System.out.print(isPalindrome2(head) + " | "); printLinkedList(head); System.out.println("========================="); head = new Node(1); printLinkedList(head); System.out.print(isPalindrome2(head) + " | "); printLinkedList(head); System.out.println("========================="); head = new Node(1); head.next = new Node(2); printLinkedList(head); System.out.print(isPalindrome2(head) + " | "); printLinkedList(head); System.out.println("========================="); head = new Node(1); head.next = new Node(2); head.next.next = new Node(3); printLinkedList(head); System.out.print(isPalindrome2(head) + " | "); printLinkedList(head); System.out.println("========================="); head = new Node(1); head.next = new Node(2); head.next.next = new Node(2); head.next.next.next = new Node(1); printLinkedList(head); System.out.print(isPalindrome2(head) + " | "); printLinkedList(head); System.out.println("========================="); } }
【方法三】——額外空間複雜度O(1)
設置兩個指針,一個快指針(一次走2步),一個慢指針(一次走1步)
快指針走完的時候,慢指針會來到中點的位置。從中點位置開始,讓右半部分逆序。
而後分別從兩邊的1開始遍歷,往中間逼近,中途有任何一個對不上,就不是迴文,若是所有都能對的上,就是迴文。
可是不論是不是迴文,你在返回結果前,都要把後半部分調回原來的樣子。
public class IsPalindromeList { public static class Node { public int value; public Node next; public Node(int data) { this.value = data; } }/** * 完全不用額外空間 * @param head * @return */ public static boolean isPalindrome3(Node head) { if (head == null || head.next == null) { return true; } //慢指針 Node slow = head; //快指針 Node fast = head; /* 快指針一次走兩步,慢指針一次走一步 當快指針走完後,慢指針來到中間位置 */ while (fast.next != null && fast.next.next != null) { slow = slow.next; fast = fast.next.next; } //右半部分的第一個元素 fast = slow.next; slow.next = null; Node n3 = null; /* 後半部分逆序 */ while (fast != null) { n3 = fast.next; fast.next = slow; slow = fast; fast = n3; } n3 = slow; fast = head; boolean res = true; //檢查每一步的數是否相等 while (slow != null && fast != null) { if (slow.value != fast.value) { res = false; break; } slow = slow.next; fast = fast.next; } slow = n3.next; n3.next = null; //返回結果以前,將後半部分逆序的部分調回來 while (slow != null) { fast = slow.next; slow.next = n3; n3 = slow; slow = fast; } return res; }
public static void printLinkedList(Node node) { System.out.print("Linked List: "); while (node != null) { System.out.print(node.value + " "); node = node.next; } System.out.println(); } public static void main(String[] args) { Node head = null; printLinkedList(head); System.out.print(isPalindrome3(head) + " | "); printLinkedList(head); System.out.println("========================="); head = new Node(1); printLinkedList(head); System.out.print(isPalindrome3(head) + " | "); printLinkedList(head); System.out.println("========================="); head = new Node(1); head.next = new Node(2); printLinkedList(head); System.out.print(isPalindrome3(head) + " | "); printLinkedList(head); System.out.println("========================="); head = new Node(1); head.next = new Node(2); head.next.next = new Node(3); printLinkedList(head); System.out.print(isPalindrome3(head) + " | "); printLinkedList(head); System.out.println("========================="); head = new Node(1); head.next = new Node(2); head.next.next = new Node(2); head.next.next.next = new Node(1); printLinkedList(head); System.out.print(isPalindrome3(head) + " | "); printLinkedList(head); System.out.println("========================="); } }
【題目】
給定一個單向鏈表的頭節點head,節點的值類型是整型,再給定一個整 數pivot。實現一個調整鏈表的函數,將鏈表調整爲左部分都是值小於 pivot的節點,中間部分都是值等於pivot的節點,右部分都是值大於 pivot的節點。除這個要求外,對調整後的節點順序沒有更多的要求。 例如:鏈表9->0->4->5->1,pivot=3。 調整後鏈表能夠是1->0->4->9->5,也能夠是0->1->9->5->4。總之,滿 足左部分都是小於3的節點,中間部分都是等於3的節點(本例中這個部分爲空),右部分都是大於3的節點便可。對某部份內部的節點順序不作要求。
【分析】
最簡單的作法:
把鏈表的每個位置放在一個容器裏,即生成一個Node類型的數組,而後在數組裏面對Node的值進行劃分:小於指定值的放左邊,等於的放中間,大於的放右邊。而後從容器開始從新鏈接這個鏈表,連完以後返回便可。此時的額外空間複雜度爲O(N)
public class SmallerEqualBigger { public static class Node { public int value; public Node next; public Node(int data) { this.value = data; } } public static Node listPartition1(Node head, int pivot) { if (head == null) { return head; } Node cur = head; int i = 0; while (cur != null) { i++; cur = cur.next; } Node[] nodeArr = new Node[i]; i = 0; cur = head; //將原鏈表的每一個Node節點放在數組裏 for (i = 0; i != nodeArr.length; i++) { nodeArr[i] = cur; cur = cur.next; } arrPartition(nodeArr, pivot); //從數組容器裏從新鏈接成鏈表結構 for (i = 1; i != nodeArr.length; i++) { nodeArr[i - 1].next = nodeArr[i]; } nodeArr[i - 1].next = null; return nodeArr[0]; } /** * 等同於荷蘭國旗問題 * @param nodeArr * @param pivot */ public static void arrPartition(Node[] nodeArr, int pivot) { int small = -1; int big = nodeArr.length; int index = 0; while (index != big) { if (nodeArr[index].value < pivot) { swap(nodeArr, ++small, index++); } else if (nodeArr[index].value == pivot) { index++; } else { swap(nodeArr, --big, index); } } } public static void swap(Node[] nodeArr, int a, int b) { Node tmp = nodeArr[a]; nodeArr[a] = nodeArr[b]; nodeArr[b] = tmp; } public static void printLinkedList(Node node) { System.out.print("Linked List: "); while (node != null) { System.out.print(node.value + " "); node = node.next; } System.out.println(); } public static void main(String[] args) { Node head1 = new Node(7); head1.next = new Node(9); head1.next.next = new Node(1); head1.next.next.next = new Node(8); head1.next.next.next.next = new Node(5); head1.next.next.next.next.next = new Node(2); head1.next.next.next.next.next.next = new Node(5); printLinkedList(head1); head1 = listPartition1(head1, 4); printLinkedList(head1); } }
【進階】—— 在原問題的要求之上再增長以下兩個要求。
在左、中、右三個部分的內部也作順序要求,要求每部分裏的節點從左 到右的順序與原鏈表中節點的前後次序一致。 例如:鏈表9->0->4->5->1,pivot=3。調整後的鏈表是0->1->9->4->5。 在知足原問題要求的同時,左部分節點從左到右爲0、1。在原鏈表中也 是先出現0,後出現1;中間部分在本例中爲空,再也不討論;右部分節點 從左到右爲九、四、5。在原鏈表中也是先出現9,而後出現4,最後出現5。若是鏈表長度爲N,時間複雜度請達到O(N),額外空間複雜度請達到O(1)。
【分析】
假設有以下鏈表,按照4來劃分,設置3個引用(small、equal、big)。
遍歷一次鏈表,找到第一個小於4的節點,找到第一個等於4的節點,找到第一個大於4的節點.。
再遍歷一遍鏈表,若是找到的是某個區域的頭節點,就省略,好比說再遍歷的時候找到的是7,就省略,由於這個7和big以前找到的節點是同一個(注意這裏比較的是Node的內存地址,而不是Node的值)。遍歷到6的時候,6>4,就往大於的區域裏放,依次下去。而後再將3個區域鏈接起來便可
public class SmallerEqualBigger { public static class Node { public int value; public Node next; public Node(int data) { this.value = data; } }/** * 不須要額外的空間複雜度,且能達到穩定性 * @param head * @param pivot * @return */ public static Node listPartition2(Node head, int pivot) { if (head == null) { return null; } //小於部分鏈表的head和tail Node sH = null, sT = null; //等於部分鏈表的head和tail Node eH = null, eT = null; //大於部分鏈表的head和tail Node bH = null, bT = null; //用來保存下一個結點 Node next = null; //劃分到三個不一樣的鏈表 while (head != null) { next = head.next; //爲了鏈表拼接後,最後一個就不用再去賦值其next域爲null了 head.next = null; //向small部分 分佈 if (head.value < pivot) { //small部分的第一個結點 if (sH == null) { sH = head; sT = head; } else { //把head放到small最後一個 sT.next = head; //更新small部分的sT sT = head; } } else if (head.value == pivot) { if (eH == null) { eH = head; eT = head; } else { eT.next = head; eT = head; } } else { if (bH == null) { bH = head; bT = head; } else { bT.next = head; bT = next; } } head = next; } ///將三個鏈表合併(注意邊界的判斷) if (sT != null) { //合併small和equal部分 sT.next = eH; eT = eT == null ? sT : eT; } if (eT != null) { eT.next = bH; } return sH != null ? sH : eH != null ? eH : bH; }
public static void printLinkedList(Node node) { System.out.print("Linked List: "); while (node != null) { System.out.print(node.value + " "); node = node.next; } System.out.println(); } public static void main(String[] args) { Node head1 = new Node(7); head1.next = new Node(3); head1.next.next = new Node(4); head1.next.next.next = new Node(6); head1.next.next.next.next = new Node(0); head1.next.next.next.next.next = new Node(4); printLinkedList(head1); head1 = listPartition2(head1, 4); printLinkedList(head1); } }
【題目】
一種特殊的鏈表節點類描述以下:
public class Node { public int value; public Node next; public Node rand; public Node(int data) { this.value = data; } }
Node類中的value是節點值,next指針和正常單鏈表中next指針的意義一 樣,都指向下一個節點,rand指針是Node類中新增的指針,這個指針可能指向鏈表中的任意一個節點,也可能指向null。 給定一個由Node節點類型組成的無環單鏈表的頭節點head,請實現一個函數完成這個鏈表中全部結構的複製,並返回複製的新鏈表的頭節點。
【分析】
這題的題意是,假設有下面鏈表,節點1的next指向2,節點2的next指向3,節點3的next指向null。另外,假設節點1的rand指針指向節點3,節點2的rand指針指向節點1,節點3的rand指針指向null。
須要作的是:複製該鏈表的全部結構(深度拷貝),最後返回1'節點
咱們能夠用HashMap來實現,具體作法是:
從節點1開始依次遍歷到節點3,每遍歷一個節點,就克隆出一個新的節點,並把這2個節點放在HashMap中,key是原鏈表節點,value是新鏈表節點。
怎麼實現克隆呢?若是一個節點是node,克隆後的節點爲newNode,能夠將node節點傳入newNode節點中:Node newNode=new Node(node.value);
可是如今對於newNode來講,next指針和rand指針都是null。怎麼辦呢?咱們能夠在hash表裏記一個記錄,例如key就是節點1,value就是節點1’。
再次遍歷原始鏈表,當我獲得節點1的時候,在map中能夠把節點1‘取出;由於節點1的next指針是指向節點2的,因此拷貝後的節點1‘的next指針也應該指向節點2’。而在map中,咱們能夠經過節點2找到節點2‘,因此就能夠將1’的next指針指向2‘;咱們還能夠經過節點1的rand指針找到節點3,節點1’的rand指針也應該指向3'節點,其中3‘節點能夠在map中找到。這是在遍歷節點1的時候,1’的指針是怎麼設置的。按照此方式設置2‘和3’的指針。
public class CopyListWithRandom { public static class Node { public int value; public Node next; public Node rand; public Node(int data) { this.value = data; } } /** * 利用HashMap,key存的是原鏈表的節點,value存的是該節點的克隆節點 * @param head * @return */ public static Node copyListWithRand1(Node head) { HashMap<Node, Node> map = new HashMap<>(); Node cur = head; while (cur != null) { //map的value裏面是純淨的節點,開始是沒有next跟rand的指向,須要本身去指定 map.put(cur, new Node(cur.value)); cur = cur.next; } Node X = head; //如下while跑完後,克隆節點之間的結構就設置完畢了 while (X != null) { // map.get(x)是x的拷貝節點x' map.get(X).next = map.get(X.next); map.get(X).rand = map.get(X.rand); X = X.next; } return map.get(head); } public static void printRandLinkedList(Node head) { Node cur = head; System.out.print("Order: "); while (cur != null) { System.out.print(cur.value + " "); cur = cur.next; } System.out.println(); cur = head; System.out.print("rand: "); while (cur != null) { System.out.print(cur.rand == null ? "- " : cur.rand.value + " "); cur = cur.next; } System.out.println(); } public static void main(String[] args) { Node head = null; Node res = null; printRandLinkedList(head); res = copyListWithRand1(head); printRandLinkedList(res); printRandLinkedList(head); System.out.println("========================="); head = new Node(1); head.next = new Node(2); head.next.next = new Node(3); head.next.next.next = new Node(4); head.next.next.next.next = new Node(5); head.next.next.next.next.next = new Node(6); head.rand = head.next.next.next.next.next; // 1 -> 6 head.next.rand = head.next.next.next.next.next; // 2 -> 6 head.next.next.rand = head.next.next.next.next; // 3 -> 5 head.next.next.next.rand = head.next.next; // 4 -> 3 head.next.next.next.next.rand = null; // 5 -> null head.next.next.next.next.next.rand = head.next.next.next; // 6 -> 4 printRandLinkedList(head); res = copyListWithRand1(head); printRandLinkedList(res); printRandLinkedList(head); System.out.println("========================="); } }
【進階】
不使用額外的數據結構,只用有限幾個變量,且在時間複雜度爲 O(N)內完成原問題要實現的函數。
【分析】
不用hash表又怎麼作呢?仍是這個鏈表,可是拷貝後的節點是放在老鏈表的下一個,而後再和老鏈表的下一個相連。注意rand指針原來的位置並沒變,只是在原來的節點1和節點2之間加了一個節點1‘。
這樣作有什麼好處呢?接下來遍歷的時候,每一次遍歷拿出2個節點,好比拿出節點1和節點1‘。1經過rand指針能夠找到3,而3的克隆節點3’就是3的下一個節點,在這種結構中,經過3的next就能夠找到3‘,而後把1’的rand指向3‘。第一種方法使用hash表的目的就是爲了知道老鏈表和新鏈表的對應關係,而用這種方法也能把新老鏈表的對應關係留下來。按照這種思路,依次拿出節點2和節點2’,節點3和節點3‘便可實現所求。
最後再將新鏈表和老鏈表分離出來,整個過程就結束了。
public class CopyListWithRandom { public static class Node { public int value; public Node next; public Node rand; public Node(int data) { this.value = data; } }public static Node copyListWithRand2(Node head) { if (head == null) { return null; } Node cur = head; Node next = null; //複製鏈表 //原來的鏈表 : 1 -> 2 -> 3 -> 4 -> 5 -> 6 //複製的鏈表 : 1 -> 1' -> 2 -> 2' -> 3-> 3' -> 4 -> 4' -> 5 -> 5' -> 6 -> 6' while (cur != null) { next = cur.next; cur.next = new Node(cur.value); cur.next.next = next; cur = next; } cur = head; //curCopy至關因而一個引線 Node curCopy = null; //設置隨機指向 while (cur != null) { next = cur.next.next; curCopy = cur.next; //注意curCopy的rand指向的是rand.next.由於原節點的next纔是純淨的節點,這些next節點到時須要分隔出去 curCopy.rand = cur.rand != null ? cur.rand.next : null; cur = next; } //這個節點就是分隔出來的鏈表的頭節點 Node res = head.next; cur = head; //分離 while (cur != null) { next = cur.next.next; curCopy = cur.next; //這一步是鏈接後面的 cur.next = next; curCopy.next = next != null ? next.next : null; cur = next; } return res; } public static void printRandLinkedList(Node head) { Node cur = head; System.out.print("Order: "); while (cur != null) { System.out.print(cur.value + " "); cur = cur.next; } System.out.println(); cur = head; System.out.print("rand: "); while (cur != null) { System.out.print(cur.rand == null ? "- " : cur.rand.value + " "); cur = cur.next; } System.out.println(); } public static void main(String[] args) { Node head = null; Node res = null; printRandLinkedList(head); res = copyListWithRand2(head); printRandLinkedList(res); printRandLinkedList(head); System.out.println("========================="); head = new Node(1); head.next = new Node(2); head.next.next = new Node(3); head.next.next.next = new Node(4); head.next.next.next.next = new Node(5); head.next.next.next.next.next = new Node(6); head.rand = head.next.next.next.next.next; // 1 -> 6 head.next.rand = head.next.next.next.next.next; // 2 -> 6 head.next.next.rand = head.next.next.next.next; // 3 -> 5 head.next.next.next.rand = head.next.next; // 4 -> 3 head.next.next.next.next.rand = null; // 5 -> null head.next.next.next.next.next.rand = head.next.next.next; // 6 -> 4 printRandLinkedList(head); res = copyListWithRand2(head); printRandLinkedList(res); printRandLinkedList(head); System.out.println("========================="); } }
【題目】
在本題中,單鏈表可能有環,也可能無環。給定兩個單鏈表的頭節點 head1和head2,這兩個鏈表可能相交,也可能不相交。請實現一個函數, 若是兩個鏈表相交,請返回相交的第一個節點;若是不相交,返回null 便可。 要求:若是鏈表1的長度爲N,鏈表2的長度爲M,時間複雜度請達到 O(N+M),額外空間複雜度請達到O(1)。
【分析】
先解決一個問題:怎麼判斷一個鏈表是否有環?若是一個鏈表有環,返回第一個入環的節點;若是一個鏈表無環,返回null;
方法一:使用hash表。
在遍歷過程當中,把每一個節點放到hashSet裏(沒有value,只有key),若是有環的話,就能重複回到一個節點。由於set把你遍歷過的節點都放到裏面去了,因此就能發現一個節點有沒有遍歷過。若是發現一個節點有遍歷過,就是有環,而且返回第一個入環的節點;若是走到null,就是無環,返回null。注意:set裏面存的不是節點的值,而是節點的內存地址。
/** * 返回鏈表的第一個入環節點——使用HashSet,須要額外空間 * @param head * @return */ public static Node getFirstLoopNode(Node head) { HashSet<Node> set = new HashSet<>(); while (head != null) { if (set.contains(head)) { return head; } set.add(head); head = head.next; } return null; }
方法二:不用額外空間,準備兩個指針,一個快指針,一個慢指針。
快指針一次走2步,慢指針一次走1步。若是走的過程當中,快指針走到了null,確定無環;若是有環,快指針和慢指針必定會在環上相遇(相遇並非值相等,而是內存地址是同一個)。相遇後,快指針回到開頭,而後變成每次走一步;接下來,快指針和慢指針一塊兒走,它們會在第一個入環節點處相遇,這是一個數學結論。此過程時間複雜度仍是O(N).
/** * 返回鏈表的第一個入環節點——使用快慢指針,不須要額外空間 * @param head * @return */ public static Node getLoopNode(Node head) { if (head == null || head.next == null || head.next.next == null) { return null; } Node slow = head.next; Node fast = head.next.next; while (slow != fast) { if (fast.next == null || fast.next.next == null) { return null; } fast = fast.next.next; slow = slow.next; } //快指針和慢指針相遇後,快指針回到頭部 fast = head; while (slow != fast) { slow = slow.next; fast = fast.next; } return slow; }
如今有兩個鏈表,head1是鏈表1的頭節點,head2是鏈表2的頭節點,調用getLoopNode()函數後,就能夠獲得head1的第一個入環節點loop1, 也能獲得head2整個鏈表中第一個入環節點loop2,若是loop1=null&&loop2=null,這就是兩個無環鏈表的相交問題。兩個無環鏈表的相交問題有兩種可能:一種是不相交,一種是相交
假若有兩個無環鏈表h一、h2,怎麼求兩條無環鏈表相交的第一個節點?
方法一:使用hash表
hash表遍歷h1,把h1上的全部節點都加到set裏去,而後在遍歷h2的過程當中,每遍歷一個節點,都檢查該節點是否存在於set中,若是存在,這個節點就是和h1相交的第一個節點。
舉個例子,第一遍把左側h1的節點所有放到set中,而後依次遍歷右側的h2節點,前兩個節點set中都不存在 ,來到第三個節點時,發如今set中,因此這個節點就是第一個相交的節點。
方法二:不使用hash表
兩個無環鏈表h1和h2。先遍歷h1,由於是無環,因此能找到結尾,統計h1的長度(len1)以及找到h1最後一個節點(e1)。再遍歷h2,統計h2的長度(len2)並找到h2最後一個節點(e2),若是e1和e2不是同一個節點,那麼h1和h2不可能相交,返回null。若是e1=e2,h1和h2確定相交,那它倆第一個相交的節點是什麼呢?舉個例子,若是len1=100,len2=80,仍是讓h1和h2都從頭開始遍歷,可是讓h1先走20步,而後和h2一塊兒往下走,它倆必定會共同進入第一個相交的節點。
/** * 兩個鏈表都無環 * @param head1 * @param head2 * @return 返回第一個相交的節點 */ public static Node noLoop(Node head1, Node head2) { if (head1 == null || head2 == null) { return null; } Node cur1 = head1; Node cur2 = head2; int n = 0; while (cur1.next != null) { n++; cur1 = cur1.next; } while (cur2.next != null) { n--; cur2 = cur2.next; } //若是最後一個節點不相等,不可能相交,即無環 if (cur1 != cur2) { return null; } cur1 = n > 0 ? head1 : head2; cur2 = cur1 == head1 ? head2 : head1; n = Math.abs(n); while (n != 0) { n--; cur1 = cur1.next; } while (cur1 != cur2) { cur1 = cur1.next; cur2 = cur2.next; } return cur1; }
若是一個鏈表有環,另一個鏈表無環,不可能相交。若是有相交,都會破壞單鏈表只有一個next的結構。
若是兩個鏈表都有環,即loop1!=null,loop2!=null。造成的結構有3種:
怎麼判斷是哪種結構?
若是loop1=loop2,是第二種結構。怎麼求相交的第一個節點?把環裏的部分忽略掉,把loop一、loop2做爲終止,這時候就等同於兩個無環鏈表的相交問題;
若是loop1!=loop2,讓loop1經過next指針往下走,由於loop1是第一個入環的節點,它往下走必定能回到本身,若是它回到本身了,都沒有遇到loop2,就是第一種結構;
若是loop1往下走後,中途遇到了loop2,就是第三種狀況。若是中途能遇到loop2,返回loop1做爲第一個相交的節點或者返回loop2做爲第一個相交的節點都對,由於loop1是距離h1更近的,loop2是距離h2更近的,它們均可以叫作h1和h2兩個鏈表第一個相交的節點。
/** * 兩個鏈表都有環 * @param head1 * @param loop1 head1的第一個入環節點 * @param head2 * @param loop2 head2的第一個入環節點 * @return */ public static Node bothLoop(Node head1, Node loop1, Node head2, Node loop2) { Node cur1 = null; Node cur2 = null; //若是是第二種結構,把環裏的部分忽略掉,把loop一、loop2做爲終止,等同於兩個無環鏈表的相交問題 if (loop1 == loop2) { cur1 = head1; cur2 = head2; int n = 0; while (cur1 != loop1) { n++; cur1 = cur1.next; } while (cur2 != loop2) { n--; cur2 = cur2.next; } cur1 = n > 0 ? head1 : head2; cur2 = cur1 == head1 ? head2 : head1; n = Math.abs(n); while (n != 0) { n--; cur1 = cur1.next; } while (cur1 != cur2) { cur1 = cur1.next; cur2 = cur2.next; } return cur1; } else { //若是loop1!=loop2,讓loop1經過next指針往下走 cur1 = loop1.next; while (cur1 != loop1) { //若是loop1走的過程當中遇到loop2,就是第三種狀況 if (cur1 == loop2) { return loop1; } cur1 = cur1.next; } return null; } }
【兩個鏈表相交系列問題的代碼實現】
public class FindFirstIntersectNode { public static class Node { public int value; public Node next; public Node(int data) { this.value = data; } } public static Node getIntersectNode(Node head1, Node head2) { if (head1 == null || head2 == null) { return null; } //head1的第一個入環節點 Node loop1 = getLoopNode(head1); //head2的第一個入環節點 Node loop2 = getLoopNode(head2); //兩個無環鏈表的相交問題 if (loop1 == null && loop2 == null) { return noLoop(head1, head2); } //兩個有環鏈表的相交問題 if (loop1 != null && loop2 != null) { return bothLoop(head1, loop1, head2, loop2); } return null; }public static void main(String[] args) { // head1鏈表:1->2->3->4->5->6->7->null Node head1 = new Node(1); head1.next = new Node(2); head1.next.next = new Node(3); head1.next.next.next = new Node(4); head1.next.next.next.next = new Node(5); head1.next.next.next.next.next = new Node(6); head1.next.next.next.next.next.next = new Node(7); // head2鏈表:0->9->8->6->7->null Node head2 = new Node(0); head2.next = new Node(9); head2.next.next = new Node(8); //head2的8節點指向head1的6節點 head2.next.next.next = head1.next.next.next.next.next; //打印兩個無環鏈表的第一個相交節點,結果爲6 System.out.println(getIntersectNode(head1, head2).value); // head1鏈表:1->2->3->4->5->6->7->4... head1 = new Node(1); head1.next = new Node(2); head1.next.next = new Node(3); head1.next.next.next = new Node(4); head1.next.next.next.next = new Node(5); head1.next.next.next.next.next = new Node(6); head1.next.next.next.next.next.next = new Node(7); //7節點的next指向4節點,造成有環 head1.next.next.next.next.next.next = head1.next.next.next; // 0->9->8->2... head2 = new Node(0); head2.next = new Node(9); head2.next.next = new Node(8); //head2的8節點的next指針指向head1的2節點 head2.next.next.next = head1.next; //兩個有環鏈表的相交問題(第二種結構) System.out.println(getIntersectNode(head1, head2).value); // head2鏈表:0->9->8->6->4->5->6.. head2 = new Node(0); head2.next = new Node(9); head2.next.next = new Node(8); //head2的8節點的next指針指向head1的6節點 head2.next.next.next = head1.next.next.next.next.next; //兩個有環鏈表的相交問題(第三種結構),返回loop1或loop2 System.out.println(getIntersectNode(head1, head2).value); } }