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;
    }
};

发布了47 篇原创文章 · 获赞 21 · 访问量 7222

猜你喜欢

转载自blog.csdn.net/qq_18315295/article/details/103774451