LeetCode 23 合并 K 个升序链表

news2024/12/23 13:11:21

LeetCode 23 合并 K 个升序链表

来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/merge-k-sorted-lists/description/

博主Github:https://github.com/GDUT-Rp/LeetCode

题目:

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

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

示例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 <= 1 0 4 10^4 104
  • 0 <= lists[i].length <= 500
  • − 1 0 4 -10^4 104 <= lists[i][j] <= 1 0 4 10^4 104
  • lists[i] 按 升序 排列
  • lists[i].length 的总和不超过 10^4

解题思路:

方法一:顺序合并

用一个变量 ans 来维护以及合并的链表,第 i 次循环把第 i 个链表和 ans 合并,答案保存到 ans 中。

Golang

/**
 * Definition for singly-linked list.
 * type ListNode struct {
 *     Val int
 *     Next *ListNode
 * }
 */
func mergeKLists(lists []*ListNode) *ListNode {
    var ans *ListNode
    for i:=0; i<len(lists); i++ {
        ans = mergeTwoLists(ans, lists[i])
    }
    return ans
}

func mergeTwoLists(a *ListNode, b *ListNode) *ListNode {
    if a == nil {
        return b
    }
    if b == nil {
        return a
    }

    var head ListNode
    tail := &head

    for (a != nil && b != nil) {
        if a.Val < b.Val {
            tail.Next = a
            a = a.Next
        } else {
            tail.Next = b
            b = b.Next
        }
        tail = tail.Next
    }

    if a != nil {
        tail.Next = a
    } else {
        tail.Next = b
    }
    return head.Next
}

C++

class Solution {
public:
    ListNode* mergeTwoLists(ListNode *a, ListNode *b) {
        if ((!a) || (!b)) return a ? a : b;
        ListNode head, *tail = &head, *aPtr = a, *bPtr = b;
        while (aPtr && bPtr) {
            if (aPtr->val < bPtr->val) {
                tail->next = aPtr; aPtr = aPtr->next;
            } else {
                tail->next = bPtr; bPtr = bPtr->next;
            }
            tail = tail->next;
        }
        tail->next = (aPtr ? aPtr : bPtr);
        return head.next;
    }

    ListNode* mergeKLists(vector<ListNode*>& lists) {
        ListNode *ans = nullptr;
        for (size_t i = 0; i < lists.size(); ++i) {
            ans = mergeTwoLists(ans, lists[i]);
        }
        return ans;
    }
};

Java

class Solution {
    public ListNode mergeKLists(ListNode[] lists) {
        ListNode ans = null;
        for (int i = 0; i < lists.length; ++i) {
            ans = mergeTwoLists(ans, lists[i]);
        }
        return ans;
    }

    public ListNode mergeTwoLists(ListNode a, ListNode b) {
        if (a == null || b == null) {
            return a != null ? a : b;
        }
        ListNode head = new ListNode(0);
        ListNode tail = head, aPtr = a, bPtr = b;
        while (aPtr != null && bPtr != null) {
            if (aPtr.val < bPtr.val) {
                tail.next = aPtr;
                aPtr = aPtr.next;
            } else {
                tail.next = bPtr;
                bPtr = bPtr.next;
            }
            tail = tail.next;
        }
        tail.next = (aPtr != null ? aPtr : bPtr);
        return head.next;
    }
}

复杂度分析

时间复杂度: 假设每个链表的最长长度是 n。在第一次合并后,ans 的长度为 n;第二次合并后,ans 的长度为 2n,第 i 次合并后,ans 的长度为 i×n。第 i 次合并的时间代价是 O(n+(i−1)×n)=O(i×n),那么总的时间代价为 O ( ∑ i = 1 k ( i × n ) ) = O ( ( 1 + k ) ⋅ k 2 × n ) = O ( k 2 n ) O(\sum_{i = 1}^{k} (i \times n)) = O(\frac{(1 + k)\cdot k}{2} \times n) = O(k^2 n) O(i=1k(i×n))=O(2(1+k)k×n)=O(k2n),故渐进时间复杂度为 O ( k 2 n ) O(k^2 n) O(k2n)

空间复杂度 O(1) 。

方法二:分治合并

考虑优化方法一,用分治的方法进行合并。

  • 将 kkk 个链表配对并将同一对中的链表合并;
  • 第一轮合并以后, k 个链表被合并成了 k 2 \frac{k}{2} 2k​ 个链表,平均长度为 2 n k \frac{2n}{k} k2n,然后是 k 4 \frac{k}{4} 4k 个链表, k 8 \frac{k}{8} 8k 个链表等等;
  • 重复这一过程,直到我们得到了最终的有序链表。

在这里插入图片描述

Golang

/**
 * Definition for singly-linked list.
 * type ListNode struct {
 *     Val int
 *     Next *ListNode
 * }
 */
func mergeKLists(lists []*ListNode) *ListNode {
    return merge(lists, 0, len(lists) - 1)
}

func merge(lists []*ListNode, left, right int) *ListNode {
    if left == right {
        return lists[left]
    }
    if left > right {
        return nil
    }
    mid := (left + right) >> 1
    return mergeTwoLists(merge(lists, left, mid), merge(lists, mid+1, right))
}

func mergeTwoLists(a *ListNode, b *ListNode) *ListNode {
    if a == nil {
        return b
    }
    if b == nil {
        return a
    }

    var head ListNode
    tail := &head

    for (a != nil && b != nil) {
        if a.Val < b.Val {
            tail.Next = a
            a = a.Next
        } else {
            tail.Next = b
            b = b.Next
        }
        tail = tail.Next
    }

    if a != nil {
        tail.Next = a
    } else {
        tail.Next = b
    }
    return head.Next
}

C++

class Solution {
public:
    ListNode* mergeTwoLists(ListNode *a, ListNode *b) {
        if ((!a) || (!b)) return a ? a : b;
        ListNode head, *tail = &head, *aPtr = a, *bPtr = b;
        while (aPtr && bPtr) {
            if (aPtr->val < bPtr->val) {
                tail->next = aPtr; aPtr = aPtr->next;
            } else {
                tail->next = bPtr; bPtr = bPtr->next;
            }
            tail = tail->next;
        }
        tail->next = (aPtr ? aPtr : bPtr);
        return head.next;
    }

    ListNode* merge(vector <ListNode*> &lists, int l, int r) {
        if (l == r) return lists[l];
        if (l > r) return nullptr;
        int mid = (l + r) >> 1;
        return mergeTwoLists(merge(lists, l, mid), merge(lists, mid + 1, r));
    }

    ListNode* mergeKLists(vector<ListNode*>& lists) {
        return merge(lists, 0, lists.size() - 1);
    }
};

Java

class Solution {
    public ListNode mergeKLists(ListNode[] lists) {
        return merge(lists, 0, lists.length - 1);
    }

    public ListNode merge(ListNode[] lists, int l, int r) {
        if (l == r) {
            return lists[l];
        }
        if (l > r) {
            return null;
        }
        int mid = (l + r) >> 1;
        return mergeTwoLists(merge(lists, l, mid), merge(lists, mid + 1, r));
    }

    public ListNode mergeTwoLists(ListNode a, ListNode b) {
        if (a == null || b == null) {
            return a != null ? a : b;
        }
        ListNode head = new ListNode(0);
        ListNode tail = head, aPtr = a, bPtr = b;
        while (aPtr != null && bPtr != null) {
            if (aPtr.val < bPtr.val) {
                tail.next = aPtr;
                aPtr = aPtr.next;
            } else {
                tail.next = bPtr;
                bPtr = bPtr.next;
            }
            tail = tail.next;
        }
        tail.next = (aPtr != null ? aPtr : bPtr);
        return head.next;
    }
}

时间复杂度:考虑递归「向上回升」的过程——第一轮合并 k 2 \frac{k}{2} 2k 组链表,每一组的时间代价是 O ( 2 n ) O(2n) O(2n);第二轮合并 k 4 \frac{k}{4} 4k​ 组链表,每一组的时间代价是 O ( 4 n ) O(4n) O(4n)…所以总的时间代价是 O ( ∑ i = 1 ∞ k 2 i × 2 i n ) = O ( k n × log ⁡ k ) O(\sum_{i = 1}^{\infty} \frac{k}{2^i} \times 2^i n) = O(kn \times \log k) O(i=12ik×2in)=O(kn×logk),故渐进时间复杂度为 O ( k n × log ⁡ k ) O(kn \times \log k) O(kn×logk)
空间复杂度:递归会使用到 O ( log ⁡ k ) O(\log k) O(logk) 空间代价的栈空间。

方法三:使用优先队列/最小堆合并

这个方法和前两种方法的思路有所不同

我们需要维护当前每个链表没有被合并的元素的最前面一个, k k k 个链表就最多有 k k k 个满足这样条件的元素,每次在这些元素里面选取 val 属性最小的元素合并到答案中。

在选取最小元素的时候,我们可以用优先队列/最小堆来优化这个过程。

Golang

/**
 * Definition for singly-linked list.
 * type ListNode struct {
 *     Val int
 *     Next *ListNode
 * }
 */

import "container/heap"

type Status struct {
    Val  int
    Ptr  *ListNode
}

type PriorityQueue []*Status

func (pq PriorityQueue) Len() int {
    return len(pq)
}

func (pq PriorityQueue) Less(i, j int) bool {
    return pq[i].Val < pq[j].Val
}

func (pq PriorityQueue) Swap(i, j int) {
    pq[i], pq[j] = pq[j], pq[i]
}

func (pq *PriorityQueue) Push(x interface{}) {
    *pq = append(*pq, x.(*Status))
}

func (pq *PriorityQueue) Pop() interface{} {
    old := *pq
    n := len(old)
    item := old[n-1]
    old[n-1] = nil
    *pq = old[0 : n-1]
    return item
}

func mergeKLists(lists []*ListNode) *ListNode {
    var q PriorityQueue
    heap.Init(&q)

    for _, node := range lists {
        // 每个链表第一个都放进这个堆
        if node != nil {
            heap.Push(&q, &Status{Val: node.Val, Ptr: node})
        }
    }

    var head ListNode
    tail := &head

    for q.Len() > 0 {
        // 取出最小的
        f := heap.Pop(&q).(*Status)
        tail.Next = f.Ptr
        tail = tail.Next
        // 只要所取的节点后面还要数据
        if f.Ptr.Next != nil {
            // 就放进堆里来
            heap.Push(&q, &Status{Val: f.Ptr.Next.Val, Ptr: f.Ptr.Next})
        }
    }

    return head.Next
}

Java

class Solution {
    class Status implements Comparable<Status> {
        int val;
        ListNode ptr;

        Status(int val, ListNode ptr) {
            this.val = val;
            this.ptr = ptr;
        }

        public int compareTo(Status status2) {
            return this.val - status2.val;
        }
    }

    PriorityQueue<Status> queue = new PriorityQueue<Status>();

    public ListNode mergeKLists(ListNode[] lists) {
        for (ListNode node: lists) {
            if (node != null) {
                queue.offer(new Status(node.val, node));
            }
        }
        ListNode head = new ListNode(0);
        ListNode tail = head;
        while (!queue.isEmpty()) {
            Status f = queue.poll();
            tail.next = f.ptr;
            tail = tail.next;
            if (f.ptr.next != null) {
                queue.offer(new Status(f.ptr.next.val, f.ptr.next));
            }
        }
        return head.next;
    }
}

C++

class Solution {
public:
    struct Status {
        int val;
        ListNode *ptr;
        bool operator < (const Status &rhs) const {
            return val > rhs.val;
        }
    };

    priority_queue <Status> q;

    ListNode* mergeKLists(vector<ListNode*>& lists) {
        for (auto node: lists) {
            if (node) q.push({node->val, node});
        }
        ListNode head, *tail = &head;
        while (!q.empty()) {
            auto f = q.top(); q.pop();
            tail->next = f.ptr; 
            tail = tail->next;
            if (f.ptr->next) q.push({f.ptr->next->val, f.ptr->next});
        }
        return head.next;
    }
};

复杂度分析

时间复杂度:考虑优先队列中的元素不超过 k k k 个,那么插入和删除的时间代价为 ¥O(\log k)$,这里最多有 k n kn kn 个点,对于每个点都被插入删除各一次,故总的时间代价即渐进时间复杂度为 O ( k n × log ⁡ k ) O(kn \times \log k) O(kn×logk)
空间复杂度:这里用了优先队列,优先队列中的元素不超过 k k k 个,故渐进空间复杂度为 O ( k ) O(k) O(k)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/964513.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

中心差分法-学习笔记《结构动力学-陈政清》

激励分段解析法仅仅对外载荷进行了离散&#xff0c;但对运动方程还是严格满足的&#xff0c;体系的运动在时间轴上依然是满足运动微分方程。然而&#xff0c;一般的时域逐步积分法进一步放松要求&#xff0c;不仅仅对外荷载进行离散化处理&#xff0c;也对体系的运动进行离散化…

前端Vue仿企查查天眼查高管信息列表组件

随着技术的不断发展&#xff0c;传统的开发方式使得系统的复杂度越来越高。在传统开发过程中&#xff0c;一个小小的改动或者一个小功能的增加可能会导致整体逻辑的修改&#xff0c;造成牵一发而动全身的情况。为了解决这个问题&#xff0c;我们采用了组件化的开发模式。通过组…

PCL 判断四点共面(三维空间)

文章目录 一、简介二、实现代码三、实现效果参考资料一、简介 这里仍然沿用之前的方式来判断三维空间中四个顶点的共面性,三维空间中四个顶点可以构成三条线段(共用同一个顶点),这三条线段所代表的矢量可以组成一个立方空间,如下图所示: 这个立方体的体积其实就是由这三个…

Blender里复制对象动画

假设在Blender里有2个对象&#xff0c;其中一个添加了动画&#xff0c;另外一个没有添加动画&#xff0c;那么如何把已有的动画拷贝到没有动画的对象上呢&#xff1f; 分为2步&#xff1a; 先选中没有动画的对象&#xff0c;再按shift键选中有动画的对象&#xff0c;此时2个对…

【论文精读】Learning Transferable Visual Models From Natural Language Supervision

Learning Transferable Visual Models From Natural Language Supervision 前言Abstract1. Introduction and Motivating Work2. Approach2.1. Creating a Sufficiently Large Dataset2.2. Selecting an Efficient Pre-Training Method2.3. Choosing and Scaling a Model2.4. P…

给Hexo添加说说功能

首发博客地址 官网地址 效果 &#x1f440; 前言 GitHub 仓库&#xff1a;Artitalk.js &#x1f389; 特性 增删查改全方面支持 支持针对每条说说的评论 支持 Markdown/html 语法 支持图片上传 &#x1f680; 快速使用 下列主题已将本项目整合进去&#xff0c;可以直接使用。 感…

Linux——常用命令大汇总(带你快速入门Linux)

纵有疾风起&#xff0c;人生不言弃。本文篇幅较长&#xff0c;如有错误请不吝赐教&#xff0c;感谢支持。 &#x1f4ac;文章目录 一.终端和shell命令解析器终端和shell命令解析器概述终端提示符的格式常用快捷键 二.Linux命令格式帮助文档&#xff1a;man 三.目录基础知识Wind…

什么是RTC

参考&#xff1a; https://zhuanlan.zhihu.com/p/377100294 RTC&#xff08;Real time communication&#xff09;实时通信&#xff0c;是实时音视频的一个简称&#xff0c;我们常说的RTC技术一般指的是WebRTC技术&#xff0c;已经被 W3C 和 IETF 发布为正式标准。由于几乎所…

tableau基础学习2:时间序列数据预处理与绘图

文章目录 数据预处理1. 原始数据2. 合并数据集2. 创建计算字段 绘图分析1. 趋势分析2. 计算字段趋势分析 这一部分&#xff0c;我们记录一些分析时序趋势的分析步骤 数据预处理 1. 原始数据 原始数据是excel表格&#xff0c;其中包含三个Sheet页&#xff0c; 这里我们选择两…

老程序员教你如何笑对问题,轻松培养逻辑思考和解决问题的能力

原文链接 ​​​​​​​老程序员教你如何笑对问题&#xff0c;轻松培养逻辑思考和解决问题的能力 故事发生在一个阳光明媚的午后&#xff0c;我们的主人公&#xff0c;老李&#xff0c;一位拥有十年工作经验的 Python 老程序员&#xff0c;正悠哉地在喝着咖啡。 这时&#x…

VisualStudio配置pybind11-Python调用C++方法

个人测试下来Debug生成的dll改pyd&#xff0c;py中import会报错gilstate->autoInterpreterState 如果遇到同样问题使用Release吧 目录 1.安装pybind11 1.pip&#xff1a; 2.github&#xff1a; 2.配置VS工程 2.在VC目录中的包含目录添加&#xff1a; 3.在VC目录中的库目录…

Debezium的三种部署方式

Debezium如何部署 debezium 有下面三种部署方式,其中最常用的就是 kafka connect。 kafka connect 一般情况下,我们通过 kafka connect 来部署 debezium,kafka connect 是一个框架和运行时: source connectors:像 debezium 这样将记录发送到 kafka 的source connectors…

JavaScript基础语法04——输入输出语法

嗨&#xff0c;大家好&#xff0c;我是雷工。 今天学习JavaScript基础语法&#xff0c;输入输出语法&#xff0c;以下为学习笔记。 1、输出语法&#xff1a; 1.1、alert&#xff08;&#xff09; 作用&#xff1a;界面弹出警告对话框。 示例&#xff1a; <script>aler…

数据结构入门 — 队列

本文属于数据结构专栏文章&#xff0c;适合数据结构入门者学习&#xff0c;涵盖数据结构基础的知识和内容体系&#xff0c;文章在介绍数据结构时会配合上动图演示&#xff0c;方便初学者在学习数据结构时理解和学习&#xff0c;了解数据结构系列专栏点击下方链接。 博客主页&am…

Linux centos7 bash编程(循环与条件判断)

在编程训练中&#xff0c;循环结构与条件判断十分重要。 根据条件为真为假确定是否执行循环。 有时&#xff0c;根据条件的真假结果&#xff0c;决定执行哪些语句&#xff0c;这就是分支语句。 为了训练分支语句与循环语句&#xff0c;我们设计一个案例&#xff1a; 求一组…

Python库-coverage测试覆盖率

Coverage.py 是用于测量Python程序代码覆盖率的工具。它 监视程序&#xff0c;注意代码的哪些部分已执行&#xff0c;然后 分析源以识别可以执行但未执行的代码。 覆盖率测量通常用于衡量测试的有效性。它 可以显示测试正在执行代码的哪些部分&#xff0c;以及哪些部分是 不。…

CentOS配置Java环境报错-bash: /usr/local/jdk1.8.0_381/bin/java: 无法执行二进制文件

CentOS配置Java环境后执行java -version时报错&#xff1a; -bash: /usr/local/jdk1.8.0_381/bin/java: 无法执行二进制文件原因是所使用的jdk的版本和Linux内核架构匹配不上 使用以下命令查看Linux架构&#xff1a; [rootlocalhost ~]# cat /proc/version Linux version 3.1…

C语言:大小端字节序存储

一、大小端字节序存储介绍 大端字节序存储模式&#xff1a;把一个数据低位字节处的数据存放在高地址处&#xff0c;数据高位字节处的数据存放在低地址处 小端字节序存储模式&#xff1a;把一个数据低位字节处的数据存放在低地址处&#xff0c;数据高位字节处的数据存放在高地址…

经管博士科研基础【12】包络定理

当我们知道一个函数的最优解时&#xff0c;我们要求解这一个函数的值函数关于函数中某一个参数的导数&#xff0c;那么就可以使用包络定理。 1. 无约束条件下的包络定理 函数在其极值点处对一个参数&#xff08;参数不是自变量&#xff09;取偏导数的结果&#xff0c;等价于这…

5G NR:RACH流程 -- Msg1发送时RA-RNTI的计算及功率控制

前言 如果阅读了这两篇博文《如何产生PRACH preamble》和《如何选择合适的时频资源发送preamble》&#xff0c;那么对msg1有了基本了解&#xff0c;但是真到了Msg1发送的时候&#xff0c;该怎么处理呢&#xff0c;这里涉及到两个问题&#xff1a; 问题1&#xff1a;发…