[LeetCode] #25 Reverse Nodes in k-Group


Problem

給一個 linked list,以連續 k 個為一組,將每組都 revert,所最後出現不滿 k 個的組,該組維持不變

  • Link
  • 等級:Hard
  • 推薦指數:[] Revert linked list 升級版

有人推薦過的題目的我才會紀錄,所以即使我覺得只有一顆星他依舊是一題有其他人推薦的題目,只是我自己不覺得需要刷
代表我覺得有時間再看就好
代表可以刷
代表一定刷
代表多刷幾次甚至把所有 solution 都弄懂


Revert linked list

既然他是 revert linked list 的升級版,那我們就先把 revert linked list 做出來
revert linked list 有兩種作法,recursion 或 iteration,我們先用 recursion

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next

def reverse_recursion(node, relink): # return new head
    if node.next:
        ret = reverse_recursion(node.next, node)
    else: # termination
        ret = node
    node.next = relink
    return ret

# usage: reverse_recursion(head, None)

Revert first k nodes of a linked list

上面已經能夠將一個 linked list 給反過來,接著我們進一步把它改成只針對前 k 個動作

def reverse_recursion(node, relink, k): # return new head
    if k == 1: # termination. revert first 1 node means no revert operation
        ret = node
    else: # k>1
        if node.next:
            ret = reverse_recursion(node.next, node, k-1)
        else:
            ret = None
    if ret:
        node.next = relink
    return ret

# usage: reverse_recursion(head, None, 4) to revert first 4 nodes.

注意到,如果不夠 k 個,我們藉由返還 None 的方式,因為第 16 行的 reassign 是在每個 recursion 的最尾端才做,所以即使是走到最後才發現不夠,None 會一路傳回去,所以就有能維持整個 list 沒有任何變動

上面的 reverse_recursion(head, None, 4) 會返還一個 new_head,並且為長度 4 的 linked list

但是,如果 linked list 還有剩下,我們其實應該也要想辦法把剩下的返還,因為雖然目前還用不到,但未來肯定會用到
比如 A -> B -> C -> D -> E -> F,當 k 是 3
那上面的 reverse_recursion 答案會給出 C -> B -> A
但我們想要 C -> B -> A -> D -> E -> F
因為 D 原本是連在 C 上面,但 C 後來要變成指向 B,我們也不能因此失去 D 的資訊吧,所以我們應該要將 D 接到 A 上,我們把 D 叫做 next_node
把 code 做點小改變,變成

def reverse_recursion(node, relink, k): # return (new_head, next_node)                
    if k == 1: # termination
        ret = (node, node.next)
    else: # k>1
        if node.next:
            ret = reverse_recursion(node.next, node, k-1)
        else:
            ret = None
    if ret:
        node.next = relink
        if relink is None:
            node.next = ret[1]
    return ret

# usage: reverse_recursion(head, None, 3) to revert first 3 nodes.

第 12 行就把 D 接上去了

Revert grouped k nodes linked list

接下來我們就要把 input 的 linked list 一批一批呼叫這個 reverse() 來把他們反轉
且因為我們的 reverse() 會 return new_head 和 next_node,所以我們總是知道要接下來要處理的下一批的頭是誰,也知道該怎麼把已經處理完的上一批的尾,跟已經處理完的下一批的頭連起來
一樣的常見技巧是我們創造一個 dummy node 當作頭,用來連到已經處理完的部分

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def reverseKGroup(self, head: Optional[ListNode], k: int) -> Optional[ListNode]:
        def reverse(node, relink, k): # return (new_head, next_node)                
            if k == 1: # termination
                ret = (node, node.next)
            else: # k>1
                if node.next:
                    ret = reverse(node.next, node, k-1)
                else:
                    ret = None
            if ret:
                node.next = relink
                if relink is None:
                    node.next = ret[1]
            return ret
        
        #return reverse(head, None, 3)[0]

        ''' k=3
        [dummy] [0] [1] [2] [3] [4] [5] [6]
                 |_______|         
          tail
            reverse([0]) -> ([2], [3])
                [2] [1] [0] [3] [4] [5] [6]
            
                tail.next = [2] # dummy.next = [2]
                tail = [0]
        
        [dummy] [2] [1] [0] [3] [4] [5] [6]
                             |_______|
                        tail
            reverse([3]) -> ([5], [6])
                            [5] [4] [3] [6]
            
                tail.next = [5] # [0].next = [5]
                tail = [3]
        
    Temination case A: if the linked list length % k != 0
        [dummy] [2] [1] [0] [5] [4] [3] [6]
                                         |_|
            reverse([6]) -> None
                                    tail
                # do nothing and break loop
        
    Temination case B: if the linked list length % k == 0
        [dummy] [2] [1] [0] [5] [4] [3]
                                    tail
                 # do nothing and break loop
        '''
        dummy = ListNode(val=0, next=head)
        tail = dummy # point to the tail of the finish part. Initialize it as a dummy node.
        while tail.next: # Temination case B
            res = reverse(tail.next, None, k)
            if res is not None:
                tail.next, tail = res[0], tail.next
            else:
                break # Temination case A

        return dummy.next

Revert linked list (Iteration)

一開始有提到 revert linked list 有兩種作法,recursion 或 iteration,我們也試一下 iteration

Revert whole linked list

一次處理一個 node,用兩個指標 prevcur

 '''
     A -> B -> None
prev cur

     None <- A, B -> None
           prev cur

     None <- A <- B
             prev cur
 '''
 def reverse_iteration(node): # return new head
     cur = node
     prev = None
     while cur:
         temp = cur.next
         cur.next = prev
         prev = cur
         cur = temp
     return prev
 
 #usage: reverse_iteration(head)'''

Revert first k nodes of a linked list

def reverse_iteration(node, k): # return (new_head, next_node)
    cur = node
    prev = None
    count = 0
    while cur is not None and count < k:
        temp = cur.next
        cur.next = prev
        prev = cur
        cur = temp
        count += 1

    if count != k: # rollback
        reverse_iteration(prev, count)
        return None
                
    node.next = cur

    return (prev, cur)

#usage: reverse_iteration(head, 3)

因為是從頭開始走,一路直接就改了 link,所以如果到最後才發現不夠,那就要做 rollback 來恢復原狀

另外一種方法是先直接走到最後,再一路改回來,也是可行,如果剩餘 linked list 長度夠,也就是 n >= k,時間就是 2k,如果不夠,時間就是 n
如果是採用上面這段 code 的 rollback 方法,當剩餘 linked list 長度夠,時間就是 k,如果不夠,時間就是 2n
合理預期在跑一筆測資的時候,剩餘長度夠的狀況應該是比較頻繁的,也就是理論上都是先成功反轉了好幾組,到最後一組才有可能會發生不夠的情形,所以選擇 rollback 的方法應該更好

Revert grouped k nodes linked list

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def reverseKGroup(self, head: Optional[ListNode], k: int) -> Optional[ListNode]:
        def reverse_iteration(node, k): # return (new_head, next_node)
            cur = node
            prev = None
            count = 0
            while cur is not None and count < k:
                temp = cur.next
                cur.next = prev
                prev = cur
                cur = temp
                count += 1

            if count != k: # rollback
                reverse_iteration(prev, count)
                return None                
                
            return (prev, cur)
        
        #return reverse_iteration(head, 3)[0]
        
        dummy = ListNode(val=0, next=head)
        tail = dummy # point to the tail of the finish part. Initialize it as a dummy node.
        while tail.next: # Temination case B
            res = reverse_iteration(tail.next, k)
            if res is not None:
                tail.next, tail = res[0], tail.next
            else:
                break # Temination case A

        return dummy.next

因為我們的 reverse_iteration 和之前的 reverse_recursion 的功能完全相同,所以其他部分的邏輯完全不需要改變

C++

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* reverseKGroup(ListNode* head, int k) {        
        ListNode *cur = head;
        ListNode dummy = ListNode();
        ListNode *tail = &dummy;
        while (cur) {
            //tail->next = reverseResursive(cur, nullptr, k, nullptr);
            tail->next = reverseIteration(cur, k);
            if (tail->next == nullptr) {
                tail->next = cur;
                break;
            }
            tail = cur;
            cur = cur->next;
        }
        
        return dummy.next;
    }
    
    ListNode* reverseResursive(ListNode* node, ListNode* prev, int k, ListNode** next) {
        if (node == nullptr) return nullptr;
        if (k<1) return node;

        if (prev == nullptr && next == nullptr) next = new ListNode*;
        
        ListNode *ret;
        if (k == 1) {
            ret = node;
            *next = node->next;
        } else if (node == nullptr) {
            ret = nullptr;
        } else {
            ret = reverseResursive(node->next, node, k-1, next);
        }

        if (ret != nullptr) {
            node->next = prev;
            if (prev == nullptr) node->next = *next;
        }
        
        if (prev == nullptr) delete next;

        return ret;
    }
    
    /*
    A -> B -> C -> NULL
        cur = A
            A.next = NULL
        cur = B
            B.next = A
        cur = C
            C.next = B
        cur = NULL
    */
    ListNode* reverseIteration(ListNode* head, int k) {
        if (k<2) return head;

        ListNode *cur = head;
        ListNode *prev = nullptr;
        ListNode *temp;
        int n = 0;
        while (n < k) {
            if (cur == nullptr) {
                reverseIteration(prev, n);
                return nullptr;
            }

            temp = cur->next;
            cur->next = prev;
            n++;

            prev = cur;
            cur = temp;
        }
        head->next = cur;
        return prev;
    }
};

其實先走一次,知道了總長之後,就可以比較簡單的處裡尾部的那個 group,並且也不會影響到時間複雜度


Author: Chris Chung
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint policy. If reproduced, please indicate source Chris Chung !
  TOC