记忆算法时间复杂度

mit*_*llc 13 algorithm recursion memoization time-complexity

我读了这篇文章,退出了一个很好的面试问题,作者想出了一个work break问题并提出了三个解决方案.高效使用一个memoization算法和笔者表示,其最坏情况下的时间复杂度是O(n^2)因为the key insight is that SegmentString is only called on suffixes of the original input string, and that there are only O(n) suffixes.

但是,我发现很难理解它为什么O(n^2).有人可以给我一个提示或证据吗?

Work Break Problem: 
    Given an input string and a dictionary of words,
    segment the input string into a space-separated
    sequence of dictionary words if possible. For
    example, if the input string is "applepie" and
    dictionary contains a standard set of English words,
    then we would return the string "apple pie" as output.
Run Code Online (Sandbox Code Playgroud)

退休大访谈问题的记忆算法

Map<String, String> memoized;

String SegmentString(String input, Set<String> dict) {
      if (dict.contains(input)) 
          return input;
      if (memoized.containsKey(input) {
          return memoized.get(input);
      }
      int len = input.length();
      for (int i = 1; i < len; i++) {
          String prefix = input.substring(0, i);
          if (dict.contains(prefix)) {
              String suffix = input.substring(i, len);
              String segSuffix = SegmentString(suffix, dict);
              if (segSuffix != null) {
                  return prefix + " " + segSuffix;
              }
          }
      }
      memoized.put(input, null);
      return null;
}
Run Code Online (Sandbox Code Playgroud)

shx*_*hx2 7

直觉

(很明显,最糟糕的情况是没有解决方案,所以我专注于那个)

由于在将值放入memoization-cache之前进行了递归调用,因此最后(最短)后缀将首先被缓存.这是因为首先使用长度为N的字符串调用该函数,然后使用长度为N-1的字符串调用自身,然后使用字符串len 0缓存并返回,然后是长度为1的字符串被缓存并返回,...,长度N被缓存并返回.

正如提示所暗示的那样,只有后缀才会被缓存,并且只有N个.这意味着,当顶级函数获得其第一次递归调用的结果(即,在长度为N-1的后缀上)时,缓存已经填充了N-1个后缀.

现在,假设已经缓存了N-1个后缀,for循环需要进行N-1个递归调用,每个调用O(1)(因为答案已经被缓存),总计为O(N).然而,(预)构建最后N-1的高速缓存需要O(N ^ 2)(下面解释),总计为O(N)+ O(N ^ 2)= O(N ^ 2).


通过数学归纳证明

这种解释可以很容易地转化为使用归纳的形式证明.以下是它的要点:

(f(N)是函数在长度为N的输入上完成所需的操作数)

归纳假设 - 存在一个常数cst f(N) < c*N^2.

基本情况是微不足道的-用于长度为1的字符串,你可以找到一个常数c,使得f(1) < c*N^2 = c

感应步骤

回顾订单的事情:

步骤1:函数首先在长度为N-1的后缀上调用自身,构建包含最后N-1个后缀的答案的缓存

步骤2:该函数然后多次调用自身O(N),每次取O(1)(由于此测试:if (memoized.containsKey(input)),以及在步骤1中已经填充了缓存的事实).

所以我们得到:

f(N+1) = f(N) + a*N <   (by the hypothesis)
       < c*N^2 + a*N <  (for c large enough)
       < c*N^2 + 2*c*N + c =
       = c*(N+1)^2
Run Code Online (Sandbox Code Playgroud)

因此我们得到了f(N+1) < c*(N+1)^2,完成了证明.

  • 非常感谢数学证明! (3认同)