在 python 中获取子字符串是 O(n) 操作吗? [复制]

Posted

技术标签:

【中文标题】在 python 中获取子字符串是 O(n) 操作吗? [复制]【英文标题】:Is taking a substring in python an O(n) operation? [duplicate] 【发布时间】:2021-09-25 01:41:38 【问题描述】:

在 C++ 中,如果我要从字符串中删除第一个字符,它看起来像:

string s = "myreallylongstring";
s = s.substr(1);

这将是 O(1)。 [如果我错了,请纠正我]

然而,在 Python 的“不可变字符串”世界中,这段代码是否在 O(n) 中运行?

s = "myreallylongstring"
s = s[1:]

如果我改用字符列表会更快吗?

【问题讨论】:

substr 在 C++ 中不是 O(1)。构造了一个新字符串。 substrO(n)。即使您知道在这种情况下n 恰好是1,它仍然是O(n) 复杂性。 Big-O 表示法将复杂性增长描述为n 趋于无穷大。 O(1) 的大 O 复杂度严格来说意味着复杂度与给定变量无关。 std::basic_string::substr() 的复杂性与被提取的子字符串的长度呈线性关系。提取除第一个字符之外的所有字符将意味着复杂度 O(length),其中 length 是字符串的原始长度。 std::basic_string::operator=() 在分配的字符串长度上也是线性的。净效应:s=s.substr(1) 为 O(length)。 @FrançoisAndrieux s.substr(1) 中的1 是要提取的第一个字符的索引,而不是要提取的字符串的长度。 注意:这里的标题问题是 Time complexity of string slice 的副本(我显然回答了,但在过去 3 个正常年份 + 1.5 个 COVID 年份忘记了,后者算作 15 个正常年份当谈到记住以前的时间时)。额外的问题(关于 C++ substr 在 big-O 上更快的 OP 是否正确,Python list 会更有效)不是。我不会使用我的 C++ 和 Python dupehammer,但如果其他人认为这是骗子,我不会反对。 【参考方案1】:

在 Python 中切片任何内置类型(memoryview 除外)在一般情况下是 O(n)(主要例外是不可变实例的完整切片,它通常返回原始实例而不复制它,因为它不是需要)。

list 的字符无济于事;从list 的开头删除一个字符是O(n)(它必须将其上方的所有内容复制到一个插槽中)。可以想象,collections.deque 可以改进 big-O(他们可以从任一端弹出 O(1)),但它也会比字符串消耗更多的内存和更多的碎片内存。

对于除了最大的字符串之外的所有字符串,即使有这些 O(n) 效率低下,您通常也可以,所以除非您实际分析并发现它是导致问题的原因,否则我会坚持切片并让它是。

也就是说,你对 C++ 的看法是错误的; s = s.substr(1) 与 Python 的 s = s[1:] 没有什么不同。它们最终都复制了整个字符串并保存了第一个字符,C++ move 分配回原始字符串,而 Python 用新对象替换了与旧对象的原始名称绑定(功能上非常相似的操作)。 s.substr(1) 甚至没有使用 std::string 的可变性特性; s.erase(0, 1) 实际上会就地擦除,改变原始字符串,但它仍然O(n),因为所有其他字符都必须被复制到之前被删除的空间中字符(我所知道的没有std::string 实现允许存储从字符串开头的偏移量以查找真实数据,指向数据的指针始终指向第一个分配的字节)。

【讨论】:

还有另一种在 O(1) 中切片的内置类型,ShadowRanger。 @don'ttalkjustcode:哈,你说得对。我自己使用该功能(range(10)[::-1]range(9, -1, -1) 更容易定义),但它是一种用途有限且灵活性很小的类型(如果您适当地使用 enumerate/zip/itertools,几乎不需要) 我不怎么想。 :-) 是的,我以前也使用过[::-1],但可能像使用memoryview 一样经常对它们使用其他切片,即从不(除了测试它是否确实有效)。【参考方案2】:

这个答案解决了问题的 C++ 部分

s = s.substr(1);

创建一个新的临时字符串并将其复制到s。操作是O(n)

更好的方法是:

s.erase(s.begin());

这仍然是O(n),但要避免创建新对象和复制。

更好的方法是使用std::string_view

auto sv = std::string_views.begin() + 1, s.end().

这是O(1),因为它不创建或更改任何字符串,它只是在现有字符串中创建一个视图

【讨论】:

这不是对 OP 关于 Python 问题的回答,只是纠正他们对 C++ 的误解。另外,std::stringremove 方法吗?我只在the docs 中看到eraseerase 可以与 s.begin() 一起使用,也可以与 0, 1 一起使用(用于从索引 0 中删除 1 个字符)。 @ShadowRanger 是的,我确实只谈论 C++ 部分。是的,remove 打错了,是erase

以上是关于在 python 中获取子字符串是 O(n) 操作吗? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

LeetCode高频算法面试题 最长回文子串

动态规划1.求最长回文子串

O(n)子字符串算法

有没有办法在 O(n) 时间内打印字符串的所有子字符串?

求最长回文子串,O(n)复杂度

求最长回文子串,O(n)复杂度