23. Merge K Sorted Lists,合并K个有序单链表

Last Updated: 2023-11-04 14:34:25 Saturday

-- TOC --

这道Hard题,有点不够Hard...

题目分析

You are given an array of k linked-lists lists, each linked-list is sorted in ascending order. Merge all the linked-lists into one sorted linked-list and return it.

将多个已经排好序的singly-linked list,合并成一个返回,已排好序的singly-linked list的数量为k,是个参数。

Example 1:
Input: lists = [[1,4,5],[1,3,4],[2,6]]
Output: [1,1,2,3,4,4,5,6]
Explanation: The linked-lists are:
[
  1->4->5,
  1->3->4,
  2->6
]
merging them into one sorted list:
1->1->2->3->4->4->5->6

Example 2:
Input: lists = []
Output: []

Example 3:
Input: lists = [[]]
Output: []

Constraints:

如果学过数据结构和算法,应该能够想到,此题考验的,是应用heap的算法,又快又省。

Beauty of Brute Force

将所有的节点放在一起排个序。

Python

由于在Python中,执行内置的sort是C的速度,因此暴力一点,反而整体的速度更快。

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def mergeKLists(self, lists: List[Optional[ListNode]]) -> Optional[ListNode]:
        a = []
        for lst in lists:
            while lst:
                a.append(lst)
                lst = lst.next
        if not a:
            return None
        a.sort(key=lambda x:x.val)
        for i in range(len(a)-1):
            a[i].next = a[i+1]
        return a[0]

内置sort是C的速度!所以,在Python的提交中,好快....:)

C++

/**
 * 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) {
        if(lists.empty())
            return nullptr;
        vector<ListNode*> slst;
        slst.reserve(4096);
        for(auto p: lists){
            while(p){
                slst.push_back(p);
                p = p->next;
            }
        }
        if(slst.empty())
            return nullptr;
        sort(slst.begin(),
             slst.end(),
             [](ListNode *a, ListNode *b)
                { return a->val < b->val; });
        ListNode *h, *p;
        h = p = slst[0];
        for(auto it{slst.begin()+1}; it!=slst.end(); ++it)
            p = p->next = *it;
        p->next = nullptr;  //!
        return h;
    }
};

C

static int cmp(const void *a, const void *b)
{ return (*(struct ListNode**)a)->val - (*(struct ListNode**)b)->val; }

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode* mergeKLists(struct ListNode** lists, int listsSize){
    if(listsSize == 0)
        return NULL;
    struct ListNode **slst = NULL;
    int sidx = 0;
    for(int i=0; i<listsSize; ++i){
        struct ListNode *p = lists[i];
        while(p){
            if(sidx%256 == 0)
                slst = realloc(slst, sizeof(char*)*(sidx/256+1)*256);
            slst[sidx++] = p;
            p = p->next;
        }
    }
    if(sidx == 0)
        return NULL;
    qsort(slst, sidx, sizeof(char*), cmp);
    struct ListNode *h, *p;
    h = p = slst[0];
    for(int i=1; i<sidx; ++i)
        p = p->next = slst[i];
    p->next = NULL;
    free(slst);
    return h;
}

Heap

当N远远大于k时,应用heap的算法会由明显优势。

Python

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:

    def _heap_trickle_down(self, lst, idx=0):
        n = len(lst)
        while idx < n:
            lidx = idx*2 + 1
            if lidx < n:
                midx = idx
                if lst[lidx].val < lst[midx].val:
                    midx = lidx
                ridx = lidx + 1
                if ridx<n and lst[ridx].val<lst[midx].val:
                    midx = ridx
                if idx == midx:
                    return
                lst[idx], lst[midx] = lst[midx], lst[idx]
                idx = midx
                continue
            return

    def _heapify(self, lst):
        for i in reversed(range(len(lst)//2)):
            self._heap_trickle_down(lst, i)

    def mergeKLists(self, lists: List[Optional[ListNode]]) -> Optional[ListNode]:
        nodes = [it for it in lists if it]
        if not nodes:
            return None
        self._heapify(nodes)
        head = p = ListNode()
        while nodes:
            p.next = p = nodes[0]
            if nodes[0].next:
                nodes[0] = nodes[0].next
            else:
                n = nodes.pop()
                if nodes:
                    nodes[0] = n
                else:
                    return head.next
            self._heap_trickle_down(nodes)
        return head.next

C++

C++的<algorithm>库中,有一组操作heap的接口,用一用吧。

/**
 * 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) {}
 * };
 */

bool cmp(const void *a, const void *b)
{ return (*(ListNode*)a).val > (*(ListNode*)b).val; }

class Solution {
public:
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        vector<ListNode*> hp;
        for(auto p: lists)
            if(p)
                hp.push_back(p);
        if(hp.empty())
            return nullptr;
        make_heap(hp.begin(), hp.end(), cmp);
        ListNode *h, *p, t {ListNode{}};
        h = p = &t;
        while(hp.size()){
            pop_heap(hp.begin(), hp.end(), cmp);
            p = p->next = hp.back();
            hp.pop_back();
            if(p->next){
                hp.push_back(p->next);
                push_heap(hp.begin(), hp.end(), cmp);
            }
        }
        return h->next;
    }
};

C

static void siftdown(struct ListNode **h, int size, int idx){
    while(idx < size){
        int left = idx*2 + 1;
        if(left < size){
            int midx = idx;
            if(h[midx]->val > h[left]->val)
                midx = left;
            int right = left + 1;
            if(right<size && h[midx]->val>h[right]->val)
                midx = right;
            if(idx == midx)
                return;
            struct ListNode *t = h[idx];
            h[idx] = h[midx];
            h[midx] = t;
            idx = midx;
            continue;  //!
        }
        return;
    }
}

static void heapify(struct ListNode **h, int size){
    for(int i=size/2-1; i>=0; --i)
        siftdown(h, size, i);
}

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode* mergeKLists(struct ListNode** lists, int listsSize){
    struct ListNode **heap = malloc(sizeof(char*)*listsSize);
    int hidx = 0;
    for(int i=0; i<listsSize; ++i)
        if(lists[i])
            heap[hidx++] = lists[i];
    if(hidx == 0){
        free(heap);
        return NULL;
    }
    heapify(heap, hidx);
    struct ListNode h;
    struct ListNode *r = &h;
    while(hidx){
        r = r->next = heap[0];
        if(heap[0]->next)
            heap[0] = heap[0]->next;
        else{
            if(--hidx)
                heap[0] = heap[hidx];
            else
                break;
        }
        siftdown(heap, hidx, 0);
    }
    free(heap);
    return h.next;
}

下面这段代码是我用来测试的,觉得还有点意思,就保留下来了,想想LeetCode可能就是用类似的代码在测试我们的提交。这段代码再次证明,singly-linked链表如果有一个sentinel head,生活将变得多么惬意......这段代码应该可以算作median级别的,单链表的创建,反向遍历,始终在头部插入节点。

int main(){
    int data1[] = {-8,-7,-6,-3,-2,-2,0,3};
    int data2[] = {-10,-6,-4,-4,-4,-2,-1,4};
    int data3[] = {-10,-9,-8,-8,-6};
    int data4[] = {-10,0,4};
    // same as int *(dataset[])
    int *dataset[] = {data1, data2, data3, data4};
    int setnum = sizeof(dataset) / sizeof(int*);
    int datanum[] = {sizeof(data1)/sizeof(int),
                     sizeof(data2)/sizeof(int),
                     sizeof(data3)/sizeof(int),
                     sizeof(data4)/sizeof(int)};

    // dataset[0] is only a plain int*
    printf("%zu\n", sizeof(dataset[0])/sizeof(int));

    struct ListNode **data = malloc(sizeof(struct ListNode*)*setnum);
    struct ListNode head;  // on stack, no need to free

    for(int i=0; i<setnum; ++i){
        head.next = NULL;
        for(int j=datanum[i]-1; j>=0; --j){
            struct ListNode *t = malloc(sizeof(struct ListNode));
            t->val = dataset[i][j];
            t->next = head.next;
            head.next = t;
        }
        data[i] = head.next;
    }

    // go
    struct ListNode *r = mergeKLists(data, setnum);

    while(r){
        printf("%d ", r->val);
        struct ListNode *p = r;
        r = r->next;
        free(p);
    }
    printf("\n");
    free(data);
    return 0;
}

本文链接:https://cs.pynote.net/ag/leetcode/202310262/

-- EOF --

-- MORE --