链表:重排链表、合并 K 个升序链表、K 个一组翻转链表

我们这次来写几道经典的链表题目。

重排链表

题目描述

重排链表

给定一个单链表 L 的头节点 head ,单链表 L 表示为:

L0 → L1 → ... → Ln - 1 → Ln

请将其重新排列后变为:

L0 → Ln → L1 → Ln - 1 → L2 → Ln - 2 → ...

不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。

示例 1:

输入:head = 1,2,3,4

输出:1,4,2,3

示例 2:

输入:head = 1,2,3,4,5

输出:1,5,2,4,3

提示:

  • 链表的长度范围为 1, 5 \* 104
  • 1 <= node.val <= 1000

算法原理

根据题目描述,我们实际上是如此插入:

实际上我们是将后半部分,逆序插入前半部分中:

  • 那么我们实际上可以先将链表从中间断开,然后逆序后半部分,最后合并即可。
  1. 首先我们要找到断开的位置,我们考虑奇数情况和偶数情况,偶数情况如上,奇数如下:

    不难发现,我们要断开的结点就是最中间结点,或者中间左边结点。

那么我们考虑使用这样的快慢指针:

fast直接从头结点的下一个结点出发,最后fast为空或者fast->next为空终止。

这样slow指向正是我们要断开的地方。

  1. 然后我们要逆序后半部分,这显然是非常简单的。这里考虑头插法逆序:

我们先创建一个哨兵位头结点:

然后将上面的链表逐个头插到下面:

最后将头结点释放即可。

  1. 合并

合并这个太简单了,参考归并

我们遍历两个链表,顺序尾插到一个新的头结点后即可。当然也可以原地插入。

算法实现

cpp 复制代码
/**
 * 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:
    void reorderList(ListNode* head) {
        if(!head || !head->next)return;
        //找中点
        ListNode *slow = head, *fast = head->next;
        while(fast && fast->next){
            slow = slow->next;
            fast = fast->next->next;
        }
        fast = slow->next;
        slow->next = nullptr;//断开两链表
        slow=head;
        //逆序后半部分
        ListNode *newhead =new ListNode, *cur = fast, *next;
        while(cur){
            next = cur->next;
            cur->next = newhead->next;
            newhead->next = cur;
            cur = next;
        }
        fast = newhead->next;
        delete newhead;
        //合并两链表
        ListNode *next1, *next2;
        while(fast){
            next1=slow->next;
            next2=fast->next;
            slow->next=fast;
            fast->next=next1;
            slow=next1;
            fast=next2;
        }
    }
};

合并 K 个升序链表

题目描述

合并 K 个升序链表

给你一个链表数组,每个链表都已经按升序排列。

请你将所有链表合并到一个升序链表中,返回合并后的链表。

示例 1:

输入:lists = \[1,4,5,1,3,4,2,6]

输出:1,1,2,3,4,4,5,6

解释:链表数组如下:

1-\>4-\>5, 1-\>3-\>4, 2-\>6

将它们合并到一个有序链表中得到。

1->1->2->3->4->4->5->6

示例 2:

输入:lists = \[\]

输出:\[\]

示例 3:

输入:lists = \[]

输出:\[\]

提示:

  • k == lists.length
  • 0 <= k <= 10^4
  • 0 <= listsi.length <= 500
  • -10^4 <= listsij <= 10^4
  • listsi 按 升序 排列
  • listsi.length 的总和不超过 10^4

算法原理

我们可以维护一个大小为k的小根堆,每次从堆顶获取结点插入到新头结点中。

然后插入头结点的next结点,除非为空。

直到堆为空终止。

那么时间复杂度就是O(nklogk)。其中nk是节点元素个数,logk是堆调整的开销。

  • 分治

我们可以将k个链表分成一半,分别合并这两半,最后再合并再一起。

参考归并的思想。

算法实现

cpp 复制代码
/**
 * 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:
    struct cmp {
        bool operator()(const ListNode* l, const ListNode* r) {
            return l->val > r->val;
        }
    };
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        priority_queue<ListNode*, vector<ListNode*>, cmp> heap;
        for (auto& e : lists)
            if(e)heap.push(e);
        ListNode *head = new ListNode, *tail = head;
        while (heap.size()) {
            ListNode* temp = heap.top();
            heap.pop();
            if (temp->next)
                heap.push(temp->next);
            tail->next = temp;
            temp->next = nullptr;
            tail = temp;
        }
        tail = head->next;
        delete head;
        return tail;
    }
};

分治

cpp 复制代码
/**
 * 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* mergeKLists(vector<ListNode*>& lists) {
        return merge(lists, 0, lists.size() - 1);
    }

    ListNode* merge(vector<ListNode*>& lists, int left, int right) {
        if (left > right)
            return nullptr;
        if (left == right)
            return lists[left];

        int mid = left + (right - left) / 2;

        ListNode* list1 = merge(lists, left, mid);
        ListNode* list2 = merge(lists, mid + 1, right);

        ListNode *head = new ListNode, *tail = head;

        while (list1 && list2) {
            if (list1->val < list2->val) {
                tail->next = list1;
                list1 = list1->next;
                tail = tail->next;
                tail->next = nullptr;
            } else {
                tail->next = list2;
                list2 = list2->next;
                tail = tail->next;
                tail->next = nullptr;
            }
        }
        list1 = list1 ? list1 : list2;
        while (list1) {
            tail->next = list1;
            list1 = list1->next;
            tail = tail->next;
            tail->next = nullptr;
        }
        tail = head->next;
        delete head;
        return tail;
    }
};

K 个一组翻转链表

题目描述

K 个一组翻转链表

给你链表的头节点 head ,每 k 个节点一组进行翻转,请你返回修改后的链表。

k 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。

你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。

示例 1:

输入:head = 1,2,3,4,5, k = 2

输出:2,1,4,3,5

示例 2:

输入:head = 1,2,3,4,5, k = 3

输出:3,2,1,4,5

提示:

链表中的节点数目为 n

  • 1 <= k <= n <= 5000
  • 0 <= Node.val <= 1000

进阶:你可以设计一个只用 O(1) 额外内存空间的算法解决此问题吗?

算法原理

  • 递归

我们之间将前k个结点翻转,然后剩下部分交给递归解决即可。不过这样的空间开销比较大,也可以换成迭代方式实现。迭代的话要先求出翻转的次数。

算法实现

cpp 复制代码
/**
 * 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) {
        if(!head)return head;
        ListNode* tail = head;
        int n = k - 1;
        while (n--) {
            if (!tail || !tail->next)
                return head;
            tail = tail->next;
        }
        ListNode* next = reverseKGroup(tail->next, k);

        tail->next = nullptr;
        reverse(head);
        head->next = next;
        return tail;
    }
    void reverse(ListNode* head) {
        ListNode *newhead = new ListNode, *next;
        while (head) {
            next = head->next;
            head->next = newhead->next;
            newhead->next = head;
            head = next;
        }
        delete newhead;
    }
};
相关推荐
疯狂打码的少年1 天前
【数据结构】栈:定义、顺序栈与链式栈
数据结构·笔记
wabs6661 天前
关于图论【最短路径之Bellman_ford 算法|卡码网94.城市间货物运输的思考】
数据结构·算法·图论·卡码网·bellman_ford·求最短路径
今日无bug1 天前
JS 数据类型 + 内存分配:从 8 种类型到栈堆模型
javascript·数据结构
wabs6661 天前
关于哈希表【力扣454.四数相加II的思考】
数据结构·算法·leetcode·散列表
Forever Nore1 天前
LeetCode 2 两数相加 - 链表
leetcode·链表·哈希表
白狐_7981 天前
408数据结构第7章:查找①——顺序查找、折半查找、分块查找知识总结
数据结构
ShineWinsu1 天前
对于C++中unordered_map的详细介绍
数据结构·c++·算法·面试·stl·哈希表·unordered_map
lingran__1 天前
C++ 高阶数据结构:AVL 树万字详解|原理、四种旋转、key‑value 实现、调试校验、set/map/红黑树 底层前置知识
数据结构·c++·二叉搜索树·平衡二叉树·avl树·红黑树前置
疯狂打码的少年1 天前
【数据结构】顺序表 vs 链表的对比与选择
数据结构·笔记·链表
wabs6661 天前
关于图论【最短路径之Dijkstra算法(堆优化版)|卡码网47.参加科学大会的思考】
数据结构·算法·图论·优先级队列·邻接表·小顶堆·卡码网