【问题标题】:Optimal way to store an element in a sorted list将元素存储在排序列表中的最佳方式
【发布时间】:2021-03-08 07:04:10
【问题描述】:

你维护一个敌人列表的数组 A,按照难度降序排列,即最难的 bug 将是数组的第一个元素。最初,代码中没有错误。你有 N 个任务。

输入格式:

第一行包含一个整数N,表示任务数。

接下来的 N 行包含下面提到的两种操作之一。

  1. 1 P:将难度为 P 的 bug 添加到数组 A 中。

  2. 2:对数组进行降序排序,在排序后的数组中打印(n / 3)thbug的难度,其中n为数组A的大小。如果bug个数小于3,则打印Not enough evidence。

代码:

def get_index(a, v):
    lo = 0
    hi = len(a)
    if not a:
        return 0
    else:
        while lo < hi:
            mid = (lo+hi)//2
            if a[mid] > v:
                lo = mid+1 
            else: 
                hi = mid                
        return hi

t = int(input())

arr = []
for _ in range(t):
    op = input().split()
    if len(op) == 2:
        index = get_index(arr, int(op[1]))
        arr.insert(index, int(op[1]))
    elif len(arr) < 3:
        print("Not enough enemies")
    else:
        print(arr[(len(arr)//3)-1])

约束:

Getting Time Limit Exceeding error for #input 17(可能是一个大输入)。 Here is a complete question 来自 HackerEarth。

如何针对大输入优化代码?

【问题讨论】:

  • 追加新元素并仅在输入 2 时对数组进行排序不是更快吗?
  • @gionni 这种方法会增加时间,因为我们每次获得输入中的 2 个元素时都需要排序(我尝试过,但更多测试用例出现时间限制错误)。
  • 好吧,我想如果你输入一个交替的 1 和 2 操作序列,它会花费更长的时间,但否则应该花费更少的时间。无论如何,我认为this 可能会有所帮助。

标签: python arrays algorithm sorting


【解决方案1】:

旨在进行一些算法改进,例如惰性排序或quick select

【讨论】:

  • 你能实现吗?
【解决方案2】:

似乎是使用 Python heapq 标准库在添加到列表时保持排序顺序而不需要额外代码的理想案例。

查看源问题,问题设置者:

  1. 需要说明是否要在 not enough ... 字符串的末尾打印句号。
  2. 需要提到整数除法,即 Python 中的 // 而不是 /。

代码

我决定使用标准输入,这留给你作为练习,但下面的代码给出了预期的输出。 (不过,您可能想进一步测试它)。

他们没有说是否允许他们偷偷摸摸地添加超出初始输入行数的额外输入行。我决定只阅读规定的行数 - 额外的被忽略。

# -*- coding: utf-8 -*-
"""
https://stackoverflow.com/questions/66525537/optimal-way-to-store-an-element-in-a-sorted-list/66527881#66527881
https://www.hackerearth.com/practice/algorithms/searching/binary-search/practice-problems/algorithm/victory-over-power-4a0cb459/
https://docs.python.org/3/library/heapq.html

Created on Mon Mar  8 10:20:29 2021

@author: Paddy3118
"""
from heapq import heapify, heappush, heappop, nlargest, nsmallest


inp = """10
1 1
1 7
2
1 9
1 21
1 8
1 5
2
1 9
2""".strip().split('\n')

inp_gen = (line.strip() for line in inp)
line_count = int(next(inp_gen))
h = []
heapify(h)
for n in range(line_count):     # Extra lines ignored
    line = next(inp_gen)
    field = [int(n) for n in line.split()]
    if field[0] == 1:
        heappush(h, field[1])
    else:
        if (lh := len(h)) < 3:
            print('Not enough enemies')
        else:
            print(nlargest(lh // 3, h)[-1])

【讨论】:

  • 我也试过这个,但它给出了错误的答案。似乎对于列表的大长度,它不会按预期工作。我提到了this 代码。如果可能,您能否以代码格式添加解决方案?
  • 嗯,如果使用 heapq 适用于小堆。它也应该适用于大型的。它可能是内存或时间限制的,但可能会为您的代码添加更多的极端情况检查以获取更小的列表。
  • 相同的代码(存在于 OP 中)适用于除#input 17 之外的所有测试用例。我已将排序部分替换为 heapq,但大多数输入的结果都错误。跨度>
  • 添加了适用于他们的示例并使用 heapq 的代码。请注意,虽然 h[0] 保证是 h 中的最小值,但对于其他值,您需要 nlargest/nsmallest。
  • 您的解决方案对于#input 17 工作正常,但对于其他测试用例,这不起作用,超出时间限制错误。但是感谢heapq 方法。我认为nlargest 将需要很长时间才能进行交替 1 和 2 操作。
【解决方案3】:

这个问题最简单的高效解决方案使用两个堆:一个用于最高 n/3 元素的最小堆,一个用于剩余(较低)元素的最大堆。

添加项时,需要将其与高堆中的最小元素进行比较。如果比较大就放到high heap,如果比较小就放到low heap。

然后,如有必要,在堆之间移动一个元素以将 n/3 个元素保留在高堆中。

添加操作需要 O(log N) 时间,并且第 n/3 个元素始终可以立即作为高堆中的最小元素使用。

【讨论】:

    【解决方案4】:

    TLDR:将列表替换为collection.deque,并引入检查以查看新元素是在列表的开头还是结尾。以下代码通过了所有测试:

    from collections import deque
    
    def get_index(a, v):
        lo = 0
        hi = len(a)
        if not a:
            return 0
        elif a[0] <= v:
            return 0
        elif v <= a[hi - 1]:
            return hi
        else:
            while lo < hi:
                mid = (lo+hi)//2
                if a[mid] > v:
                    lo = mid+1 
                else: 
                    hi = mid                
            return hi
    
    t = int(input())
    
    arr = deque()
    for _ in range(t):
        op = input().split()
        if len(op) == 2:
            index = get_index(arr, int(op[1]))
    
            arr.insert(index, int(op[1]))
        elif len(arr) < 3:
            print("Not enough enemies")
        else:
            print(arr[(len(arr)//3)-1])
    

    详细解释:正如您从 cmets 中看到的,我的第一个想法是使用排序,但在大多数情况下它显然比 OP 解决方案慢。

    然后我尝试运行 OP 解决方案,发现它仅针对 #input17 失败。检查输入,我发现这是您的二分插入算法的最坏情况,因为它只是一个递增数字的列表。

    这很容易解决,我只是添加了一个显式检查以查看新元素是大于arr[0] 还是小于arr[hi-1]

    添加它本身并没有使算法通过测试,这起初让我感到惊讶。经过一番调查,我明白问题出在插入(duh),它需要O(n) 操作在开头添加一个元素(基本上是测试用例#17 中的每一步)。这就是collection.deque 发挥作用的地方。感谢deque,您可以使用O(1) 操作在开头插入(我确定appendleft 这么快,我猜插入也一样快)。

    【讨论】:

    • 谢谢。很高兴知道列表的插入需要O(n)。如果我用bisect lib 替换了get_index 函数,则接受#input17。
    猜你喜欢
    • 2011-09-17
    • 2012-07-25
    • 2014-03-08
    • 2012-07-15
    • 1970-01-01
    • 2019-02-28
    • 1970-01-01
    • 2010-10-14
    • 2014-07-08
    相关资源
    最近更新 更多