LeetCode刷題(十八)-----鏈表-------medium部分(Java、C++)

148. 排序鏈表

在O(nlogn)時間複雜度和常數級空間複雜度下,對鏈表進行排序。
示例 1:
輸入: 4->2->1->3
輸出: 1->2->3->4
示例 2:
輸入: -1->5->3->4->0
輸出: -1->0->3->4->5

思路一:排序鏈表-bottom-to-up O(1) 空間
由於題目要求空間複雜度是 O(1),因此不能使用遞歸。因此這裏使用 bottom-to-up 的算法來解決。
bottom-to-up 的歸併思路是這樣的:先兩個兩個的 merge,完成一趟後,再 4 個4個的 merge,直到結束。舉個簡單的例子:[4,3,1,7,8,9,2,11,5,6].
在這裏插入圖片描述
鏈表裏操作最難掌握的應該就是各種斷鏈啊,然後再掛接啊。在這裏,我們主要用到鏈表操作的兩個技術:
1.merge(l1, l2),雙路歸併,我相信這個操作大家已經非常熟練的,就不做介紹了。
2.cut(l, n),可能有些同學沒有聽說過,它其實就是一種 split 操作,即斷鏈操作。不過我感覺使用 cut 更準確一些,它表示,將鏈表 l 切掉前 n 個節點,並返回後半部分的鏈表頭。
3.額外再補充一個 dummyHead 大法,已經講過無數次了,仔細體會吧。
希望同學們能把雙路歸併和 cut 斷鏈的代碼爛記於心,以後看到類似的題目能夠刷到手軟。
掌握了這三大神器後,我們的 bottom-to-up 算法僞代碼就十分清晰了:
在這裏插入圖片描述
好了,下面是比較正式的代碼。

class Solution {
public:
    ListNode* sortList(ListNode* head) {
        ListNode dummyHead(0);
        dummyHead.next = head;
        auto p = head;
        int length = 0;
        while (p) {
            ++length;
            p = p->next;
        }
        
        for (int size = 1; size < length; size <<= 1) {
            auto cur = dummyHead.next;
            auto tail = &dummyHead;
            
            while (cur) {
                auto left = cur;
                auto right = cut(left, size); // left->@->@ right->@->@->@...
                cur = cut(right, size); // left->@->@ right->@->@  cur->@->...
                
                tail->next = merge(left, right);
                while (tail->next) {
                    tail = tail->next;
                }
            }
        }
        return dummyHead.next;
    }
    
    ListNode* cut(ListNode* head, int n) {
        auto p = head;
        while (--n && p) {
            p = p->next;
        }
        
        if (!p) return nullptr;
        
        auto next = p->next;
        p->next = nullptr;
        return next;
    }
    
    ListNode* merge(ListNode* l1, ListNode* l2) {
        ListNode dummyHead(0);
        auto p = &dummyHead;
        while (l1 && l2) {
            if (l1->val < l2->val) {
                p->next = l1;
                p = l1;
                l1 = l1->next;       
            } else {
                p->next = l2;
                p = l2;
                l2 = l2->next;
            }
        }
        p->next = l1 ? l1 : l2;
        return dummyHead.next;
    }
};

作者:ivan_allen
鏈接:https://leetcode-cn.com/problems/sort-list/solution/148-pai-xu-lian-biao-bottom-to-up-o1-kong-jian-by-/
來源:力扣(LeetCode)
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。

思路二:
解答一:歸併排序(遞歸法)
題目要求時間空間複雜度分別爲O(nlogn)和O(1),根據時間複雜度我們自然想到二分法,從而聯想到歸併排序;

對數組做歸併排序的空間複雜度爲 O(n),分別由新開闢數組O(n)和遞歸函數調用O(logn)組成,而根據鏈表特性:

數組額外空間:鏈表可以通過修改引用來更改節點順序,無需像數組一樣開闢額外空間;

遞歸額外空間:遞歸調用函數將帶來O(logn)的空間複雜度,因此若希望達到O(1)空間複雜度,則不能使用遞歸。

通過遞歸實現鏈表歸併排序,有以下兩個環節:
分割 cut 環節: 找到當前鏈表中點,並從中點將鏈表斷開(以便在下次遞歸 cut 時,鏈表片段擁有正確邊界);
我們使用 fast,slow 快慢雙指針法,奇數個節點找到中點,偶數個節點找到中心左邊的節點。
找到中點 slow 後,執行 slow.next = None 將鏈表切斷。
遞歸分割時,輸入當前鏈表左端點 head 和中心節點 slow 的下一個節點 tmp(因爲鏈表是從 slow 切斷的)。

cut 遞歸終止條件: 當head.next == None時,說明只有一個節點了,直接返回此節點。

合併 merge 環節: 將兩個排序鏈表合併,轉化爲一個排序鏈表。雙指針法合併,建立輔助ListNode h 作爲頭部。

設置兩指針 left, right 分別指向兩鏈表頭部,比較兩指針處節點值大小,由小到大加入合併鏈表頭部,指針交替前進,直至添加完兩個鏈表。

返回輔助ListNode h 作爲頭部的下個節點 h.next。

時間複雜度 O(l + r),l, r 分別代表兩個鏈表長度。
當題目輸入的 head == None 時,直接返回None。

作者:jyd
鏈接:https://leetcode-cn.com/problems/sort-list/solution/sort-list-gui-bing-pai-xu-lian-biao-by-jyd/
來源:力扣(LeetCode)
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。

思路三:
解:由於複雜度的限定,其實就是用歸併排序求解
歸併排序三部曲:
• fast-slow找中點:直到快的走到了末尾,然後慢的所在位置就是中間位置,這樣就分成了兩段
• 將鏈表分成兩部分
• 合併兩個有序鏈表

class Solution {
public:
    ListNode* sortList(ListNode* head) {
        return mergesort(head);
    }
    ListNode* mergesort(ListNode* node)
    {
        if(!node || !node->next) return node;
        ListNode *fast=node;//快指針走兩步
        ListNode *slow=node;//慢指針走一步
        ListNode *brek=node;//斷點
        while(fast && fast->next)
        {
            fast=fast->next->next;
            brek=slow;
            slow=slow->next;
        }
        brek->next=nullptr;
        ListNode *l1=mergesort(node);
        ListNode *l2=mergesort(slow);
        return merge(l1,l2);
    }
    ListNode* merge(ListNode* l1,ListNode* l2)
    {
        if(l1==NULL)
        {
            return l2;
        }
        if(l2==NULL)
        {
            return l1;
        }
        if(l1->val < l2->val)
        {
            l1->next=merge(l1->next,l2);
            return l1;
        }
        else
        {
            l2->next=merge(l2->next,l1);
            return l2;
        }
    }
};

作者:chenlele
鏈接:https://leetcode-cn.com/problems/sort-list/solution/pai-xu-lian-biao-by-gpe3dbjds1/
來源:力扣(LeetCode)
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。

我的:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* sortList(ListNode* head) 
    {
        return mergesort(head);     
    }
    ListNode* mergesort(ListNode* node)
    {
        if(!node || !node -> next)
        {
            return node;
        }
        ListNode *fast = node;
        ListNode *slow = node;
        ListNode *brek = node;
        while(fast && fast -> next)
        {
            fast = fast -> next -> next;
            brek = slow;
            slow = slow -> next;
        }
        brek -> next = nullptr;
        ListNode *l1 = mergesort(node);
        ListNode *l2 = mergesort(slow);
        return merge(l1,l2);
    }
    ListNode* merge(ListNode* l1,ListNode* l2)
    {
        if(l1 == NULL)
        {
            return l2;
        }
        if(l2 == NULL)
        {
            return l1;
        }
        if(l1-> val < l2->val)
        {
            l1->next=merge(l1->next,l2);
            return l1;
        }
        else
        {
            l2 -> next = merge(l2 -> next,l1);
            return l2;
        }
    }
};

328. 奇偶鏈表

給定一個單鏈表,把所有的奇數節點和偶數節點分別排在一起。請注意,這裏的奇數節點和偶數節點指的是節點編號的奇偶性,而不是節點的值的奇偶性。
請嘗試使用原地算法完成。你的算法的空間複雜度應爲 O(1),時間複雜度應爲 O(nodes),nodes 爲節點總數。
示例 1:
輸入: 1->2->3->4->5->NULL
輸出: 1->3->5->2->4->NULL
示例 2:
輸入: 2->1->3->5->6->4->7->NULL 
輸出: 2->3->6->7->1->5->4->NULL
說明:
•	應當保持奇數節點和偶數節點的相對順序。
•	鏈表的第一個節點視爲奇數節點,第二個節點視爲偶數節點,以此類推。

思路一:奇偶鏈表
解法
想法
將奇節點放在一個鏈表裏,偶鏈表放在另一個鏈表裏。然後把偶鏈表接在奇鏈表的尾部。
算法
一個LinkedList 需要一個頭指針和一個尾指針來支持雙端操作。我們用變量 head 和 odd 保存奇鏈表的頭和尾指針。 evenHead 和 even 保存偶鏈表的頭和尾指針。算法會遍歷原鏈表一次並把奇節點放到奇鏈表裏去、偶節點放到偶鏈表裏去。遍歷整個鏈表我們至少需要一個指針作爲迭代器。這裏 odd 指針和 even 指針不僅僅是尾指針,也可以扮演原鏈表迭代器的角色。

解決鏈表問題最好的辦法是在腦中或者紙上把鏈表畫出來。比方說:
在這裏插入圖片描述
圖片 1. 奇偶鏈表的例子
在這裏插入圖片描述
複雜度分析
• 時間複雜度: O(n)。總共有 n個節點,我們每個遍歷一次。
• 空間複雜度: O(1)。我們只需要 4 個指針。

作者:LeetCode
鏈接:https://leetcode-cn.com/problems/odd-even-linked-list/solution/qi-ou-lian-biao-by-leetcode/
來源:力扣(LeetCode)
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。

我的:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* oddEvenList(ListNode* head) 
    {
        if(head == NULL)
        {
            return NULL;
        }     
        ListNode *odd = head;
        ListNode *even = head -> next;
        ListNode *evenHead = even;
        while(even != NULL && even -> next != NULL)
        {
            odd -> next = even -> next;
            odd = odd -> next;
            even -> next = odd -> next;
            even = even -> next;
        }
        odd -> next = evenHead;
        return head;
    }
};

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章