Swift 演算法實戰之路:連結串列

發表於2016-06-04

1721232-e8e8069e07e22728

上期我們探討了使用Swift如何破解陣列、字串、集合、字典相關的演算法題。本期我們一起來講講用Swift如何實現連結串列以及連結串列相關的技巧。本期主要內容有:

  • 連結串列基本結構
  • Dummy節點
  • 尾插法
  • 快行指標

基本結構

對於連結串列的概念,實在是基本概念太多,這裡不做贅述。我們直接來實現連結串列節點。

有了節點,就可以實現連結串列了。

有了上面的基本操作,我們來看如何解決複雜的問題。

Dummy節點和尾插法

話不多說,我們直接先來看下面一道題目。

給一個連結串列和一個值x,要求將連結串列中所有小於x的值放到左邊,所有大於等於x的值放到右邊。原連結串列的節點順序不能變。
例:1->5->3->2->4->2,給定x = 3。則我們要返回 1->2->2->5->3->4

直覺告訴我們,這題要先處理左邊(比x小的節點),然後再處理右邊(比x大的節點),最後再把左右兩邊拼起來。
思路有了,再把題目抽象一下,就是要實現這樣一個函式:

即我們有給定連結串列的頭節點,有給定的x值,要求返回新連結串列的頭結點。接下來我們要想:怎麼處理左邊?怎麼處理右邊?處理完後怎麼拼接?
先來看怎麼處理左邊。我們不妨把這個題目先變簡單一點:

給一個連結串列和一個值x,要求只保留連結串列中所有小於x的值,原連結串列的節點順序不能變。
例:1->5->3->2->4->2,給定x = 3。則我們要返回 1->2->2

我們只要採用尾插法,遍歷連結串列,將小於x值的節點接入新的連結串列即可。程式碼如下:
注意,上面的程式碼我們引入了Dummy節點,它的作用就是作為一個虛擬的頭前結點。我們引入它的原因是我們不知道要返回的新連結串列的頭結點是哪一個,它有可能是原連結串列的第一個節點,可能在原連結串列的中間,也可能在最後,甚至可能不存在(nil)。而Dummy節點的引入可以巧妙的涵蓋所有以上情況,我們可以用dummy.next方便得返回最終需要的頭結點。
現在我們解決了左邊,右邊也是同樣處理。接著只要讓左邊的尾節點指向右邊的頭結點即可。全部程式碼如下:

注意這句post.next = nil,這是為了防止連結串列迴圈指向構成環,是必須的但是很容易忽略的一步。
剛才我們提到了環,那麼怎麼檢測連結串列中是否有環存在呢?
1721232-c62b5ffb2c62c88f

快行指標

筆者理解快行指標,就是兩個指標訪問連結串列,一個在前一個在後,或者一個移動快另一個移動慢,這就是快行指標。所以如何檢測一個連結串列中是否有環?用兩個指標同時訪問連結串列,其中一個的速度是另一個的2倍,如果他們相等了,那麼這個連結串列就有環了。程式碼如下:

再舉一個快行指標一前一後的例子,看下面這道題。
刪除連結串列中倒數第n個節點。例:1->2->3->4->5,n = 2。返回1->2->3->5。
注意:給定n的長度小於等於連結串列的長度。
解題思路依然是快行指標,這次兩個指標移動速度相同。但是一開始,第一個指標(指向頭結點之前)就落後第二個指標n個節點。接著兩者同時移動,當第二個移動到尾節點時,第一個節點的下一個節點就是我們要刪除的節點。程式碼如下:

這裡還用到了Dummy節點,因為有可能我們要刪除的是頭結點。

總結

這次我們用Swift實現了連結串列的基本結構,並且實戰了連結串列的幾個技巧。在結尾處,我還想強調一下Swift處理連結串列問題的兩個細節問題:

  • 一定要注意頭結點可能就是nil。所以給定連結串列,我們要看清楚head是不是optional,在判斷是不是要處理這種邊界條件。
  • 注意每個節點的next可能是nil。如果不為nil,請用”!”修飾變數。在賦值的時候,也請注意”!”將optional節點傳給非optional節點的情況。

相關文章