【问题标题】:What is the complexity of the code to find word in a set of cubes在一组立方体中查找单词的代码的复杂性是多少
【发布时间】:2015-06-19 09:26:48
【问题描述】:

我已经解决了程序here。以前我认为复杂度是 O(n!) 其中 n 是单词中的字符。

但是今天我觉得不对。它应该是 (6)^(单词中的字符),其中 6 是立方体的边。

为了更通用,假设立方体有超过 6 个边,复杂度应该是 O(cubefaces ^ (characters in input word))

有人可以解释一下这种情况下的时间复杂度吗?

【问题讨论】:

  • 您是在询问您的解决方案的时间复杂度,还是最佳解决方案的时间复杂度?
  • @templatetypedef-OP 正在询问该链接中发布的他/她的解决方案的时间复杂度。
  • just FWIW 这可以在多项式时间内解决,O(n^2) 甚至更好地使用二分匹配。
  • @NiklasB。哦,伙计,这很漂亮。我想我从来没有意识到这种联系!
  • @NiklasB。你真的知道任何可以在 O(n^2) 或更好的时间内找到最大二分匹配的算法吗?

标签: algorithm time-complexity complexity-theory string-matching asymptotic-complexity


【解决方案1】:

如果有if (cubeMatrix.length != word.length()) return false;,并且立方体的每个边的字母都是唯一的(即立方体的两侧没有相同的字母),那么你的算法的时间复杂度是O( SN - S + 1S!) 当 N >= S, O(S N!) 当N S。这里S是立方体的边数,N是立方体的个数。

简而言之,您进行递归调用只有在与立方体侧面字母对应的单词中有一个未使用的字母,因此,在最坏的情况下,您进行递归调用的次数不超过单词字母未使用。并且未使用的单词字母的数量随着递归深度的增加而减少,最终这个数字变得小于立方体的边数。这就是为什么在最终的递归深度中,复杂性变成了阶乘。

更多细节

让我们介绍一下f(n),即您使用cubeNumber = n 调用findWordExists 的次数。我们还引入了 g(n),即 findWordExistscubeNumber = n 递归调用自身的次数(但现在使用 @ 987654327@ = n + 1).

f(0) = 1,因为您仅以非递归方式调用 findWordExists 一次。

f(n) = f(n - 1) g (n - 1) 当n > 0.

我们知道 g(n) = min { S, N - n },因为正如我已经指出的那样,findWordExists 被递归调用的次数不超过剩余字母的数量——if (frequency > 0) 检查负责这一点——以及剩余的字母等于剩余的立方体数,即N - n

现在我们可以计算findWordExists 总共被调用了多少次:
f(0) + f(1) + ... + f(N) =
= 1 + g(0) + g(0) g(1) + ... + g (0) g(1) ... g(N - 1) =
= 1 + S + S2 + ... + SN em> - S + SN - S (S - 1) + SN - S (S em> - 1) (S - 2) + ... + SN - S (S - 1) (S - 2) ... 1 =
= O(SN - SS !)。

但是每个findWordExists 调用(决赛除外)都会遍历每一边,所以我们需要将findWordExists 调用的数量乘以边的数量:S O em>(SN - SS!) = O (SN - S + 1S!) —这就是我们的时间复杂度。

更好的算法

实际上,您的问题是二分匹配问题,因此有比蛮力更有效的算法,例如Kuhn’s algorithm.

库恩算法的复杂度为O(N M),其中N为顶点数, M 是边数。在您的情况下,N 是立方体的数量,而 M 只是 N2,因此您的情况可能是 O(N3)。但是你还需要遍历所有立方体的所有边,所以如果立方体的边数大于N2,那么复杂度是O (N S),其中S是立方体的边数。

这是一个可能的实现:

import java.util.*;

public class CubeFind {
    private static boolean checkWord(char[][] cubes, String word) {
        if (word.length() != cubes.length) {
            return false;
        }
        List<Integer>[] cubeLetters = getCubeLetters(cubes, word);
        int countMatched = new BipartiteMatcher().match(cubeLetters, word.length());
        return countMatched == word.length();
    }

    private static List<Integer>[] getCubeLetters(char[][] cubes, String word) {
        int cubeCount = cubes.length;

        Set<Character>[] cubeLetterSet = new Set[cubeCount];
        for (int i = 0; i < cubeCount; i++) {
            cubeLetterSet[i] = new HashSet<>();
            for (int j = 0; j < cubes[i].length; j++) {
                cubeLetterSet[i].add(cubes[i][j]);
            }
        }
        List<Integer>[] cubeLetters = new List[cubeCount];
        for (int i = 0; i < cubeCount; i++) {
            cubeLetters[i] = new ArrayList<>();
            for (int j = 0; j < word.length(); j++) {
                if (cubeLetterSet[i].contains(word.charAt(j))) {
                    cubeLetters[i].add(j);
                }
            }
        }
        return cubeLetters;
    }

    public static void main(String[] args) {
        char[][] m = {{'e', 'a', 'l'} , {'x', 'h' , 'y'},  {'p' , 'q', 'l'}, {'l', 'h', 'e'}};
        System.out.println("Expected true,  Actual: " + CubeFind.checkWord(m, "hell"));
        System.out.println("Expected true,  Actual: " + CubeFind.checkWord(m, "help"));
        System.out.println("Expected false, Actual: " + CubeFind.checkWord(m, "hplp"));
        System.out.println("Expected false, Actual: " + CubeFind.checkWord(m, "hplp"));
        System.out.println("Expected false, Actual: " + CubeFind.checkWord(m, "helll"));
        System.out.println("Expected false, Actual: " + CubeFind.checkWord(m, "hel"));
    }
}

class BipartiteMatcher {
    private List<Integer>[] cubeLetters;
    private int[] letterCube;
    private boolean[] used;

    int match(List<Integer>[] cubeLetters, int letterCount) {
        this.cubeLetters = cubeLetters;
        int cubeCount = cubeLetters.length;
        int countMatched = 0;

        letterCube = new int[letterCount];
        Arrays.fill(letterCube, -1);

        used = new boolean[cubeCount];
        for (int u = 0; u < cubeCount; u++) {
            if (dfs(u)) {
                countMatched++;

                Arrays.fill(used, false);
            }
        }
        return countMatched;
    }

    boolean dfs(int u) {
        if (used[u]) {
            return false;
        }
        used[u] = true;

        for (int i = 0; i < cubeLetters[u].size(); i++) {
            int v = cubeLetters[u].get(i);

            if (letterCube[v] == -1 || dfs(letterCube[v])) {
                letterCube[v] = u;
                return true;
            }
        }
        return false;
    }
}

【讨论】:

  • @shekharsuman,请注意,n 对应于findWordExistscubeNumber 参数。因此,f(0) 是对findWordExists 的调用次数,cubeNumber = 0。它确实只被调用一次(来自checkWord):findWordExists(cubeMatrix, charFreq, 0);。所以,这不是一个错误。
  • @dened- 是的,现在这听起来确实是一个精确(更严格)的评估,我在回答中将其作为上限。好办法。也投了赞成票。
【解决方案2】:
for (int i = 0; i <  cubeMatrix[cubeNumber].length; i++) 

这将说明给定立方体(或者更确切地说是立方体的面)中的字符数。

另外,在这个循环中,你有一个

if (frequency > 0) {
   charFreq.put(cubeMatrix[cubeNumber][i], frequency - 1);
   if (findWordExists(cubeMatrix, charFreq, cubeNumber + 1)) {
      return true;
          ..
          // and so on.

这将导致递归调用,从而调用 cubeNumber+1,然后是 cubeNumber+1+1,..,等等。

而且,最后当这种情况发生时

if (cubeNumber == cubeMatrix.length) {
        for (Integer frequency : charFreq.values()) {
            if (frequency > 0) return false;
        }
        return true;
    }

会遇到,for 循环不会再被执行。

假设,不。立方体的个数 = n,每个立方体中存储的字符 = 每个立方体的广义面(由 OP 创造)= f。

最坏情况分析:-

从第 0 个立方体开始到第 (n-1) 个立方体,for 循环将迭代 cubeMatrix[cubeNumber].length 次,等于每个立方体中存储的字符数 = f 次。

并且,在for循环的每一次迭代中,对于cubeNumber 0的情况,实际递归调用将是n-1次,直到到达最后一个cube number。

因此,对于 cubeArray(f 个字符)中的每个字符条目,我们必须调用所有可用的立方体(根据我们的假设总共 n 个)。

因此,代码检查查找单词的总次数 = f ^ n。

用您的话来说,f = cubefaces = 广义立方体面上可能出现的字符数;

和 , n = 可用于您的测试的立方体总数。

它确实取决于减少的字符频率 当单词长度不匹配时,根据单词中的字符 立方体的数量。在这种情况下,结果将是错误的。

但是,在字长等于立方体数的情况下, 在最坏的情况下,输出将与字长无关。

严格来说,它还取决于单词的字符数(因为与频率相比会减少计算中的几种情况),但不幸的是,在最坏的情况下,它不取决于字符数在单词中因为我们将检查所有可用立方体中的所有字符条目来创建单词。

【讨论】:

  • 但是我们可以将单词的长度作为时间复杂度函数的参数,这样我们就能推导出更准确的渐近复杂度。当字长等于立方体的数量时,请参阅my analysis。而后者是最重要的情况,因为对于其他情况,我们可以简单地return false,而不需要任何复杂的计算。
  • @dened- 这是我在回答中提到的。请参阅倒数第二段和第三段中的引用文本。
  • 但是你没有提到它是如何改变渐近复杂度的。在这些情况下不是 f^n ,不是吗?
  • @dened- 好吧,首先我到处都在谈论最坏情况的复杂性,因为无法准确判断单词字符的依赖性;下一个-当您有条件语句时,我不知道如何计算案例的复杂性,这意味着取决于 n OR f。当您没有 fn(之前给出/在编译时给出)时,我不能假设任何事情并评估答案。我将不得不谈论仅最坏情况的复杂性
  • 实际上,在进行算法分析时,如果考虑条件语句,通常可以使其更准确。比如我分析成功了,这里也只讲最坏情况分析。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-01-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-03-03
  • 2011-02-16
相关资源
最近更新 更多