[labuladong算法小抄]如何k個一組反轉鏈表

摘自labuladong算法小抄,使用go語言從新描述算法

以前的文章「遞歸反轉鏈表的一部分」講了如何遞歸地反轉一部分鏈表,有讀者就問如何迭代地反轉鏈表,這篇文章解決的問題也須要反轉鏈表的函數,咱們不妨就用迭代方式來解決。數據結構

本文要解決「K 個一組反轉鏈表」,不難理解:框架

 

這個問題常常在面經中看到,並且 LeetCode 上難度是 Hard,它真的有那麼難嗎?函數

對於基本數據結構的算法問題其實都不難,只要結合特色一點點拆解分析,通常都沒啥難點。下面咱們就來拆解一下這個問題。學習

1、分析問題

首先,前文學習數據結構的框架思惟提到過,鏈表是一種兼具遞歸和迭代性質的數據結構,認真思考一下能夠發現這個問題具備遞歸性質。動畫

什麼叫遞歸性質?直接上圖理解,好比說咱們對這個鏈表調用 reverseKGroup(head, 2),即以 2 個節點爲一組反轉鏈表:spa

 

 

 

 若是我設法把前 2 個節點反轉,那麼後面的那些節點怎麼處理?後面的這些節點也是一條鏈表,並且規模(長度)比原來這條鏈表小,這就叫子問題3d

 

 

咱們能夠直接遞歸調用 reverseKGroup(cur, 2),由於子問題和原問題的結構徹底相同,這就是所謂的遞歸性質。指針

發現了遞歸性質,就能夠獲得大體的算法流程:code

一、先反轉以 head 開頭的 k 個元素。

 

 

 

 二、將第 k + 1 個元素做爲 head 遞歸調用 reverseKGroup 函數。

 

 

 三、將上述兩個過程的結果鏈接起來

 

 

 

總體思路就是這樣了,最後一點值得注意的是,遞歸函數都有個 base case,對於這個問題是什麼呢?

題目說了,若是最後的元素不足 k 個,就保持不變。這就是 base case,待會會在代碼裏體現。

2、代碼實現

首先,咱們要實現一個 ReverseSingleList 函數反轉一個區間以內的元素。在此以前咱們再簡化一下,給定鏈表頭結點,如何反轉整個鏈表?

// 反轉以 a 爲頭結點的鏈表
func ReverseSingleList(a *ListNode) *ListNode {
    var (
        pre, cur, nxt *ListNode
    )
    pre = nil
    cur = a
    nxt = a
    for cur != nil {
        nxt = cur.next
        // 逐個結點反轉
        cur.next = pre
        // 更新指針位置
        pre = cur
        cur = nxt
    }
    // 返回反轉後的頭結點
    return pre
}

 

 

 

此次使用迭代思路來實現的,藉助動畫理解應該很容易。

「反轉以 a 爲頭結點的鏈表」其實就是「反轉 a 到 null 之間的結點」,那麼若是讓你「反轉 a 到 b 之間的結點」,你會不會?

只要更改函數簽名,並把上面的代碼中 null 改爲 b 便可:

/** 反轉區間 [a, b) 的元素,注意是左閉右開 */
func ReverseSingleList(a *ListNode, b *ListNode) *ListNode {
    var (
        pre, cur, nxt *ListNode
    )
    pre = nil
    cur = a
    nxt = a
    //終止的條件改一下就好了
    for cur != b {
        nxt = cur.next
        // 逐個結點反轉
        cur.next = pre
        // 更新指針位置
        pre = cur
        cur = nxt
    }
    // 返回反轉後的頭結點
    return pre
}

如今咱們迭代實現了反轉部分鏈表的功能,接下來就按照以前的邏輯編寫 reverseKGroup 函數便可:

func ReverseKGroup(head *ListNode, k int) *ListNode {
    if head == nil {
        return nil
    }
    // 區間 [a, b) 包含 k 個待反轉元素
    var (
        a, b *ListNode
    )
    b = head
    a = head
    for i := 0; i < k; i++ {
        // 不足 k 個,不須要反轉,base case
        if b == nil {
            return head
        }
        b = b.next
    }
    // 反轉前 k 個元素
    newHead := ReverseSingleList(a, b)
    // 遞歸反轉後續鏈表並鏈接起來
    a.next = ReverseKGroup(b, k)
    return newHead
}

解釋一下 for 循環以後的幾句代碼,注意 ReverseSingleList函數是反轉區間 [a, b),因此情形是這樣的:

 

 

 遞歸部分就不展開了,整個函數遞歸完成以後就是這個結果,徹底符合題意:

相關文章
相關標籤/搜索