我很难理解Python列表切片的空间复杂性。
对于
Thread
是为切片分配了新空间(就像在String中那样,因为它们是不可变的)还是在同一数组上完成了操作?
对于类似的东西
arr[2:] = arr[2:][::-1]
ans = [i+1 for i in range(n)]
空间复杂度是多少?它会与ans是字符串(例如for i in range(k):
ans[i:] = ans[i:][::-1]
之类的字符串)不同还是相同?
答案 0 :(得分:1)
简而言之,每个列表切片操作都涉及复制相关对象引用(而不是对象本身)。
在您的示例中:
arr[2:]
复制从索引2开始存储在arr
中的对象引用,并将它们放入未命名的新列表中(我将其称为L1
)。 [::-1]
复制L1
中的对象引用,并将它们以相反的顺序放置到未命名的新列表中(我将其称为L2
)。 / li>
arr[2:] = ...
将arr
中存储的对象引用替换为L2
中存储的对象引用。值得注意的是,这些都不是可以保证的。这就是CPython当前所做的。
一些相关功能是:
list_slice
-简单切片(无步幅)list_subscript
-下标,公司。扩展切片(带步幅)list_ass_slice
-简单的切片分配(无步幅)list_ass_subscript
-下标分配公司。使用扩展切片看看list
的实现:https://github.com/python/cpython/blob/master/Objects/listobject.c
有一些有趣的花絮,例如code that protects against a[::-1] = a
。
答案 1 :(得分:1)
Python严格遵守可能的副作用。就语言而言,就地执行您的任何操作都是不正确。
您的操作
arr[2:] = arr[2:][::-1]
是三个单独的切片操作:
arr[2:]
从arr
的所有元素(但前两个)创建一个新列表。...[::-1]
根据...
的所有元素创建一个新的反向列表。arr[2:] = ...
用arr
替换了...
的所有元素(但前两个)。每个切片操作基本上等于原始O(n)复制操作。由于仅复制引用,因此元素的大小或类型无关紧要。
在您完整的示例中,在O(k)循环中这相当于三个O(n)切片操作:
ans = [i+1 for i in range(n)] # O(n)
for i in range(k): # O(k)
ans[i:] = ans[i:][::-1] # O(n)
总的来说,时间复杂度为O(nk)。空间复杂度仅为O(n),因为临时切片可立即回收。您基本上可以得到初始列表以及一些临时副本。
与
ans
是字符串(例如ans = '12345...n'
)一样还是不同?
操作的复杂性不会改变-它们仍然是相同的原始操作。
实际上,CPython实现会欺骗某些对象。例如,+=
是inplace mutation for strings with refcount 1,即使字符串是不可变的。但是,这不适用于您的分片使用。
通常,在Python中依靠内置的优化是一个坏主意。
如果您担心空间不足,请从编写精益代码开始。例如,ans[i:][::-1]
应该只是ans[i::-1]
。仅此一项就将所需的临时空间减少了一半。