问题描述
谁能帮我理解算法的时间和空间复杂度来平衡括号
def isValid(s: String): Boolean = {
@annotation.tailrec
def go(i: Int,stack: List[Char]): Boolean = {
if (i >= s.length) {
stack.isEmpty
} else {
s.charat(i) match {
case c @ ('(' | '[' | '{') => go(i + 1,c +: stack)
case ')' if stack.isEmpty || stack.head != '(' => false
case ']' if stack.isEmpty || stack.head != '[' => false
case '}' if stack.isEmpty || stack.head != '{' => false
case _ => go(i + 1,stack.tail)
}
}
}
go(0,Nil)
}
根据我的理解,尾递归将空间复杂度降低到 0(1),但在这里我使用 List 的附加数据结构作为累加器,谁能解释一下如何计算空间复杂度和时间复杂度
解决方法
您的代码中存在一个错误:您仅在堆栈上推送括号,但弹出所有内容,因此此实现仅适用于仅包含括号的字符串......不确定那是否是意图。如果实现得当,它在时间上应该是线性的,空间复杂度也是线性的,但不是在整个字符串的长度上,只在它包含的括号数上。
val oc = "([{" zip ")]}"
object Open { def unapply(c: Char) = oc.collectFirst { case (`c`,r) => r }}
object Close { def unapply(c: Char) = oc.collectFirst { case (_,`c`) => c }}
object ## { def unapply(s: String) = s.headOption.map { _ -> s.tail }}
def go(s: String,stack: List[Char] = Nil): Boolean = (s,stack) match {
case ("",Nil) => true
case ("",_) => false
case (Open(r) ## tail,st) => go(tail,r :: st)
case (Close(r) ## tail,c :: st) if c == r => go(tail,st)
case (Close(_) ## _,_) => false
case (_ ## tail,st)
}
go(s)
(公平地说,这实际上是空间线性的,因为 s.toList
:)我内心的审美无法抗拒。如果你愿意,你可以把它改回 s.charAt(i)
,它看起来不再那么漂亮了......或者使用 s.head
和 `s。
我认为,当您将算法实现为尾递归函数而不是非尾递归函数时,顺序上不会有任何优势会在时间和空间复杂度上产生差异函数或循环,否则每个人都会这样做。尾递归只是防止您进入会导致堆栈溢出的深层嵌套递归调用。
您当前算法的时间复杂度应为 O(n),辅助空间复杂度应为 O(n),尾递归与否。
您仍然可以使用计数器而不是一堆括号将辅助空间复杂度降低到 O(1),但这与尾递归无关。 O(1) 辅助空间复杂度只有在您只处理使用计数器而不是堆栈跟踪的 1 类括号时才有可能。但是,如果考虑堆栈帧大小,非尾调用优化的递归可能仍然受限于 O(n)。
除了@Dima 提到的错误,如果我要重构您的解决方案,我会选择:
def isValid(s: String): Boolean = {
@annotation.tailrec
def go(l: List[Char],stack: List[Char] = Nil): Boolean = (l,stack) match {
case ((c @ ('(' | '[' | '{')) :: cs,xs) => go(cs,c :: xs)
case (')' :: cs,'(' :: xs) => go(cs,xs)
case (']' :: cs,'[' :: xs) => go(cs,xs)
case ('}' :: cs,'{' :: xs) => go(cs,xs)
case ((')' | ']' | '}') :: _,_) => false
case (_ :: cs,xs) => go(cs,xs)
case (Nil,xs) => xs.isEmpty
}
go(s.toList)
}