更詳細的講解和代碼調試演示過程,請參看視頻
如何進入google,算法面試技能全面提高指南vue
給定兩個單向鏈表,這兩個鏈表有可能會有重疊,狀況以下:java
兩個單向鏈表從節點5開始重合,要求給定一個空間複雜度爲O(1)的算法,返回兩個鏈表相交時的第一個節點。依據上圖,也就是返回節點5.node
首先咱們須要作的是,確保給定的兩個單向鏈表,他們是相交的。這個很好肯定,只要從頭遍歷兩個鏈表,若是他們的尾節點是同樣的話,那麼這兩個鏈表就是相交的,問題是,如何儘快找到他們相交的第一個節點。面試
最笨的辦法是,先找到尾節點,而後去掉尾節點,而後再次遍歷查找新的尾節點,而後再去掉,直到兩個鏈表沒有共同的尾節點,那麼最後去掉的共同尾巴節點,則是兩個鏈表的首次相交節點。這種作法可行,可是算法複雜度是O(n^2)。有沒有更好的辦法呢?算法
假設第一個鏈表,從頭結點到首次相交節點,所經歷的距離用T1來表示,根據上圖例子,T1 = 5, 也就是第一個隊列從頭結點0開始,須要經歷節點1,2,3,4也就是總共5個節點後才能到達節點5.微信
咱們用T3 表示隊列2從頭節點開始,到達首次相交節點的距離。根據上圖,T3 = 3.markdown
咱們用T2表示兩隊列相交部分的節點數.依據上圖T2 = 5.app
由此隊列1的長度爲: T1 + T2 (1)
隊列2的長度爲:T3 + T2 (2)ui
若是咱們能算出T3的數值,那麼咱們從隊列2的頭結點出發,通過T3步後,就能達到首次相交節點。咱們如何計算T3的數值呢?this
對T3的計算,須要一個小技巧.咱們把隊列2進行反轉,獲得下面情形:
若是此時咱們從隊列1的頭結點開始進行遍歷,那麼從上頭的節點0開始出發,會到隊列2的頭結點0結束。這樣,在反轉後,若是再次從頭遍歷隊列1的話,獲得的長度就是:
T1 + T3 + 1 (3).
根據上面三個公式,咱們即可以計算出T3來。
(3) - (1) = T1 + T3 + 1 - T1 - T2 = T3 - T2 + 1
(3) - (1) + (2) = T3 - T2 + 1 + T3 + T2 = 2*T3 + 1
由此,咱們能夠反解出T3, 有了T3,咱們即可以獲得兩隊列首次相交節點了。
這個算法除了須要遍歷兩個隊列外,還須要對其中一個隊列進行反轉,不管是遍歷仍是反轉,其算法複雜度都是O(n), 所以總算法複雜度是O(n).
代碼實現:
public class ListIntersetChecker { private Node listHead1; private Node listHead2; private int firstListLen = 0; //t1 + t2 private int secondListLen = 0; // t3 + t2 private int lenAfterReverse = 0; // t1 + t3 private ListReverse listReverse = null; public ListIntersetChecker(Node listHead1, Node listHead2) { this.listHead1 = listHead1; this.listHead2 = listHead2; } public Node getFirstIntersetNode() { if (isTwoListInterset() == false) { return null; } listReverse = new ListReverse(listHead2); Node reverseHead = listReverse.getReverseList(); lenAfterReverse = getListLen(listHead1); int t3 = ((lenAfterReverse - firstListLen) + secondListLen - 1) / 2; int steps = secondListLen - t3 - 1; while (steps > 0) { reverseHead = reverseHead.next; steps--; } return reverseHead; } private int getListLen(Node head) { int len = 0; while (head != null) { head = head.next; len++; } return len; } private boolean isTwoListInterset() { Node head1 = listHead1; Node head2 = listHead2; while (head1.next != null || head2.next != null) { if (head1.next != null) { head1 = head1.next; firstListLen++; } if (head2.next != null) { head2 = head2.next; secondListLen++; } } firstListLen++; secondListLen++; return head1 == head2; } }
ListIntersetCheck.java 用於實現上面描述的算法。 getFirstIntersetNode返回兩重疊隊列首次相交節點。isTwoListInterset 用於判斷兩隊列是否相交。在遍歷兩隊列時,統計兩隊列的長度,也就是得到 T1 + T2 以及 T3 + T2的值。
而後把隊列2進行反轉,反轉後,再從隊列1的頭節點進行遍歷,獲得的lenAfterReverse就是 T1 + T3 + 1.
int t3 = ((lenAfterReverse - firstListLen) + secondListLen - 1) / 2;
上面語句則根據前面的推導計算出T3.
因爲隊列2已經反轉了,因此不能從隊列2的頭結點去遍歷,只能從隊列2的尾節點開始遍歷,若是頭結點開始遍歷須要T3步的話,那麼從尾節點遍歷,則須要steps = secondListLen - (T3 + 1) 步。
由此,代碼從隊列2反轉後的頭結點開始,通過steps個節點後抵達兩隊列首次相交時的節點。
再看看主入口代碼:
public class LinkList { public static void main(String[] args) { ListUtility util1 = new ListUtility(); ListUtility util2 = new ListUtility(); Node list1 = util1.createList(10); Node list2 = util2.createList(3); Node node = util1.getNodeByIdx(5); Node tail = util2.getNodeByIdx(2); tail.next = node; ListIntersetChecker intersetChecker = new ListIntersetChecker(list1, list2); Node interset = intersetChecker.getFirstIntersetNode(); System.out.println("The first interset node is : " + interset.val); } }
程序啓動時,先構造兩個隊列,隊列1節點從0到9,隊列2從0到2,而後把隊列2的尾節點的next指向隊列1的編號爲5的節點,因而就構造了咱們例子圖中的兩個相交隊列,而後再利用ListIntersetChecker得到兩重合隊列的首個相交節點。
最後程序運行結果爲:
The first interset node is : 5
結果跟咱們理論推導一致,也就是說,咱們的說法實現是正確的。更詳細的代碼講解和推導調試過程,請參看視頻。
本文分享自微信公衆號 - Coding迪斯尼(gh_c9f933e7765d)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。