Javascript - 循环内拼接和连接的时间和空间复杂度

Posted

技术标签:

【中文标题】Javascript - 循环内拼接和连接的时间和空间复杂度【英文标题】:Javascript - Time and space complexity of splice and concat inside loop 【发布时间】:2019-05-05 03:42:49 【问题描述】:

我有一个问题,需要通过将其初始值的副本附加到自身来将字符串转换为另一个字符串。该问题允许在某些地方删除单个字符。

解释

let x = "abba"; // First string
let y = "aba" // Second initial string

y("aba") => remove last "a" => y("ab") => y+initialY = "ab"+"aba" => 
y("ababa") => remove char at index 2 => y("abba") => y == x => sucess

我的算法成功解决了问题:

let x = "abbbbcccaaac"
let y = "abc"

let xArr = x.split('')
let yArr = y.split('')

let count = 0;

for (let i = 0; i < xArr.length; i++) 
  if(yArr[i] == undefined) 
    yArr = yArr.concat(y.split(''));  
    count++;
  
  if(xArr[i] != yArr[i]) 
    yArr.splice(i, 1);
    i--;
  


console.log("Output is:", yArr.join(''))
console.log("Appends in order to transform:", count)

该算法按预期工作,但是,我不确定它的时间和空间复杂性,最重要的是效率。

    这个算法是否在O(n) 的时间复杂度中,其中n 是x 的长度?

    如果不是O(n),问题能在O(n)的时间内解决吗?

    .concat().splice().split() 是否会以某种方式改变时间复杂度,因为它们嵌套在 for 循环中?如果不是,它们是否仍然会改变算法的时间复杂度?改变多少?

    鉴于这个问题的规律,这是一种有效的解决方法吗?

    这个算法的空间复杂度是多少?

【问题讨论】:

其实,如果我们真的知道 splice、split 或 concat 的时间复杂度,那么它显然会影响你算法的整体时间复杂度。但是由于我们不这样做,我们会将它们视为恒定操作,O(1),这实际上将循环操作保持在 O(N) - 最坏的情况。空间复杂度为 O(1),因为我们没有为每个循环操作创建新的存储(在本例中为计数),而只是更新它。 IMO,这是一个公平的解决方案,因为我不知道您给定问题的限制是什么。 @CalebLucas “但既然我们不这样做,我们会认为它们是一个持续的操作”——我们应该这样做吗?在那种情况下,这个问题仍然有意义吗?此外,ECMA 规范提供了关于它们是什么的提示。 @meowgoesthedog “我们应该吗?” - 我觉得这取决于一个人的目标。这些函数在运行时由 javascript 引擎优化。如果目标是真正深入研究每个功能的细节及其在给定场景中的应用,那么考虑到它们的时间复杂性当然是最重要的。 @CalebLucas 你是对的 split() ,concat() 等已经被JS引擎优化。然而,这并不意味着这些操作没有时间/空间复杂性。根据问题的意图,有2个答案。如果这是为了面试,那么我们需要考虑这些方法的时间和空间复杂性。如果是应用程序,您无需担心。看问题,这似乎是面试问题,因为那个 OP 需要考虑这些事情,因为下一个问题将是这个算法的复杂性 【参考方案1】:

通常这样的问题很难给出明确的答案,因为不同的 Javascript 实现对于基本数组操作(例如creating a new array of size n)具有不同的时间复杂度。 Javascript 数组通常会以dynamic arrays 或hashtables 的形式实现,这些数据结构具有不同的性能特征。

因此,splice 从数组中删除一个元素没有明确的时间复杂度。我们可以说的是,删除一个元素需要动态数组的线性时间,正如@Ry- 在 cmets 中指出的那样,哈希表也需要线性时间,因为需要重新编号后面的索引。我们也可以说很有可能使用了这两种数据结构中的一种,并且没有任何明智的实现将花费更多超过线性时间来完成splice

无论哪种方式,您的算法最糟糕的情况是x = 'aa...aa'y = 'abb...bb',即x'a' 的n 个副本,而y'a',后跟(m - 1) 个副本'b'.

对于动态数组哈希表,仅splice 操作的时间复杂度为 O(nm²)。这是因为外部循环迭代 O(nm) 次(注意循环内的 i--,每次需要删除字母 'b' 时都会发生这种情况),而 splice 操作需要移位或重新编号 O(m ) 在索引i 之后的yArr 中的元素。

但是假设使用了一些更奇特的数据结构,它支持在亚线性时间内删除元素(例如skip list)。在那种情况下,上面只给出了 O(nm) 倍的“删除”操作的复杂性。但是我们还没有算上concat;这会创建一个新的数据结构并将每个项目复制到其中,这仍然需要线性时间。 concat 被调用 O(n) 次,每次调用平均花费 O(n + m) 时间,因此仅 concat 操作的复杂度为 O(n² + nm)。

所以时间复杂度很可能是O(n² + nm²),当然至少是O(n² + nm);不是线性的。


空间复杂度为 O(n),因为 yArr 的长度永远不会超过 xArr 的两倍。

【讨论】:

splice(i, 1) 对于索引 -> 值哈希表来说不是 O(1),因为它必须对所有内容重新编号。

以上是关于Javascript - 循环内拼接和连接的时间和空间复杂度的主要内容,如果未能解决你的问题,请参考以下文章

javascript中字符串和数字怎么拼接

急!如何用javascript写一个html页内关键字的搜索

使用javascript在循环内设置超时

JavaScript回炉重造

Java8中使用"+"进行字符串拼接还是使用StringBuilder?

表连接