Python O(n)复杂度算法,无需remove()方法即可从未排序列表中删除值的实例

Python O(n)复杂度算法,无需remove()方法即可从未排序列表中删除值的实例,python,python-3.x,Python,Python 3.x,我有一个家庭作业问题要写一个函数,它是BagofWord类的一部分,用于从未排序的列表中删除值的实例。我们可以使用的列表操作不包括remove()。我们只需要O(n)复杂度,而naive算法的性能并不那么好 我尝试了一个简单的算法。这是一个太复杂的算法。它使用list.pop(index),其本身具有O(n)复杂性,并且有两个循环。因为我们不允许使用list.remove(),而且列表理解也会有同样的复杂性,但语法更简洁,所以我正在尝试找到更好的实现 我认为解决方案可能是一种快速排序算法,因为如

我有一个家庭作业问题要写一个函数,它是BagofWord类的一部分,用于从未排序的列表中删除值的实例。我们可以使用的列表操作不包括remove()。我们只需要O(n)复杂度,而naive算法的性能并不那么好

我尝试了一个简单的算法。这是一个太复杂的算法。它使用list.pop(index),其本身具有O(n)复杂性,并且有两个循环。因为我们不允许使用list.remove(),而且列表理解也会有同样的复杂性,但语法更简洁,所以我正在尝试找到更好的实现

我认为解决方案可能是一种快速排序算法,因为如果我首先对列表进行排序,我可能能够以O(n)的复杂度完成这项工作。但是,如果没有pop(索引)的复杂性,我将如何删除此项?现在我想知道通过KMP算法搜索模式是解决方案还是散列

 def remove(self, item):
        """Remove all copies of item from the bag. 
        Do nothing if the item doesn't occur in the bag.
        """
        index = 0
        while index < len(self.items):
            if self.items[index] == item:
                self.items.pop(index)
            else:
                index += 1
def移除(自身,项目):
“”“从包中删除项目的所有副本。”。
如果行李中没有物品,请不要执行任何操作。
"""
索引=0
而索引
复杂性是二次的。然而,我想要一个O(n)的复杂度


编辑:为了澄清,我们实际上必须修改现有列表

如果列表中的元素是相对较小的整数或可以表示为较小的整数,则可以用O(max(maxValue,n))进行排序

另一种方法是为列表中的每个元素提供指向上一个和下一个元素的指针。这样你就可以删除O(1)中的一个元素。然而,这使得通过索引获取项目的操作在O(n)时间内运行

此外,如果项目的顺序无关紧要,您可以存储像
(项目,计数)
这样的对,其中
计数
项目
出现的次数,那么您只需要为给定的
项目
删除一个这样的对,并且具有所需的复杂性


希望有帮助

编辑:最简单的方法是使用列表理解:

self.items = [x for x in self.items if x != item]
它是O(n),比下面的选项快。它也是迄今为止最“pythonic”的


但是,它会创建列表的新副本。如果您实际上被迫修改现有列表,以下是我的原始答案:

下面是一个“就地”O(n)算法,它使用两个指针向下折叠列表,删除不需要的元素:

ixDst = 0
for ixSrc in range(0, len(items)):
  if items[ixSrc] != item:
    items[ixDst] = items[ixSrc]
    ixDst += 1
del items[ixDst:]
(看它运行)

唯一有问题的部分是使用
del
向下调整列表的大小。我相信这是正确的,而且“应该”是O(1),因为我们要删除的部分位于列表的末尾

此外,@chepner在评论中还提出了一个更具pythonic风格的就地答案(而且速度更快):

self.items[:] = (x for x in self.items if x != item)

感谢@juanpa.arrivillaga和@chepner的讨论。

如果您需要它来执行就地删除,您可以将项目移到已删除的项目上,并在最后的一次操作中截断列表:

index = 0
for value in self.items:
    if value != item : 
        self.items[index] = value
        index += 1
del self.items[index:]
如果您有能力创建一个新列表(没有列表理解),您可以这样做:

cleanedItems = []
for value in items:
    if value != item: cleanedItems.append(value)
self.items = cleanedItems 

你能使用
del self.items[x]
?@JohnGordon仍然会有
.pop
相同的问题。remove
,也就是说,从列表中删除元素总的来说是O(N^2)。你不能按O(N)排序,不计算排序也会作弊。等等,我刚刚注意到:“因为列表理解会有同样的复杂性”-这不是真的,你可以写一个O(n)列表理解如@juanpa在下面所评论的。它复制了列表,但如果这是可以接受的,它就是正确的解决方案。在C语言中,这一切都很好,在C语言中,您可以手动创建一个双链接列表来实现这一点,但在Python中您永远不会这样做,几乎可以肯定您会坚持使用
列表
data类型,您只需创建一个新列表。注意,Python没有指针。惯用的O(N)解决方案是
self.items=[x代表self.items中的x,如果x==item]
嗯,是的,我同意这是实践中最好的方法,pythonic和快速编码,我自己会使用它。在我的回答中,我只是试图展示一些算法方法,经典的方法)此外,似乎它应该是
self.items=[x为x在self.items中,如果x!=item]
你会这样做,因为OP说“在适当的位置”“这是必须的。我同意这不是最好的方法,但这是可能的,并且不会比列表理解慢。好吧,时间复杂度是一样的,但是由于不断的因素,列表理解肯定会更快。无论如何,我不认为该行动必须到位。OP应该澄清一下。照此看来,目前尚不清楚是否只需对行李对象执行操作,行李对象使用
列表
存储物品。在这种情况下,就地修改列表或创建一个新列表并重新分配它可能是“就地”的,但我希望看到这方面的基准测试(如果出于某种原因性能特别重要的话)。好的,for循环版本将执行得更紧密,但我会使用
for ix_src,enumerate(items)中的元素
以获得更好的性能。在Python中手动索引速度很慢。每个索引操作都涉及一个特殊的方法解析(公平地说,比常规方法解析快),然后在解释器级别调用相应的函数。相反,直接在列表上迭代会将所有这些内容向下推到C层。尽管如此,
items[ixDst]=items[ixSrc]
如果有很多元素要过滤,操作将减慢速度。此外,
self.items[:]=(self.items中的x代表x,如果x!=item)
中的genexp只是g