迭代字符串的时间复杂性实际上是O(n ^ 2)还是O(n)?


88

我正在处理CTCI之外的问题。

第1章的第三个问题是您采用了诸如

'Mr John Smith '

并要求您将中介空间替换为%20

'Mr%20John%20Smith'

作者使用Python提供了此解决方案,称其为O(n):

def urlify(string, length):
    '''function replaces single spaces with %20 and removes trailing spaces'''
    counter = 0
    output = ''
    for char in string:
        counter += 1
        if counter > length:
            return output
        elif char == ' ':
            output = output + '%20'
        elif char != ' ':
            output = output + char
    return output

我的问题:

我了解这是从左到右扫描实际字符串方面的O(n)。但是Python中的字符串不是一成不变的吗?如果我有一个字符串,并且用+运算符向它添加了另一个字符串,它是否分配必要的空间,复制原始字符串,然后复制附加字符串?

如果我有一个n长度为1的字符串集合,则需要:

1 + 2 + 3 + 4 + 5 + ... + n = n(n+1)/2

还是O(n ^ 2)时间,是吗?还是我在Python处理附加方式方面弄错了?

或者,如果您愿意教我如何钓鱼:我将如何亲自找到答案?我尝试向Google寻求官方消息一直没有成功。我发现https://wiki.python.org/moin/TimeComplexity,但这在字符串上没有任何内容。


17
有人应该告诉笔者约urllib.urlencode
维姆

10
@wim这是关于数组和字符串的实践问题
user5622964

3
这本书的目的是教面试问题,这些问题通常要求您重新发明轮子以了解被访者的思维过程。
詹姆斯·维尔茨巴

1
由于它是Python,我认为最好在的范围内进行,rtrim并且replace最好使用O(n)。复制字符串似乎不是最有效的方法。
OneCricketeer

2
@RNar您能解释一个副本如何花固定的时间吗?
詹姆斯·维尔茨巴

Answers:


83

在CPython(Python的标准实现)中,有一个实现细节,使得该通常为O(n)的形式在字节码评估循环调用++=使用两个字符串操作数的代码中实现。如果Python检测到left参数没有其他引用,它将调用realloc来尝试通过调整字符串的大小来避免复制。这不是您应该依靠的东西,因为它是一个实现细节,并且因为如果realloc最终需要频繁移动字符串,性能无论如何都会降低到O(n ^ 2)。

在没有怪异的实现细节的情况下,由于涉及的二次复制量,该算法为O(n ^ 2)。这样的代码仅在具有可变字符串的语言(例如C ++)中才有意义,甚至在您想使用的C ++中也是如此+=


2
我正在查看您链接的代码...看起来该代码的很大一部分正在清理/删除对要附加的字符串的指针/引用,对吗?然后到最后,它执行_PyString_Resize(&v, new_len)为串联的字符串分配内存,然后memcpy(PyString_AS_STRING(v) + v_len, PyString_AS_STRING(w), w_len);由它来执行复制。如果就地调整大小失败,它PyString_Concat(&v, w);也会(我认为这意味着当原始字符串地址末尾的连续内存不可用时)。这如何显示提速?
user5622964

我在前面的评论中没有足够的空间,但是我的问题是我是否正确理解了该代码以及如何解释这些代码的内存使用/运行时。
user5622964

1
@ user5622964:糟糕,忘记了奇怪的实现细节。没有有效的调整大小政策;它只是呼唤realloc并希望最好。
user2357112支持Monica

memcpy(PyString_AS_STRING(v) + v_len, PyString_AS_STRING(w), w_len);工作如何?根据cplusplus.com/reference/cstring/memcpy的定义void * memcpy ( void * destination, const void * source, size_t num );和描述:"Copies the values of num bytes from the location pointed to by source directly to the memory block pointed to by destination."在这种情况下,num是附加字符串的大小,source是第二个字符串的地址,我认为是吗?但是,为什么目的地(第一个字符串)+ len(第一个字符串)呢?双重记忆?
user5622964

7
@ user5622964:这是指针算法。如果您想了解CPython源代码直到奇怪的实现细节,就需要知道C。超级压缩版本是PyString_AS_STRING(v)第一个字符串数据v_len的地址,加起来可以得到字符串后面的地址。数据结束。
user2357112支持Monica 2015年

38

作者依靠的恰好是这里的优化,但不是明确可靠的。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),但这没关系,因为它在循环之外。


这个答案很好,因为它告诉您如何串联字符串。
user877329'7

在计算运行时间方面的精确答案。
Intesar Haider

25

我在Python Speed>使用最佳算法和最快工具上找到了这段文本:

字符串连接最好通过''.join(seq)一个O(n)过程来完成。相反,使用'+'or'+='运算符可能会导致O(n^2)过程,因为可能会为每个中间步骤构建新的字符串。CPython 2.4解释器在某种程度上缓解了这个问题。但是,''.join(seq)仍然是最佳做法


3

对于将来的访问者:由于这是一个CTCI问题,因此此处不需要任何有关学习urllib包的引用,特别是根据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'))
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.