23. Merge k Sorted Lists

这道题目,自己犯了懒惰病,以至于几乎没怎么好好思考过,结果就导致了连最基本的解法都没有想出来。其实这道题,解法共有三种。下面分别进行阐述。

解法1:最基础也最符合直觉的解法就是先合并1、2,12结果和3合并,123结果和4合并.....123...k-1结果和k合并,我们计算一下复杂度。在这里,我们假设已排序链表的平均长度为n。

1、2合并,遍历2n个节点。

12结果和3合并,遍历3n个节点。

123结果和4合并,遍历4n个节点。

...

123...k-1结果和k合并,遍历kn个节点

总共遍历的节点数目为n(2+3+...+k) = n*(k^2+k-2)/2, 因此时间复杂度是O(n*(k^2+k-2)/2) = O(nk^2),代码如下:

ListNode *mergeKLists(vector<ListNode *> &lists) {
        if(lists.size() == 0)return NULL;
        ListNode*res = lists[0];
        for(int i = 1; i < lists.size(); i++)
            res = merge2list(res, lists[i]);
        return res;
    }
    
    ListNode *merge2list(ListNode *head1, ListNode*head2)
    {
        ListNode node(0), *res = &node;
        while(head1 && head2)
        {
            if(head1->val <= head2->val)
            {
                res->next = head1;
                head1 = head1->next;
            }
            else
            {
                res->next = head2;
                head2 = head2->next;
            }
            res = res->next;
        }
        if(head1)
            res->next = head1;
        else if(head2)
            res->next = head2;
        return node.next;
    }

解法2: 利用分治的思想把合并k个链表分为两个合并k/2个链表的任务,一直划分,直到任务中只剩下一个链表或者两个链表。可以很简单的用递归来实现。因此算法复杂度为T(k) = 2T(k/2) + O(nk),根据主定理可以得到算法的复杂度为O(nklogk)。

递归的代码就不贴了。下面是非递归的代码。非递归的思想是(以四个链表为例):

0、2合并,合并结果放到0的位置

1、3合并,合并结果放到1的位置

再把0、1合并(相当于原来的13 和 24合并)。

ListNode *mergeKLists(vector<ListNode *> &lists) {
        int n = lists.size();
        if(n == 0)return NULL;
        while(n >1)
        {
            int k = (n+1)/2;
            for(int i = 0; i < n/2; i++)
                lists[i] = merge2list(lists[i], lists[i + k]);
            n = k;
        }
        return lists[0];
    }
     
    ListNode *merge2list(ListNode *head1, ListNode*head2)
    {
        ListNode node(0), *res = &node;
        while(head1 && head2)
        {
            if(head1->val <= head2->val)
            {
                res->next = head1;
                head1 = head1->next;
            }
            else
            {
                res->next = head2;
                head2 = head2->next;
            }
            res = res->next;
        }
        if(head1)
            res->next = head1;
        else if(head2)
            res->next = head2;
        return node.next;
    }

解法3:维护一个k个大小的最小堆,初始化堆中元素为每个链表的头结点,每次从堆中选择最小的元素加入到结果链表,再选择该最小元素所在链表的下一个节点加入到堆中。这样当堆为空时,所有链表的节点都已经加入了结果链表。元素加入堆中的复杂度为O(logk),总共有kn个元素加入堆中,因此,复杂度也和算法2一样是O(nklogk)。代码如下:

class Solution {
private:
struct cmp // 比较类
{
    bool operator ()(const ListNode *a, const ListNode *b) // 从小到大排列
    {
            return a->val > b->val;
    }
};
public:
    ListNode *mergeKLists(vector<ListNode *> &lists) {
        int n = lists.size();
        if(n == 0)return NULL;
        ListNode node(0), *res = &node;// 一个trick,在这里,res充当移动的cursor
        priority_queue<ListNode*, vector<ListNode*>, cmp> que;
        for(int i = 0; i < n; i++)
            if(lists[i])
                que.push(lists[i]);
        while(!que.empty())
        {
            ListNode * p = que.top();
            que.pop();
            res->next = p;
            res = p; // 注意,在代码中,没有写res->next = NULL这样的语句,res最后指向的那个节点,其next一定为NULL
             
            if(p->next)// 向que中加入剩下的元素
                que.push(p->next);
        }
        return node.next;
    }
};


更新------------------------------

思路1:priority queue

将每个list的最小节点放入一个priority queue (min heap)中。之后每从queue中取出一个节点,则将该节点在其list中的下一个节点插入,以此类推直到全部节点都经过priority queue。由于priority queue的大小为始终为k,而每次插入的复杂度是log k,一共插入过nk个节点。时间复杂度为O(nk logk),空间复杂度为O(k)

注意C++的STL中的priority queue默认是max heap,定义一个新的比较函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Solution {
public:
    struct compNode {
        bool operator()(ListNode *p, ListNode *q) const {
            return p->val>q->val;
        }  
    };

    ListNode *mergeKLists(vector<ListNode *> &lists) {
        priority_queue<ListNode*, vector<ListNode*>, compNode> pq;
        ListNode *dummy = new ListNode(0), *tail = dummy;
        
        for(int i=0; i<lists.size(); i++) 
            if(lists[i]) pq.push(lists[i]);
            
        while(!pq.empty()) {
            tail->next = pq.top();
            tail = tail->next;
            pq.pop();
            if(tail->next) pq.push(tail->next);
        }
        
        return dummy->next;
    }
};


思路2:Merge two lists

利用合并两个list的方法,依次将每个list合并到结果的list中。这个方法的空间复杂度为O(1),时间复杂度为:
2n + 3n + ... + kn = [(k+1)*k/2-1]*n = O(nk^2)
由于时间复杂度较大,大数据测试会超时。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Solution {
public:
    ListNode *mergeKLists(vector<ListNode *> &lists) {
        ListNode *ret = NULL;
        for(int i=0; i<lists.size(); i++) 
            ret = merge2Lists(ret, lists[i]);
        return ret;
    }
    
    ListNode* merge2Lists(ListNode *h1, ListNode *h2) {
        ListNode *dummy = new ListNode(0), *tail = dummy;
        while(h1 && h2) {
            if(h1->val<=h2->val) {
                tail->next = h1;
                h1 = h1->next;
            }
            else {
                tail->next = h2;
                h2 = h2->next;
            }
            tail = tail->next;
        }
        tail->next = h1 ? h1 : h2;
        return dummy->next;
    }
};


思路3:二分法

类似merge sort,每次将所有的list两两之间合并,直到所有list合并成一个。如果用迭代而非递归,则空间复杂度为O(1)。时间复杂度:
2n * k/2 + 4n * k/4 + ... + (2^x)n * k/(2^x) = nk * x
k/(2^x) = 1 -> 2^x = k -> x = log2(k)
所以时间复杂度为O(nk log(k)),与方法一相同。


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class Solution {
public:
    ListNode *mergeKLists(vector<ListNode *> &lists) {
        if(lists.empty()) return NULL;
        int end = lists.size()-1;
        while(end>0) {
            int begin = 0;
            while(begin<end) {
                lists[begin] = merge2Lists(lists[begin], lists[end]);
                begin++;
                end--;
            }
        }
        return lists[0];
    }
    
    ListNode* merge2Lists(ListNode *h1, ListNode *h2) {
        ListNode *dummy = new ListNode(0), *tail = dummy;
        while(h1 && h2) {
            if(h1->val<=h2->val) {
                tail->next = h1;
                h1 = h1->next;
            }
            else {
                tail->next = h2;
                h2 = h2->next;
            }
            tail = tail->next;
        }
        tail->next = h1 ? h1 : h2;
        return dummy->next;
    }
};




标题基于SpringBoot+Vue的学生交流互助平台研究AI更换标题第1章引言介绍学生交流互助平台的研究背景、意义、现状、方法与创新点。1.1研究背景与意义分析学生交流互助平台在当前教育环境下的需求及其重要性。1.2国内外研究现状综述国内外在学生交流互助平台方面的研究进展与实践应用。1.3研究方法与创新点概述本研究采用的方法论、技术路线及预期的创新成果。第2章相关理论阐述SpringBoot与Vue框架的理论基础及在学生交流互助平台中的应用。2.1SpringBoot框架概述介绍SpringBoot框架的核心思想、特点及优势。2.2Vue框架概述阐述Vue框架的基本原理、组件化开发思想及与前端的交互机制。2.3SpringBoot与Vue的整合应用探讨SpringBoot与Vue在学生交流互助平台中的整合方式及优势。第3章平台需求分析深入分析学生交流互助平台的功能需求、非功能需求及用户体验要求。3.1功能需求分析详细阐述平台的各项功能需求,如用户管理、信息交流、互助学习等。3.2非功能需求分析对平台的性能、安全性、可扩展性等非功能需求进行分析。3.3用户体验要求从用户角度出发,提出平台在易用性、美观性等方面的要求。第4章平台设计与实现具体描述学生交流互助平台的架构设计、功能实现及前后端交互细节。4.1平台架构设计给出平台的整体架构设计,包括前后端分离、微服务架构等思想的应用。4.2功能模块实现详细阐述各个功能模块的实现过程,如用户登录注册、信息发布与查看、在线交流等。4.3前后端交互细节介绍前后端数据交互的方式、接口设计及数据传输过程中的安全问题。第5章平台测试与优化对平台进行全面的测试,发现并解决潜在问题,同时进行优化以提高性能。5.1测试环境与方案介绍测试环境的搭建及所采用的测试方案,包括单元测试、集成测试等。5.2测试结果分析对测试结果进行详细分析,找出问题的根源并
内容概要:本文详细介绍了一个基于灰狼优化算法(GWO)优化的卷积双向长短期记忆神经网络(CNN-BiLSTM)融合注意力机制的多变量多步时间序列预测项目。该项目旨在解决传统时序预测方法难以捕捉非线性、复杂时序依赖关系的问题,通过融合CNN的空间特征提取、BiLSTM的时序建模能力及注意力机制的动态权重调节能力,实现对多变量多步时间序列的精准预测。项目不仅涵盖了数据预处理、模型构建与训练、性能评估,还包括了GUI界面的设计与实现。此外,文章还讨论了模型的部署、应用领域及其未来改进方向。 适合人群:具备一定编程基础,特别是对深度学习、时间序列预测及优化算法有一定了解的研发人员和数据科学家。 使用场景及目标:①用于智能电网负荷预测、金融市场多资产价格预测、环境气象多参数预报、智能制造设备状态监测与预测维护、交通流量预测与智慧交通管理、医疗健康多指标预测等领域;②提升多变量多步时间序列预测精度,优化资源调度和风险管控;③实现自动化超参数优化,降低人工调参成本,提高模型训练效率;④增强模型对复杂时序数据特征的学习能力,促进智能决策支持应用。 阅读建议:此资源不仅提供了详细的代码实现和模型架构解析,还深入探讨了模型优化和实际应用中的挑战与解决方案。因此,在学习过程中,建议结合理论与实践,逐步理解各个模块的功能和实现细节,并尝试在自己的项目中应用这些技术和方法。同时,注意数据预处理的重要性,合理设置模型参数与网络结构,控制多步预测误差传播,防范过拟合,规划计算资源与训练时间,关注模型的可解释性和透明度,以及持续更新与迭代模型,以适应数据分布的变化。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值