Merge K Sorted Lists

题目描述与分析

合并k个有序链表。假设每个链表的长度都是n来分析复杂度吧。
这一题是LeetCode的第23题。类似于归并排序(Merge Sort)最后的合并步骤。

朴素Naive的实现是每次合并一个list进来,时间复杂度是 O(kn2) 的。

如果能够每次快速取到所有lists的最小值中的最小值就可以快速合并了。那么用堆或者优先队列来按照值的大小维护所有lists的head,就可以在 O(1) 的时间内取到当前最小值,然后把这个值的next入堆并维护堆即可,维护堆的复杂度是 O(log(k)) 。初始化堆是线性的, O(k) . 所有总的时间复杂度应该是 O(knlog(k))

最初写的时候遇到了一些问题没有注意。主要就是
1. k=0
2. 链表为空
最后还是参考了别人的边界情况才明白LeetCode里的输入格式是什么意思。需要做的就是提前扫描一遍k个lists把空的list忽略。

C++的实现

c++ 的 stl 中的make_heap, push_heap, pop_heap 这些也是第一次用,参考了:
1.cplusplus.com/…/make_heap
2. lengbingshy的博客:C++中定义比较函数的三种方法

STL的 Heap 实现

Error: elements in iterator range [__first, __last) do not form a heap.

需要注意的是在使用自定义heap的比较函数的时候,调用push_heap()和pop_heap()的时候也要传入比较函数,不然按照默认的比较函数就会报”不是heap”的错。

Code

#include <algorithm>
#include <vector>

using namespace std;

struct ListNode {
    int val;
    ListNode *next;
    ListNode(int x) : val(x), next(NULL) {}
};

bool cmp(ListNode *left, ListNode *right) {
    return left->val > right->val;
}

// Solution class for leetcode
class Solution {
public:
    ListNode *mergeKLists(vector <ListNode *>& lists) {
        vector<ListNode *>v;
        for (int i = 0; i < lists.size(); i++) {
            if (lists[i] != NULL) 
                v.push_back(lists[i]);
        }

        if (v.size() == 0) return NULL;
        if (v.size() == 1) return v[0];

        ListNode head(0);
        ListNode *tail = NULL;
        head.val = 0;
        head.next = NULL;
        tail = &head;

        // init heap by cmp function
        make_heap(v.begin(), v.end(), cmp);
        while (v.size() > 0)
        {
            ListNode *front = v.front();
            pop_heap(v.begin(), v.end(), cmp); // pop using cmp function
            v.pop_back();
            if (front->next != NULL)
            {
                v.push_back(front->next);
                push_heap(v.begin(), v.end(), cmp); // push using cmp function
            }
            front->next = NULL;
            tail->next = front;
            tail = tail->next;
        }

        return head.next;
    }
};

STL 的 priority_queue 实现

参考
  1. cplusplus.com : priority_queue
  2. priority_queue 的 constructor
    为了熟悉priority_queue的用法又实现了一下。但是也是遇到了意料之外的情况。
第一版实现 Time Limit Exceeded
class CompareListNodePtr {
    bool reverse;
public:
    CompareListNodePtr(const bool & _reverse = false) {
        reverse = _reverse;
    }
    bool operator ()(const ListNode *left, const ListNode *right) {
        if (reverse) return left->val > right->val;
        else return left->val < right->val;
    }
};

typedef priority_queue<ListNode *, vector<ListNode *>, CompareListNodePtr > ListNodePQ;

class Solution {
public:
    ListNode *mergeKLists(vector <ListNode *>& lists) {

        ListNode head(0);
        ListNode * tail = &head;
        ListNodePQ pq(CompareListNodePtr(true));
        for (int i = 0; i < lists.size(); i++)
        {
            if (lists[i] != NULL) pq.push(lists[i]);
        }
        if (pq.size() == 0) return NULL;
        if (pq.size() == 1) return pq.top();

        while (! pq.empty()) {
            ListNode * t = pq.top();
            pq.pop();
            if (t->next) pq.push(t->next);
            tail->next = t;
            t->next = NULL;
            tail = tail->next;
        }
        return head.next;
    }

};

发现TLE之后就想起了数据结构课上学过的东西,就是直接顺序插入堆来建立优先队列的复杂度稍高,而 make_heap 的实现是可以做到线性建堆的。所以正确的做法应该是把作为Container的vector传给priority_queue来建立堆。

第二版实现Accepted
class CompareListNodePtr {
    bool reverse;
public:
    CompareListNodePtr(const bool & _reverse = false) {
        reverse = _reverse;
    }
    bool operator ()(const ListNode *left, const ListNode *right) {
        if (reverse) return left->val > right->val;
        else return left->val < right->val;
    }
};

typedef priority_queue<ListNode *, vector<ListNode *>, CompareListNodePtr > ListNodePQ;

class Solution {
public:
    ListNode *mergeKLists(vector <ListNode *>& lists) {

        ListNode head(0);
        ListNode * tail = &head;
        vector<ListNode *> v;
        for (int i = 0; i < lists.size(); i++)
        {
            if (lists[i] != NULL) v.push_back(lists[i]);
        }
        if (v.size() == 0) return NULL;
        if (v.size() == 1) return v.front();
        ListNodePQ pq(v.begin(), v.end(), CompareListNodePtr(true));

        while (! pq.empty()) {
            ListNode * t = pq.top();
            pq.pop();
            if (t->next) pq.push(t->next);
            tail->next = t;
            t->next = NULL;
            tail = tail->next;
        }
        return head.next;
    }

};

Golang 的实现

“container/heap”

Golang 中也有实现好的容器可以用, 搜索一下找到文档(golang.org 貌似目前也被墙了。但是用go自带的godoc命令在本地查看文档也可以)就可以照着写了。把对应的Type实现heap的interface就可以了。

参考的 ListNodeHeap 实现

import "container/heap"

// reference: golang.org/pkg/container/heap/#example__intHeap

type ListNode struct {
    Val  int
    Next *ListNode
}

type ListNodeHeap []*ListNode

func (h ListNodeHeap) Len() int {
    return len(h)
}

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

func (h ListNodeHeap) Swap(i, j int) {
    h[i], h[j] = h[j], h[i]
}

func (h *ListNodeHeap) Push(x interface{}) {
    *h = append(*h, x.(*ListNode))
}

func (h *ListNodeHeap) Pop() interface{} {
    old := *h
    n := len(old)
    x := old[n-1]
    *h = old[0 : n-1]
    return x
}

Time Efficiency Comparison

C++ is slower than Golang implementation. Maybe STL is really slow than straight forward implementation.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值