zoukankan      html  css  js  c++  java
  • 乘风破浪:LeetCode真题_023_Merge k Sorted Lists

     乘风破浪:LeetCode真题_023_Merge k Sorted Lists

    一、前言

        上次我们学过了合并两个链表,这次我们要合并N个链表要怎么做呢,最先想到的就是转换成2个链表合并的问题,然后解决,再优化一点的,就是两个两个合并,当然我们也可以一次性比较所有的元素,然后一点点的进行合并等等。

    二、Merge k Sorted Lists

    2.1 问题

    2.2 分析与解决

         首先我们看看官方的解法:

         第一种方法:暴力算法,将所有的元素放到一个数组里面,排序之后再用指针链接。

    # Definition for singly-linked list.
    # class ListNode(object):
    #     def __init__(self, x):
    #         self.val = x
    #         self.next = None
    
    class Solution(object):
        def mergeKLists(self, lists):
            """
            :type lists: List[ListNode]
            :rtype: ListNode
            """
            self.nodes = []
            head = point = ListNode(0)
            for l in lists:
                while l:
                    self.nodes.append(l.val)
                    l = l.next
            for x in sorted(self.nodes):
                point.next = ListNode(x)
                point = point.next
            return head.next
    

        第二种方法:用N个指针,每次比较最小的,然后连接起来。

         第三种方法:通过优先级队列减少了一些比较的时间。

    from Queue import PriorityQueue
    
    class Solution(object):
        def mergeKLists(self, lists):
            """
            :type lists: List[ListNode]
            :rtype: ListNode
            """
            head = point = ListNode(0)
            q = PriorityQueue()
            for l in lists:
                if l:
                    q.put((l.val, l))
            while not q.empty():
                val, node = q.get()
                point.next = ListNode(val)
                point = point.next
                node = node.next
                if node:
                    q.put((node.val, node))
            return head.next
    

           第四种,我们提到过的转换成两个链表合并问题。

        第五种,分治法合并,减少合并次数。

    class Solution(object):
        def mergeKLists(self, lists):
            """
            :type lists: List[ListNode]
            :rtype: ListNode
            """
            amount = len(lists)
            interval = 1
            while interval < amount:
                for i in range(0, amount - interval, interval * 2):
                    lists[i] = self.merge2Lists(lists[i], lists[i + interval])
                interval *= 2
            return lists[0] if amount > 0 else lists
    
        def merge2Lists(self, l1, l2):
            head = point = ListNode(0)
            while l1 and l2:
                if l1.val <= l2.val:
                    point.next = l1
                    l1 = l1.next
                else:
                    point.next = l2
                    l2 = l1
                    l1 = point.next.next
                point = point.next
            if not l1:
                point.next=l2
            else:
                point.next=l1
            return head.next
    

     

       我们的解法:其实和优先级队列很相似,都是使用了小根堆这样的结构来处理。

    import java.util.ArrayList;
    import java.util.Comparator;
    import java.util.List;
    
    public class Solution {
        /**
         * <pre>
         * Merge k sorted linked lists and return it as one sorted list.
         * Analyze and describe its complexity.
         *
         * 题目大意:
         * 合并k个排好的的单链表
         *
         * 解题思路:
         * 使用一个小堆来进行操作,先将k个单链表的第一个结点入堆,再取堆中的最小素,此为最小的元素,
         * 将这个元素的下一个结点堆,再取堆中最小的,依次操作直到堆为空
         * </pre>
         *
         * @param lists
         * @return
         */
        public ListNode mergeKLists(ListNode[] lists) {
    
            // 为空或者没有元素
            if (lists == null || lists.length < 1) {
                return null;
            }
    
            // 只有一个元素
            if (lists.length == 1) {
                return lists[0];
            }
    
            // 创建一个小顶堆,并且使用一个匿名内部类作为比较器
            MinHeap<ListNode> minHeap = new MinHeap<ListNode>(new Comparator<ListNode>() {
                @Override
                public int compare(ListNode o1, ListNode o2) {
                    if (o1 == null) {
                        return -1;
                    }
    
                    if (o2 == null) {
                        return 1;
                    }
    
                    return o1.val - o2.val;
                }
            });
    
    
            // 将数组中链表的第一个结点入堆
            for (ListNode node : lists) {
                if (node != null) {
                    minHeap.add(node);
                }
            }
    
            // 头结点,作辅助使用
            ListNode head = new ListNode(0);
            // 当前处理的结点
            ListNode curr = head;
    
            while (!minHeap.isEmpty()) {
                ListNode node = minHeap.deleteTop();
    
                // 结点的下一个结点不为空就将下一个结点入堆
                if (node.next != null) {
                    minHeap.add(node.next);
                }
    
                curr.next = node;
                curr = node;
            }
    
            return head.next;
        }
    
        /**
         * 小顶堆
         *
         * @param <T>
         */
        private static class MinHeap<T> {
            // 堆中元素存放的集合
            private List<T> items;
    
            private Comparator<T> comp;
    
            /**
             * 构造一个椎,始大小是32
             */
            public MinHeap(Comparator<T> comp) {
                this(32, comp);
            }
    
            /**
             * 造诣一个指定初始大小的堆
             *
             * @param size 初始大小
             */
            public MinHeap(int size, Comparator<T> comp) {
                items = new ArrayList<>(size);
                this.comp = comp;
            }
    
            /**
             * 向上调整堆
             *
             * @param index 被上移元素的起始位置
             */
            public void siftUp(int index) {
                T intent = items.get(index); // 获取开始调整的元素对象
    
                while (index > 0) { // 如果不是根元素
                    int parentIndex = (index - 1) / 2; // 找父元素对象的位置
                    T parent = items.get(parentIndex);  // 获取父元素对象
                    if (comp.compare(intent, parent) < 0) { //上移的条件,子节点比父节点小
                        items.set(index, parent); // 将父节点向下放
                        index = parentIndex; // 记录父节点下放的位置
                    } else { // 子节点不比父节点小,说明父子路径已经按从小到大排好顺序了,不需要调整了
                        break;
                    }
                }
    
                // index此时记录是的最后一个被下放的父节点的位置(也可能是自身),
                // 所以将最开始的调整的元素值放入index位置即可
                items.set(index, intent);
            }
    
            /**
             * 向下调整堆
             *
             * @param index 被下移的元素的起始位置
             */
            public void siftDown(int index) {
                T intent = items.get(index);  // 获取开始调整的元素对象
                int leftIndex = 2 * index + 1; // // 获取开始调整的元素对象的左子结点的元素位置
    
                while (leftIndex < items.size()) { // 如果有左子结点
                    T minChild = items.get(leftIndex); // 取左子结点的元素对象,并且假定其为两个子结点中最小的
                    int minIndex = leftIndex; // 两个子节点中最小节点元素的位置,假定开始时为左子结点的位置
    
                    int rightIndex = leftIndex + 1;  // 获取右子结点的位置
                    if (rightIndex < items.size()) {  // 如果有右子结点
                        T rightChild = items.get(rightIndex);  // 获取右子结点的元素对象
                        if (comp.compare(rightChild, minChild) < 0) {  // 找出两个子节点中的最小子结点
                            minChild = rightChild;
                            minIndex = rightIndex;
                        }
                    }
    
                    // 如果最小子节点比父节点小,则需要向下调整
                    if (comp.compare(minChild, intent) < 0) {
                        items.set(index, minChild); // 将子节点向上移
                        index = minIndex; // 记录上移节点的位置
                        leftIndex = index * 2 + 1; // 找到上移节点的左子节点的位置
                    } else { // 最小子节点不比父节点小,说明父子路径已经按从小到大排好顺序了,不需要调整了
                        break;
                    }
                }
    
                // index此时记录是的最后一个被上移的子节点的位置(也可能是自身),
                // 所以将最开始的调整的元素值放入index位置即可
                items.set(index, intent);
            }
    
            /**
             * 向堆中添加一个元素
             *
             * @param item 等待添加的元素
             */
            public void add(T item) {
                items.add(item); // 将元素添加到最后
                siftUp(items.size() - 1); // 循环上移,以完成重构
            }
    
            /**
             * 删除堆顶元素
             *
             * @return 堆顶部的元素
             */
            public T deleteTop() {
                if (items.isEmpty()) { // 如果堆已经为空,就报出异常
                    throw new RuntimeException("The heap is empty.");
                }
    
                T maxItem = items.get(0); // 获取堆顶元素
                T lastItem = items.remove(items.size() - 1); // 删除最后一个元素
                if (items.isEmpty()) { // 删除元素后,如果堆为空的情况,说明删除的元素也是堆顶元素
                    return lastItem;
                }
    
                items.set(0, lastItem); // 将删除的元素放入堆顶
                siftDown(0); // 自上向下调整堆
                return maxItem; // 返回堆顶元素
            }
    
            /**
             * 判断堆是否为空
             *
             * @return true是空,false否
             */
            public boolean isEmpty() {
                return items.isEmpty();
            }
        }
    
    }
    

    三、总结

        通过上面的分析我们可以发现只有不断的优化我们的算法,才能使得效率不断地提升,同时也锻炼了我们的思考能力。

  • 相关阅读:
    诸葛亮的后半生:狗笼子里挥舞丈八蛇矛
    一句话摘录
    【书摘】The Joshua tree epiphany
    玩具程序:bigInt
    旅行的力量
    记忆的力量
    快的力量
    Windbg学习笔记【4】
    戴尔笔记本win8全新安装
    悟透JavaScript
  • 原文地址:https://www.cnblogs.com/zyrblog/p/10220664.html
Copyright © 2011-2022 走看看