1. 程式人生 > >劍指Offer Java版 雙指標在連結串列中的應用

劍指Offer Java版 雙指標在連結串列中的應用

       所謂雙指標,指的是在遍歷物件的過程中,不是使用單個指標進行訪問,而是使用兩個相同方向或者相反方向的指標進行遍歷,從而達到相應的目的。雙指標的使用可以降低程式的時間複雜度或者空間複雜度,總之是一種有效的解決問題的方案。

       (注:這裡所說的指標,並不是C/C++中指標的概念,而是指索引,遊標或指標,可迭代物件等)

       雙指標在連結串列中也有很多用處,比如前面寫到過的找出連結串列中的倒數第k個結點,就巧妙地利用到了雙指標,此外,判斷連結串列中是否有環也可以使用雙指標,設兩個快慢指標,讓快指標一次移動兩步,慢指標一次移動一步,若連結串列中有環,那麼快指標與慢指標一定能夠相遇,若兩者沒有相遇,說明連結串列中沒有環。還有如果要給出連結串列中的中間的結點,也可以使用快慢指標,讓快指標一次移動兩步,慢指標一次移動一步,當快指標剛好到達連結串列的末尾時,慢指標所指向的正好是中間的結點(對於奇數個結點,就是中間中的一個,若是偶數個結點,是中間結點中的後一個,即length / 2);

       由此看出,雙指標的思想就是建立兩個指標,這兩個指標可以使相同方向,一般前進的速度不同或者兩者的前進順序不一致;也可能是相反的方向,通過使用相關的變數控制來達到我們的目的

        下面是幾道相關的題目:

         1.給出一個連結串列,判斷是否有環

public boolean hasCycle(ListNode head) {
        if(head == null){
            return false;
        }
        ListNode fast = head, slow = head;

        while(fast != null && fast.next!=null){
            slow = slow.next;
            fast = fast.next.next;
            if(slow == fast){
                return true;
            }
        }
        //注意迴圈的條件是fast不為空,並且fast.next不為空
        //若只有一個頭結點,並且頭結點的next指向頭結點,仍然應該返回true
        return head.next == head;
}
       

       2.給出一個連結串列,若連結串列中有環,給出環的入口結點,否則返回null

           這裡寫圖片描述

       若連結串列中有環,則採用快慢指標判斷時,兩者一定在環內相遇(除非環的入口在頭結點)。推導過程如下:

如果判斷出單鏈表有環,如何找到環的入口?比如上圖中,D就是環的入口。假設慢指標移動了S個結點,則快指標移動了2S個結點,如果環入口離頭結點的距離為x,環入口離相遇的那個結點距離為y,環的長度為r, 單鏈表總長度為L,他們的關係推導如下:

  • 2S = S + n * r (快指標的步數等於S 加上環上多轉的n圈)
  • S = n * r
  • S = x + y = n * r
  • x + y = (n - 1) * r + r
  • x + y = (n - 1) * r + L - x
  • x = (n - 1) * r + L - x - y

    最後一個式子中,L - x - y為相遇點到環入口的距離,最後一個式子表明環入口到頭結點的距離等於(n - 1) * r 加上相遇點到環入口的距離。也就是說一個指標從頭結點出發,另一個指標從相遇點出發,步長都為1的話,這兩個指標一定會在環入口相遇(因為 r為環的長度,(n - 1) * r一定是環的整數倍)。

<span style="font-family:microsoft yahei;"> public ListNode detectCycle(ListNode head) {
        //如果有環,則兩者一定是在環中相遇
        ListNode meetNode = meetNode(head);
		if(meetNode == null){
			return null;
		}
        ListNode slow = head;
        //當兩者再次相遇時,即為連結串列中環的入口
        while(slow != meetNode){
            slow = slow.next;
            meetNode = meetNode.next;
        }
        return slow;
 }
 public ListNode meetNode(ListNode head){
        if(head == null){
            return null;
        }
        ListNode meetNode;
        ListNode fast = head, slow = head;
        while(fast != null && fast.next != null){
            slow = slow.next;
            fast = fast.next.next;
            if(slow == fast){
                meetNode = slow;
                return meetNode;
            }
        }
        //注意迴圈的條件是fast不為空,並且fast.next不為空
        //若只有一個頭結點,並且頭結點的next指向頭結點,仍然應該返回true
        return head.next == head ? head : null;
 }</span>
        其他解法可以見這個連結,雙指標中環的入口

        雙指標的應用還有還多,除了可以應用在連結串列中,還可以用在陣列中。比如求兩個數的和等於給定的數時,就可以使用雙指標來解決。以後會仔細談一談2sum(兩個數的和),3sum,4sum問題以及適用於雙指標的其他問題。

     2016 年10月 8 號更新

    今天刷leetcode的時候,碰到了一道happy number的題

A happy number is a number defined by the following process: Starting with any positive integer, replace the number by the sum of the squares of its digits, and repeat the process until the number equals 1 (where it will stay), or it loops endlessly in a cycle which does not include 1. Those numbers for which this process ends in 1 are happy numbers.

剛開始以為是逐漸的判斷,後來看了解析之後才明白我們同樣可以檢測是否有環(類似於連結串列中檢測有環的思路),可以看這篇文章

下面是Ac的程式碼

public boolean isHappy(int n) {
        int slow = n , fast = n;
        do{
            slow = digitSquareSum(slow);
            fast = digitSquareSum(fast);
            fast = digitSquareSum(fast);
        }while(slow != fast);
        return slow == 1;
    }
    
    public int digitSquareSum(int n){
        if(n < 0){
            return -1;
        }
        int sum = 0;
        while(n > 0){
            sum += Math.pow(n % 10 , 2);
            n = n / 10;
        }
        return sum;
    }

不得不說,之前以為檢測環只能用於連結串列中,也沒想過還可以這樣使用,算是打開了思路吧