Python 迭代字符串追加的时间复杂度实际上是O(n^2)还是O(n)?
我正在解决CTCI的一个问题 第1章的第三个问题是,你需要一个字符串,比如Python 迭代字符串追加的时间复杂度实际上是O(n^2)还是O(n)?,python,string,algorithm,time-complexity,string-concatenation,Python,String,Algorithm,Time Complexity,String Concatenation,我正在解决CTCI的一个问题 第1章的第三个问题是,你需要一个字符串,比如 “约翰·史密斯先生” 并要求您将中间空格替换为%20: 'Mr%20John%20Smith' 作者用Python提供了这个解决方案,称之为O(n): 我的问题: 我知道这是O(n),从左到右扫描实际字符串。但是Python中的字符串不是不可变的吗?如果我有一个字符串,并且我使用+操作符向其中添加了另一个字符串,它是否分配了必要的空间,在原始字符串上复制,然后在附加字符串上复制 如果我有一个长度为1的n字符串集合,则需要
“约翰·史密斯先生”
并要求您将中间空格替换为%20
:
'Mr%20John%20Smith'
作者用Python提供了这个解决方案,称之为O(n):
我的问题:
我知道这是O(n),从左到右扫描实际字符串。但是Python中的字符串不是不可变的吗?如果我有一个字符串,并且我使用+
操作符向其中添加了另一个字符串,它是否分配了必要的空间,在原始字符串上复制,然后在附加字符串上复制
如果我有一个长度为1的n
字符串集合,则需要:
1+2+3+4+5+…+n=n(n+1)/2
或者O(n^2)时间,是吗?或者我在Python处理附加的方式上搞错了
或者,如果你愿意教我如何钓鱼:我将如何为自己找到答案?我试图用谷歌搜索一个官方消息来源,但没有成功。我发现,但这没有任何关于字符串的内容。在Python的标准实现CPython中,有一个实现细节,使得它通常是O(n),在中实现。如果Python检测到左参数没有其他引用,它将调用
realloc
,试图通过调整字符串大小来避免复制。这不是您应该依赖的东西,因为这是一个实现细节,并且因为如果realloc
最终需要频繁移动字符串,那么无论如何性能都会降低到O(n^2)
没有奇怪的实现细节,由于涉及到二次复制量,该算法是O(n^2)。这样的代码只在一个具有可变字符串的语言中是有意义的,比如C++,甚至在C++中,你也希望使用<代码> += 。 字符串连接最好用
''完成。join(seq)
是一个O(n)
过程。相反,使用'+'
或'+='
运算符可能会导致一个O(n^2)
过程,因为可能会为每个中间步骤生成新字符串。CPython 2.4解释器在某种程度上缓解了这个问题;然而,'.join(seq)
仍然是最佳实践
作者依赖于一个恰好在这里的优化,但并不明确可靠
strA=strB+strC
通常是O(n)
,使函数O(n^2)
。但是,很容易确定整个过程是O(n)
,使用数组:
output = []
# ... loop thing
output.append('%20')
# ...
output.append(char)
# ...
return ''.join(output)
简而言之,append
操作是分期摊销的O(1)
,(尽管可以通过将数组预分配到正确的大小使其成为强O(1)
),使循环O(n)
然后,
join
也是O(n)
,但这没关系,因为它在循环之外。对于未来的访问者:因为这是一个CTCI问题,这里不需要任何关于学习包的参考,特别是根据OP和本书,这个问题是关于数组和字符串的
以下是一个更完整的解决方案,灵感来自@njzk2的伪:
text = 'Mr John Smith'#13
special_str = '%20'
def URLify(text, text_len, special_str):
url = []
for i in range(text_len): # O(n)
if text[i] == ' ': # n-s
url.append(special_str) # append() is O(1)
else:
url.append(text[i]) # O(1)
print(url)
return ''.join(url) #O(n)
print(URLify(text, 13, '%20'))
应该有人告诉作者关于
urllib.urlencode
@wim这是一个关于数组和字符串的实践问题。这本书的目的是教授面试问题,这通常要求你重新发明轮子来查看面试者的思维过程。因为它是Python,我认为做一个rtrim
和replace
将是更可取的,并且是大致的O(n)
。在字符串上复制似乎是最不有效的方法。@r您能解释一下复制是如何花费固定时间的吗?我正在查看您链接的代码。。。这段代码的很大一部分似乎是在清理/删除所附加字符串的指针/引用,对吗?然后在最后执行\u PyString\u Resize(&v,new\u len)
为连接的字符串分配内存,然后执行memcpy(PyString\u AS\u string(v)+v\u len,PyString\u AS\u string(w),w\u len)代码>哪个进行复制。如果就地调整大小失败,则会执行PyString\u Concat(&v,w)代码>(我假设这意味着原始字符串地址末尾的连续内存不可用时)。这是如何显示加速的?我在上一篇评论中已经没有空间了,但我的问题是我是否正确理解了代码,以及如何解释这些代码的内存使用/运行时。@user5622964:哎呀,记错了奇怪的实现细节。没有有效的调整策略;它只是调用realloc
并希望得到最好的结果代码>工作?根据它有定义void*memcpy(void*destination,const void*source,size\u t num)代码>和说明:“将num字节的值从源指向的位置直接复制到目标指向的内存块。”
在本例中,num是附加字符串的大小,而source是第二个字符串的地址,我猜?但是为什么目的地(第一个字符串)+len(第一个字符串)?双内存?@user5622964:这是指针算法。如果你想了解CPython源代码到奇怪的实现细节,你需要知道C。超级压缩版本是PyString\u AS\u STRING(v)
是第一个字符串数据的地址,添加v_len
会在字符串的数据结束后立即获得地址。这个答案很好,因为它告诉您如何连接字符串。在计算运行时的上下文中,这个答案是精确的。
text = 'Mr John Smith'#13
special_str = '%20'
def URLify(text, text_len, special_str):
url = []
for i in range(text_len): # O(n)
if text[i] == ' ': # n-s
url.append(special_str) # append() is O(1)
else:
url.append(text[i]) # O(1)
print(url)
return ''.join(url) #O(n)
print(URLify(text, 13, '%20'))