題目1
【簡單】環形鏈表
給定一個鏈表,判斷鏈表中是否有環。
爲了表示給定鏈表中的環,我們使用整數 pos 來表示鏈表尾連接到鏈表中的位置(索引從 0 開始)。 如果 pos 是 -1,則在該鏈表中沒有環。
示例 1:
輸入:head = [3,2,0,-4], pos = 1
輸出:true
解釋:鏈表中有一個環,其尾部連接到第二個節點。
示例 2:
輸入:head = [1,2], pos = 0
輸出:true
解釋:鏈表中有一個環,其尾部連接到第一個節點。
解答
快慢雙指針,可以有效的減少空間複雜度和時間複雜度的匹配次數
首先,由於鏈表是個環,所以相遇的過程可以看作是快指針從後邊追趕慢指針的過程。那麼做如下思考:
1:快指針與慢指針之間差一步。此時繼續往後走,慢指針前進一步,快指針前進兩步,兩者相遇。
2:快指針與慢指針之間差兩步。此時繼續往後走,慢指針前進一步,快指針前進兩步,兩者之間相差一步,轉化爲第一種情況。
3:快指針與慢指針之間差N步。此時繼續往後走,慢指針前進一步,快指針前進兩步,兩者之間相差(N+1-2)-> N-1步。
因此,此題得證。所以快指針必然與慢指針相遇。又因爲快指針速度是慢指針的兩倍,所以相遇時必然只繞了一圈。
上述解釋來自鏈接:https://www.zhihu.com/question/23208893/answer/117115415
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def hasCycle(self, head: ListNode) -> bool:
# 快慢雙指針追趕碰撞解法,時間複雜度爲O(n),空間複雜度爲O(1)
if not head:
return head
slow = head
quick = head
while quick and slow:
# 這裏因爲quick是跳兩次,所以要判斷quick和quick.next是否都爲空,否則會報NoneType的異常
slow = slow.next
if quick.next:
quick = quick.next.next
else:
return False
if quick is slow:
return True
return False
快慢指針的用途:
1、判斷鏈表中的環(如上題所示)
2、找中間值
一般的思路是:先遍歷一次鏈表,記錄住一共有多少個節點,然後,再次遍歷找尋中點。
利用快慢指針,我們來看看這個問題會變成什麼樣。思路如下:我們把一個鏈表看成一個跑道,假設a的速度是b的兩倍,那麼當a跑完全程後,b剛好跑一半,以此來達到找到中間節點的目的。
如下圖,最開始,slow與fast指針都指向鏈表第一個節點,然後slow每次移動一個指針,fast每次移動兩個指針。
3、刪除倒數第n個節點
刪除倒數第n個節點,那就等於是要我們先找出待刪除元素前一個元素,也就是第n-1個節點。聰明的你肯定發現了,我們又把這個問題轉化爲找鏈表上的某個節點的問題了,這是快慢指針最擅長的場景。
那如何找第(n-1)個元素呢?我們一開始就讓fast指針比slow指針快n+1個元素,接下來,兩個指針都是一步一步來往下走。那麼當fast指針走完時,slow指針就剛剛好停留在第(n-1)個元素上。
以下圖解了n=2時的情形(其中dummyHead是我們手動加上去的虛擬頭節點):
題目2
【簡單】迴文鏈表
編寫一個函數,檢查輸入的鏈表是否是迴文的。
示例 1:
輸入: 1->2
輸出: false
示例 2:
輸入: 1->2->2->1
輸出: true
進階:
你能否用 O(n) 時間複雜度和 O(1) 空間複雜度解決此題?
解答
- 快慢指針遍歷到鏈表中間,快指針走兩步、慢指針走一步,最後慢指針的位置就是鏈表中間
- 從中間開始反轉鏈表後半段
- 從原鏈表頭和反轉後的鏈表頭開始比較 value
# Definition for singly-linked list.
# class ListNode(object):
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution(object):
def isPalindrome(self, head):
if not head:
return True
slow = head
fast = head
# slow 遍歷到中間,最後 slow 停的位置是 n/2+1
while fast and fast.next:
slow = slow.next
fast = fast.next.next
# 然後從 slow 開始反轉鏈表
# 如果是奇數,多出來的一個節點不用管了
pre = slow
while slow and slow.next:
tmp = slow.next.next
slow.next.next = pre
pre = slow.next
slow.next = tmp
# 反轉後的頭結點是 pre,從 pre 和 head 開始比較 val
while head and pre:
if head.val != pre.val:
return False
head = head.next
pre = pre.next
return True
# 鏈接:https://leetcode-cn.com/problems/palindrome-linked-list-lcci/solution/shuang-zhi-zhen-fan-zhuan-lian-biao-python-by-yuyi/
【參考】