LeetCode 92 | 大公司常考的面試題,翻轉連結串列當中指定部分

TechFlow2019發表於2020-08-21

今天是LeetCode專題的第58篇文章,我們一起來看看LeetCode 92題,翻轉連結串列II(Reverse LInked List II)。

這題的官方難度是Medium,2451個贊同,145個反對,通過率38.6%。從這份資料上我們也看得出來,這題的質量很高,廣受好評。也的確如此,這是一道非常經典的連結串列問題,不僅考驗我們對於連結串列的理解和掌握,而且對基本功的要求也很高。

題意

給定一個連結串列和兩個整數m和n,m和n分別代表連結串列當中的第m和第n個元素,其中m <= n。要求我們通過一次遍歷將連結串列當中m到n這一段元素進行翻轉

樣例

Input: 1->2->3->4->5->NULL, m = 2, n = 4
Output: 1->4->3->2->5->NULL

題解

這題的題意很直接,就是讓我們翻轉連結串列當中指定的部分,並且只能通過一次遍歷完成。

我們很容易想明白,通過m和n我們可以將連結串列分成三個部分:

分別是m左側的,m到n之間的也就是我們需要翻轉的部分,以及n右側的部分。當然這裡可能存在一個特殊情況,m左側和n的右側都有可能沒有元素,我們可以先忽略,先考慮最一般的情況。

翻轉連結串列我們最常用的方法就是先把[m, n]區間裡的元素先儲存起來,人工翻轉了之後,再重新構建成一段連結串列,替換原連結串列對應的部分。但是很明顯也可以發現,這樣做是不符合題目要求的。因為我們儲存元素遍歷了連結串列一次,我們在構建連結串列的時候又遍歷了一次,至少需要兩次遍歷才可以完成

那怎麼樣才能一次遍歷完成連結串列的翻轉呢?其實也很簡單,我們只需要倒敘插入就好了。

比如我們有這樣一段連結串列,我們想要翻轉其中2、3、4這三個節點:

首先,我們從1開始,1是翻轉之後的起始部分。我們先記錄下1的位置,這裡1指向2保持不變。對於2號節點我們需要記錄下它的後繼,這裡我們用tmp記錄2號節點的後繼,也就是3號節點。之後我們將2號節點的後繼置為None。

再下一步,我們用tmp記錄3號節點的後繼,將3號節點的後繼指向2號節點。

最後,我們如法炮製,將4號節點的後繼指向3號節點,將1號節點的後繼指向4號節點,並且將2號節點的後繼指向4號節點的原後繼,也就是None。這樣我們就完成了連結串列部分的翻轉,其中的原理很簡單,就是利用了連結串列遍歷和插入時候的性質,每次將需要翻轉部分元素的後繼指向它們原本的前驅。這句話有些拗口,但是多讀幾遍也就理解了。

這個思路非常簡單,但是用程式碼實現卻不容易,很容易寫錯。尤其是在賦值的時候,很容易搞錯指標到底應該指向哪裡,到底需要哪些臨時變數。關於這些內容沒有太好的辦法,只能加強訓練,提升自己的基本功。

最後, 我們考慮一下特殊情況。題目當中的特殊情況有兩種, 第一種是m=1,第二種是n=連結串列末尾。其中第二種不算是特殊情況,因為在連結串列當中末尾的元素也有後繼,就是None。但是m=1的情況需要小心,因為m=1的時候,翻轉部分是沒有前驅的,稍稍有些不同。解決也很簡單,我們單獨特判一下這種情況,人為建立一個前驅節點即可。

貼上程式碼:

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def reverseBetween(self, head: ListNode, m: int, n: int) -> ListNode:
        if m == n:
            return head
        
        pnt = head
        # 移動m-2格,到達翻轉部分的前驅節點
        for i in range(m-2):
            pnt = pnt.next
            
        
        flag = False
        # 特判m=1的情況,如果m=1那麼人為製造一個節點作為前驅
        if m == 1:
            flag = True
            pnt = ListNode(0)
            pnt.next = head
            head = pnt
            
        # cur即當前待翻轉的節點
        cur = pnt.next
        # pre表示cur的前驅
        pre = pnt
        # last表示最後一個翻轉的元素
        last = pnt.next
            
        for i in range(m, n+1):
            # 先記錄下當前節點的後繼
            nxt = cur.next
            # 將當前節點的後繼指向前驅
            cur.next = pre
            pnt.next = cur
            pre = cur
            cur = nxt
            
        # 將last指向翻轉之後的元素,將連結串列串起來
        last.next = cur
        return head.next if flag else head
            
            

總結

連結串列的相關操作非常考驗基本功,雖然演算法簡單,但是對連結串列不熟悉,邏輯思考能力稍稍弱一些的同學想要做出這道題還是比較困難的。也因此,很多公司在面試的時候喜歡詢問連結串列相關的操作和演算法,以此考察候選人的基本功。如果想要進入大公司的,建議好好練習一下相關的問題,一定會有幫助的。

今天的文章到這裡就結束了,如果喜歡本文的話,請來一波素質三連,給我一點支援吧(關注、轉發、點贊)。

- END -

掃碼關注,獲取更多優質文章

相關文章