如何k個一組反轉鏈表


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

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

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

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

一、分析問題

首先,前文學習數據結構的框架思維提到過,鏈表是一種兼具遞歸和迭代性質的數據結構,認真思考一下可以發現這個問題具有遞歸性質

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

如果我設法把前 2 個節點反轉,那么后面的那些節點怎么處理?后面的這些節點也是一條鏈表,而且規模(長度)比原來這條鏈表小,這就叫子問題

我們可以直接遞歸調用 reverseKGroup(cur, 2),因為子問題和原問題的結構完全相同,這就是所謂的遞歸性質。

發現了遞歸性質,就可以得到大致的算法流程:

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

2、將第 k + 1 個元素作為 head 遞歸調用 reverseKGroup 函數

3、將上述兩個過程的結果連接起來

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

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

二、代碼實現

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

// 反轉以 a 為頭結點的鏈表
ListNode reverse(ListNode a) {
    ListNode pre, cur, nxt;
    pre = null; cur = a; nxt = a;
    while (cur != null) {
        nxt = cur.next;
        // 逐個結點反轉
        cur.next = pre;
        // 更新指針位置
        pre = cur;
        cur = nxt;
    }
    // 返回反轉后的頭結點
    return pre;
}

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

「反轉以 a 為頭結點的鏈表」其實就是「反轉 a 到 null 之間的結點」,那么如果讓你「反轉 ab 之間的結點」,你會不會?

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

/** 反轉區間 [a, b) 的元素,注意是左閉右開 */
ListNode reverse(ListNode a, ListNode b) {
    ListNode pre, cur, nxt;
    pre = null; cur = a; nxt = a;
    // while 終止的條件改一下就行了
    while (cur != b) {
        nxt = cur.next;
        cur.next = pre;
        pre = cur;
        cur = nxt;
    }
    // 返回反轉后的頭結點
    return pre;
}

現在我們迭代實現了反轉部分鏈表的功能,接下來就按照之前的邏輯編寫 reverseKGroup 函數即可:

ListNode reverseKGroup(ListNode head, int k) {
    if (head == null) return null;
    // 區間 [a, b) 包含 k 個待反轉元素
    ListNode a, b;
    a = b = head;
    for (int i = 0; i < k; i++) {
        // 不足 k 個,不需要反轉,base case
        if (b == null) return head;
        b = b.next;
    }
    // 反轉前 k 個元素
    ListNode newHead = reverse(a, b);
    // 遞歸反轉后續鏈表並連接起來
    a.next = reverseKGroup(b, k);
    return newHead;
}

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

遞歸部分就不展開了,整個函數遞歸完成之后就是這個結果,完全符合題意:

三、最后說兩句

從閱讀量上看,基本數據結構相關的算法文章看的人都不多,我想說這是要吃虧的。

大家喜歡看動態規划相關的問題,可能因為面試很常見,但就我個人理解,很多算法思想都是源於數據結構的。我們公眾號的成名之作之一,「學習數據結構的框架思維」就提過,什么動規、回溯、分治算法,其實都是樹的遍歷,樹這種結構它不就是個多叉鏈表嗎?你能處理基本數據結構的問題,解決一般的算法問題應該也不會太費事。

那么如何分解問題、發現遞歸性質呢?這個只能多練習,也許后續可以專門寫一篇文章來探討一下,本文就到此為止吧,希望對大家有幫助!

我最近精心制作了一份電子書《labuladong的算法小抄》,分為【動態規划】【數據結構】【算法思維】【高頻面試】四個章節,共 60 多篇原創文章,絕對精品!限時開放下載,在我的公眾號 labuladong 后台回復關鍵詞【pdf】即可免費下載!

目錄

歡迎關注我的公眾號 labuladong,技術公眾號的清流,堅持原創,致力於把問題講清楚!

labuladong


免責聲明!

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



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