[中等] 147. 對鏈表進(jìn)行插入排序

歡迎關(guān)注 leetcode 專欄

題目

對鏈表進(jìn)行插入排序。

插入排序的動(dòng)畫演示如上垮衷。從第一個(gè)元素開始冈闭,該鏈表可以被認(rèn)為已經(jīng)部分排序(用黑色表示)稼稿。
每次迭代時(shí),從輸入數(shù)據(jù)中移除一個(gè)元素(用紅色表示)者吁,并原地將其插入到已排好序的鏈表中窘俺。

插入排序算法:

  1. 插入排序是迭代的,每次只移動(dòng)一個(gè)元素复凳,直到所有元素可以形成一個(gè)有序的輸出列表瘤泪。
  2. 每次迭代中灶泵,插入排序只從輸入數(shù)據(jù)中移除一個(gè)待排序的元素,找到它在序列中適當(dāng)?shù)奈恢枚酝荆⑵洳迦搿?/li>
  3. 重復(fù)直到所有輸入數(shù)據(jù)插入完為止赦邻。

示例 1:

輸入: 4->2->1->3
輸出: 1->2->3->4

示例 2:

輸入: -1->5->3->4->0
輸出: -1->0->3->4->5

輸入的節(jié)點(diǎn)對象的定義

class ListNode:
    def __init__(self, x):
        self.val = x
        self.next = None

鏈接:https://leetcode-cn.com/problems/insertion-sort-list

解法

常規(guī)解法

思路:

  1. 考慮到鏈表的表頭可能常常會(huì)變化,不利于維護(hù)实檀,因此可創(chuàng)建一個(gè)永遠(yuǎn)不會(huì)被替換的表頭 ListNode(float('-inf'))惶洲,每次從該表頭開始比較
  2. 插入排序的子問題,就是將待排序的節(jié)點(diǎn) current_node 插入到已排序區(qū)膳犹,因此首先要在已排序區(qū)找到最后一個(gè)小于等于 current_node 值的節(jié)點(diǎn) cur 恬吕,并將 current_node 插入到 cur 后面,以保持排序的穩(wěn)定性须床。
  3. 定義當(dāng)前已排序區(qū)的最后一個(gè)節(jié)點(diǎn)是 last铐料,如果 curlast,那么相當(dāng)于什么都不用動(dòng)侨颈,且 current_node 變成了新的 last ;如果不是芯义,則插入 current_nodecur 后面哈垢,并更新 last 的指針。
class Solution:
    def insertionSortList(self, head: ListNode) -> ListNode:
        if not head: return None
        
        # 創(chuàng)建一個(gè)永遠(yuǎn)不會(huì)被替換的假頭
        new_head = ListNode(float('-inf'))
        new_head.next = head
        
        last = head # 已排序區(qū)的最后一個(gè)節(jié)點(diǎn)扛拨,初始化為 head
        while last.next: 
            # 待排序的節(jié)點(diǎn)
            current_node = last.next 
            
            # 找到 current_node 的前一個(gè)節(jié)點(diǎn) cur耘分,小于等于是為了保持排序的穩(wěn)定
            # 如果已排序區(qū)的值都 <= current_node ,則 cur 為 last
            # 如果已排序區(qū)存在大于 current_node 的節(jié)點(diǎn)绑警,則 cur 會(huì)變成該節(jié)點(diǎn)的前一個(gè)節(jié)點(diǎn)
            cur = new_head
            while cur != last and cur.next.val <= current_node.val:
                cur = cur.next
            
            # 插入 current_node 到正確位置
            if cur == last:
                # 更新已排序區(qū)的最后一個(gè)節(jié)點(diǎn)
                last = current_node
            else:
                # 先把 current_node 從鏈表中分離出來
                last.next = current_node.next
                # 再插入 current_node 到 cur 與 cur.next 之間
                current_node.next = cur.next
                cur.next = current_node                

        return new_head.next

空間復(fù)雜度 O(1)求泰,只是額外申請了幾個(gè)變量的空間。
時(shí)間復(fù)雜度 O(N^2):

  1. 最好情況下计盒,當(dāng)原來的鏈表是逆序時(shí)渴频,每次在已排序區(qū)只需要比較1次,總共只需要比較 N - 1 次北启,時(shí)間復(fù)雜度僅為 O(n)卜朗。
  2. 最差情況下,當(dāng)原來的鏈表是升序時(shí)咕村,對于第 i 個(gè)數(shù)场钉,需要比較 i - 1 次,總共需要比較 N*(N-1)/2 次懈涛,時(shí)間復(fù)雜度為 O(n^2)逛万。
  3. 當(dāng)鏈表是雜亂無章時(shí),時(shí)間復(fù)雜度為 O(n^2)批钠。

數(shù)組解法

可以考慮先將鏈表轉(zhuǎn)化成數(shù)組宇植,然后對數(shù)組使用插入排序得封,再重新轉(zhuǎn)化為鏈表。

# 數(shù)組解法
class Solution:
    def insertionSortList(self, head: ListNode) -> ListNode:
        if not head: return None
        
        # 將節(jié)點(diǎn)放入數(shù)組
        nodes = []
        node = head
        while node:
            nodes.append(node)
            node = node.next
        
        # 排序數(shù)組
        # nodes = sorted(nodes, key=lambda x: x.val)
        self.insertion_sort(nodes)

        # 更新指針
        for prev, cur in zip(nodes, nodes[1:]):
            prev.next = cur
            cur.next = None

        return nodes[0]
    
    def insertion_sort(self, arr): 
        for i in range(1, len(arr)): 
            key = arr[i] 
            j = i-1
            while j >= 0 and key.val < arr[j].val :
                arr[j + 1] = arr[j] 
                j -= 1
            arr[j + 1] = key

空間復(fù)雜度 O(N)当纱,創(chuàng)建了一個(gè)長度為 N 的數(shù)組呛每。
時(shí)間復(fù)雜度 O(N*logN),分為三個(gè)部分:

  1. 遍歷一遍并插入到數(shù)組中坡氯,遍歷的時(shí)間復(fù)雜度為 O(N)
  2. 排序的復(fù)雜度晨横,如果用插入排序的話就是 O(N^2)
  3. 更新指針,復(fù)雜度也是 O(N)

如果把排序部分用歸并排序或快排來實(shí)現(xiàn)的話箫柳,總體的時(shí)間復(fù)雜度就可以降為 O(N*logN)手形。

執(zhí)行時(shí)間差這么多,有點(diǎn)奇怪悯恍,看了 leetcode 評論區(qū)库糠,有人說是測試集有問題,好吧~

更多刷題涮毫,盡在 leetcode 專欄

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末瞬欧,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子罢防,更是在濱河造成了極大的恐慌艘虎,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,378評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件咒吐,死亡現(xiàn)場離奇詭異野建,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)恬叹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,970評論 3 399
  • 文/潘曉璐 我一進(jìn)店門候生,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人绽昼,你說我怎么就攤上這事唯鸭。” “怎么了硅确?”我有些...
    開封第一講書人閱讀 168,983評論 0 362
  • 文/不壞的土叔 我叫張陵肿孵,是天一觀的道長。 經(jīng)常有香客問我疏魏,道長停做,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,938評論 1 299
  • 正文 為了忘掉前任大莫,我火速辦了婚禮蛉腌,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己烙丛,他們只是感情好舅巷,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,955評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著河咽,像睡著了一般钠右。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上忘蟹,一...
    開封第一講書人閱讀 52,549評論 1 312
  • 那天飒房,我揣著相機(jī)與錄音,去河邊找鬼媚值。 笑死狠毯,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的褥芒。 我是一名探鬼主播嚼松,決...
    沈念sama閱讀 41,063評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼锰扶!你這毒婦竟也來了献酗?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,991評論 0 277
  • 序言:老撾萬榮一對情侶失蹤坷牛,失蹤者是張志新(化名)和其女友劉穎罕偎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體漓帅,經(jīng)...
    沈念sama閱讀 46,522評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡锨亏,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,604評論 3 342
  • 正文 我和宋清朗相戀三年痴怨,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了忙干。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,742評論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡浪藻,死狀恐怖捐迫,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情爱葵,我是刑警寧澤施戴,帶...
    沈念sama閱讀 36,413評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站萌丈,受9級特大地震影響赞哗,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜辆雾,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,094評論 3 335
  • 文/蒙蒙 一肪笋、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦藤乙、人聲如沸猜揪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,572評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽而姐。三九已至,卻和暖如春划咐,著一層夾襖步出監(jiān)牢的瞬間拴念,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,671評論 1 274
  • 我被黑心中介騙來泰國打工尖殃, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留丈莺,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,159評論 3 378
  • 正文 我出身青樓送丰,卻偏偏與公主長得像缔俄,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子器躏,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,747評論 2 361