1. 程式人生 > >LeetCode 61——旋轉連結串列

LeetCode 61——旋轉連結串列

1. 題目

2. 解答

2.1. 方法一

將連結串列每個節點向右移動 1 個位置,其實就是讓連結串列最後一個結點指向第一個結點。

因此,向右移動 k 個位置就重複上述過程 k 次即可。

然後,我們注意到,若連結串列有 n 個結點,則移動 n 次後就還是原連結串列。

原始連結串列 1->2->3->NULL
向右旋轉 1 步: 3->1->2->NULL
向右旋轉 2 步: 2->3->1->NULL
向右旋轉 3 步: 1->2->3->NULL

實際上,若連結串列有 n 個結點,我們只需要移動 n % k 次即可。

確定連結串列有多少個結點,我們則可以用快慢指標法。

  • 偶數個結點時,結點個數等於 i * 2。

  • 奇數個結點時,結點個數等於 i * 2 + 1。
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* rotateRight
(ListNode* head, int k) { if (head == NULL || head->next == NULL) return head; int node_num = 0; // 連結串列的結點個數 ListNode* slow = head; ListNode* fast = head; // 利用快慢指標確定連結串列的結點總個數 while(fast && fast->next) { slow =
slow->next; fast = fast->next->next; node_num++; } if (fast) // 奇數個結點 { node_num = node_num * 2 + 1; } else // 偶數個結點 { node_num = node_num * 2; } // 若連結串列有 K 個結點,向右移動 K 個位置就還是原連結串列 // 因此我們只需要移動 K % node_num 次即可 if (k % node_num == 0) return head; else k = k % node_num; ListNode *temp = head; ListNode *last_node = NULL; for (int i = 0; i < k; i++) { // 向右移動 1 個位置就是讓最後一個結點指向第一個結點 // 先找到倒數第二個結點 while (temp->next->next) { temp = temp->next; } // 最後一個結點指向第一個結點,倒數第二個節點指向 NULL last_node = temp->next; temp->next = NULL; last_node->next = head; head = last_node; temp = head; } return head; } };

2.2. 方法二

上面的演算法中每次將連結串列結點向右移動 1 個位置的時候,我們都要遍歷一次連結串列,效率較低。

針對連結串列 1->2->3->4->5->NULL,如果要將連結串列每個結點向右移動 2 個位置,那倒數第 2 個結點就是旋轉後新連結串列的起始位置;如果要將連結串列每個結點向右移動 3 個位置,那倒數第 3 個結點就是旋轉後新連結串列的起始位置;而如果要將連結串列每個結點向右移動 33 個位置,那倒數第 3 個結點就是旋轉後新連結串列的起始位置。

更一般的情況下,若連結串列長度為 n,移動次數為 k,旋轉後連結串列的起始位置就是原連結串列倒數第 k % n 個結點

在上面的例子中,假設新連結串列的起始位置是結點 4,那連結串列就被分成了兩部分。1->2->3 和 4->5->NULL,我們要做的就是把 1->2->3 拼接在 4->5->NULL 後即可。

實現思路是這樣的:

我們先通過快慢指標確定連結串列長度 n,然後找到旋轉後連結串列起始位置的上一個結點,記為 new_head_last,然後從起始位置 new_head 開始向後遍歷,當到達 NULL 時,將 head 指向的頭結點拼接在 new_head 後面,然後再將新連結串列的最後一個結點也即 new_head_last 後面置為 NULL 即可。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* rotateRight(ListNode* head, int k) {
        
        if (head == NULL || head->next == NULL) return head;
        
        int node_num = 0; // 連結串列的結點個數
        int mid_num = 0;
        ListNode* slow = head;
        ListNode* fast = head;

        // 利用快慢指標確定連結串列的結點總個數
        while(fast && fast->next)
        {
            slow = slow->next;
            fast = fast->next->next;
            mid_num++;
        }

        if (fast) // 奇數個結點
        {
            node_num = mid_num * 2 + 1;
        }
        else // 偶數個結點
        {
            node_num = mid_num * 2;
        }
        
        // 若連結串列有 K 個結點,向右移動 K 個位置就還是原連結串列
        // 因此我們只需要移動 K % node_num 次即可
        if (k % node_num == 0) return head;
        else k = k % node_num;
        
        int which_node_is_new = 0; // 旋轉後的頭結點是第幾個節點
        ListNode* new_head = NULL;
        ListNode* new_head_last = NULL;
 
        // 查詢旋轉後頭結點的上一個結點
        // 此時 slow 指標指向中間結點,分為在 slow 前還是在 slow 後
        which_node_is_new = node_num - k; 
        if (which_node_is_new > node_num / 2)
        {
            new_head_last = slow;
            for (int i = 0; i < which_node_is_new - mid_num - 1; i++)
            {
                new_head_last = new_head_last->next;
            }
        }
        else
        {
            new_head_last = head;
            for (int i = 1; i < which_node_is_new; i++)
            {
                new_head_last = new_head_last->next;
            } 
        }

        new_head = new_head_last->next;
        ListNode* temp = new_head;
        
        while (temp->next)
        {
            temp = temp->next;
        }
        
        temp->next = head;
        new_head_last->next = NULL;
        
        return new_head;
    }
};

獲取更多精彩,請關注「seniusen」!