刷題找工作《環形鏈表問題》通解

NO.141 環形鏈表 簡單

JzeHZd.png

Jzeose.png

思路一:HashSet p指針遍歷鏈表,將每個節點存入 set ,如果訪問的節點 set 中存在,說明有環。

時間複雜度:O(n) 空間複雜度:O(n)

思路二:快慢指針 其實就是 Floyd 算法,如果快指針走到了結尾,說明不是環形鏈表;如果是環形鏈表快慢指針一定會相遇,因爲入環之後每次移動快慢指針,就相當於讓兩個指針的距離 -1 。

public boolean hasCycle(ListNode head) {
    if (head == null || head.next == null) return false;
    ListNode fast = head.next, slow = head;
    while (fast != null && fast.next != null) {
        //相遇說明有環
        if (fast == slow) return true;
        fast = fast.next.next;
        slow = slow.next;
    }
    return false;
}

時間複雜度:O(n) 空間複雜度:O(1)

NO.142 環形鏈表II 中等

JzebdA.png

JzeTqH.png

和其姊妹題NO.141的解題思路一樣,稍微一些調整變化。

思路一:HashSet p指針遍歷鏈表,將每個節點存入 set ,如果訪問的節點 set 中存在,說明有環並返回此時的節點p。線性遍歷,第一次重複一定是入環的第一個節點。

public ListNode detectCycle(ListNode head) {
    if (head == null || head.next == null) return null;
    HashSet<ListNode> set = new HashSet<>();
    ListNode p = head;
    while (p != null) {
        if (set.contains(p)) {
            return p;
        }
        set.add(p);
        p = p.next;
    }
    return null;
}

時間複雜度:O(n) 空間複雜度:O(n)

思路二:快慢指針 快慢指針如何進行不在贅述。重點在於如何找到入環的第一個節點。

先看一下最終實現:如果快指針遍歷完成,沒有環;

如果有環,當快慢指針第一次相遇後,慢指針原地不動,重置快指針回到表頭節點,此時快慢指針都保持每次移動一個節點,當快慢指針第二次相遇的時候,此時相遇節點就是入環的第一個節點。

public ListNode detectCycle(ListNode head) {
    ListNode fast = head, slow = head;
    while (true) {
        //無環
        if (fast == null || fast.next == null) return null;
        fast = fast.next.next;
        slow = slow.next;
        //第一次相遇
        if (slow == fast) break;
    }
    //重置fast
    fast = head;
    //第二次相遇
    while (slow != fast) {
        slow = slow.next;
        fast = fast.next;
    }
    return slow;
}

時間複雜度:O(n) 空間複雜度:O(1)

爲什麼兩次相遇一定是入環的第一個節點?

設鏈表有環,且鏈表共有 a+b 個節點,其中 鏈表頭部到鏈表入口 有 a 個節點(不計鏈表入口節點), 鏈表環 有 b 個節點;設兩指針分別走了 f,s 步,則有:

  1. fast 走的步數是slow步數的 2 倍,即 f = 2s ;
  2. fastslow多走了 n 個環的長度,即 f = s + nb ; ( 雙指針都走過一次非環的部分 a 步,然後在環內繞圈直到重合,重合時 fastslow 多走 環的長度整數倍 n )
  3. 以上兩式相減得:f = 2nb,s = nb,即fastslow 指針分別走了 2n,n 個 環的周長

如果一個指針 k 遍歷這個有環鏈表一次需要走 k = a + nb 步 (此時 n=1),此時 k 會回到入環節點。

而目前,slow 指針走過的步數爲 nb 步。因此,我們只要想辦法讓 slow 再走 a 步停下來,就可以到環的入口節點。

但是此時我們並不知道參數鏈表的 a 是多少!

所以讓 fastslow 第一次相遇後(此時 s = nb),fast 從頭節點開始每次走一步,同時 slow 也在環內每次走一步,當 fast 走 a 步到達入環節點時,slow 共走了 s = a + nb 步,即 slow 也到達入環節點。

所以只需要第二次 fastslow 相遇即可,此時一定是在入環節點。

NO.202 快樂數 簡單

在這裏插入圖片描述
思路一:HashTable 用一個 HashSet 對每一個數字的每一位求平方相加,結果 num 如果是 1 則說明 n 是快樂數;如果不是 1,判斷 HashSet 中是否已經存在相同的 num,如果存在說明出現閉環,不存在則結果放入 Set 並繼續按位平方求和,判斷結果。

public boolean isHappy(int n) {
    HashSet<Integer> set = new HashSet<>();
    //還沒有證明n是快樂數,也沒有發生循環
    while (n != 1 && !set.contains(n)) {
        set.add(n);
        n = helper(n);
    }
    return n == 1;
}
//按位平方求和
private int helper(int n) {
    int num = 0;
    while (n > 0) {
        int temp = n%10;
        num+=temp*temp;
        n/=10;
    }
    return num;
}

不需要考慮平方求和之後的結果num超出範圍,因爲 999 的結果是 243 、9999 的結果 324 、、、 9999999999999 的結果 1053 。。。發現,三位數最大的按位平方求和結果還是個三位數,所以無論怎麼求下去,都是三位數。

時間複雜度:O(logn) 空間複雜度:O(n)

思路二:快慢指針 不斷地生成 num ,num 生成下一個 num ,這是一種鏈式的遞減,如果鏈上的某個節點出現了 1 ,說明 n 是快樂數,如果鏈上出現了環,說明 n 不是快樂數。

這種思路下,本問題就轉換成了判斷鏈表是否有環的問題,在判斷的途中順便檢測是否有節點爲 1 。

慢指針一次走一個節點,快指針一次走兩個節點,如果存在循環,那麼快慢一定會相遇。

public boolean isHappy(int n) {
    int slow = n, fast = helper(n);
    while (fast != 1 && slow != fast) {
        slow = helper(slow);
        fast = helper(helper(fast));
    }
    return fast == 1;
}

//按位平方求和
private int helper(int n) {
    int num = 0;
    while (n > 0) {
        int temp = n % 10;
        num += temp * temp;
        n /= 10;
    }
    return num;
}

時間複雜度:O(logn) 空間複雜度:O(1)

NO.287 尋找重複數 中等

在這裏插入圖片描述
思路一:快慢指針 讀完題並不能立刻想到判環問題。如果將數組建圖,下標位置 i 都有一個連接 nums[i] 的邊。本題說有且只有一個重複元素 num,那麼 num 這個位置至少有兩條邊指向他,所以圖裏一定有環,而這個重複元素 num 就是環的入口。

問題變成了找環的入口問題,就變成了上面的 NO.142 環形鏈表 II。區別僅在於本題是數組而已。

依然快慢指針,雖然本題不是一個鏈表,但是根據前面所說的建圖後有了鏈表的性質(下標 i -> nums[i]),所以移動一步就是 slow = nums[slow],同樣的走兩步就是 fast = nums[nums[fast]]

public int findDuplicate(int[] nums) {
    int slow = 0, fast = 0;
    while (true) {
        slow = nums[slow];
        fast = nums[nums[fast]];
        //第一次相遇
        if (slow == fast) break;
    }
    //重置一個指針,快慢指針都每次一個步長
    slow = 0;
    while (slow != fast) {
        slow = nums[slow];
        fast = nums[fast];
    }
    //第二次相遇就是環入口
    return slow;
}

時間複雜度:O(n) 空間複雜度:O(1)


本人菜鳥,有錯誤請告知,感激不盡!

更多題解源碼和學習筆記:githubCSDNM1ng

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章