LeetCode 92 | 大公司常考的面試題,翻轉鏈表當中指定部分


今天是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 -

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM