【问题标题】:Why does my first solution run slower than my second solution for this Leetcode problem (Word Search)?为什么我的第一个解决方案比我的 Leetcode 问题(单词搜索)的第二个解决方案运行得慢?
【发布时间】:2020-02-22 22:53:34
【问题描述】:

这是给不熟悉这个问题的人的问题陈述:

给定一个 2D 棋盘和一个单词,找出该单词是否存在于网格中。 单词可以由顺序相邻单元格的字母构成,其中“相邻”单元格是水平或垂直相邻的单元格。同一个字母单元格不能多次使用。

我选择用 Java 来解决这个问题。在这两种解决方案中,主查询都需要一个函数exist(char[][] board, String word),我使用一个辅助函数DFS() 作为算法的关键(标准回溯)。

这两种解决方案的 exist() 函数是:

public boolean exist(char[][] board, String word) {
        if (word == null || word.length() == 0) return true;
        if (board == null || board.length == 0 || board[0] == null) return false;
        if (word.length() > board.length * board[0].length) return false;
        char[] w = word.toCharArray();
        char a = w[0];
        for (int i = 0; i < board.length; i++) {
            for (int j = 0; j < board[0].length; j++) {
                if (board[i][j] == a && DFS(board,w,i,j,0)) return true;
            }
        }
        return false;
    }

现在,这是第一种方法的函数DFS()。 (请注意,该程序假定一个单词永远不会包含 @ 字符,但可以通过使用一些随机 Unicode 字符甚至布尔数组来使单词变得更强大,以确保总保证):

解决方案 1

public boolean DFS(char[][] board, char[] word, int i, int j, int idx) {
        if (idx == word.length) return true;
        if (i < 0 || i >= board.length || j < 0 || j >= board[0].length ||
           board[i][j] != word[idx]) return false;
        board[i][j] = '@';
        int[][] dirs = {{-1,0},{1,0},{0,-1},{0,1}};
        for (int[] d : dirs) {
            if (DFS(board,word,i+d[0],j+d[1],idx+1)) return true;
        }
        board[i][j] = word[idx];
        return false;
    }

这是第二个解决方案:

解决方案 2

public boolean DFS(char[][] board, char[] word, int i, int j, int idx) {
        if (idx == word.length) return true;
        if (i < 0 || i >= board.length || j < 0 || j >= board[0].length ||
           board[i][j] != word[idx]) return false;
        board[i][j] = '@';
        boolean found = false;
        found = DFS(board,word,i-1,j,idx+1) || DFS(board,word,i+1,j,idx+1) ||
            DFS(board,word,i,j-1,idx+1) || DFS(board,word,i,j+1,idx+1);
        board[i][j] = word[idx];
        return found;
    }

现在,据我了解,由于 Java 短路,如果任何子问题返回 true,DFS() 的两个版本都应该停止探索其他路径。事实上,我可以评估的两个版本之间存在的唯一操作差异是,如果找到解决方案,第一个版本不会打扰重置板(恢复到其原始形式,没有 @ 字符指示访问的位置),而第二个版本可以。

所以据我了解,如果有的话,第二种解决方案应该比第一种解决方案。但很明显,我错了:解决方案一在 8 毫秒内运行,但解决方案二在 3 毫秒内运行。

有人知道我在这里可能缺少什么吗?

谢谢

【问题讨论】:

  • 1.: 你做过理论上的运行时分析吗? 2.: 为什么在调用DFS 时不使用空格? 3.:查看DFS的来电数量。如果数量相同,则查找是否有不必要的对象创建。
  • 我想知道int[][] dirs = {{-1,0},{1,0},{0,-1},{0,1}}; 分配是否是问题所在。 Java 应该足够聪明,可以在堆栈上分配它,但谁知道呢。尝试将其移出函数到类范围。让我知道事情的后续。不过,即使在这里,我也不建议过分重视 LC 的运行时间——最好编写自己的基准测试。两个 DFS 例程都应该短路,但解决方案 2 很好地展开,所以没有循环或if
  • @akuzminykh 1. 你读过这篇文章吗?这些算法实际上是等效的。 2.因为我认为这看起来更好。 3. 同样,算法实际上是等效的,人们不会认为在最多 N^2 个堆栈帧中分配 8 个整数的数组会导致速度降低 2 倍以上。
  • @ggorlen 这是一个很好的可能性,我很高兴你提到了它,因为这个数组确实应该被声明为一个类成员,因为它可以持续重用。有趣的是,进行此更改只会将解决方案 1 的运行时间从 8 毫秒减少到 6 毫秒,因此解决方案 2 的运行速度仍然是原来的两倍
  • 如前所述,soln 2 具有展开的循环,不需要索引到数组来获取数据。我将其归类为微优化领域。如果您编写自己的真正推动代码的基准测试,我怀疑您会看到重大差异,如果您这样做,您可以对其进行分析以查看问题所在。运行代码 2-6 毫秒并不能告诉你太多。

标签: java algorithm graph runtime depth-first-search


【解决方案1】:

jit 编译器比你聪明。 第二个版本具有更简单的内联代码(4 个版本),而不是 1 个被反编译和重新编译几次。我的猜测。

【讨论】:

  • 嗯,这是一个有趣的观点。我对 JIT 编译过程不太熟悉,会更多地研究它。谢谢!
  • 这将是一个乏味的过程。我所能做的就是在 linux vm 中运行一个特殊的热点插件,并让它反汇编 jit 编译的代码,然后“尝试”检测它提供的大量汇编代码中的差异。当时我的问题中有一些指针:stackoverflow.com/questions/49754232/…
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-05-20
  • 2010-11-14
  • 1970-01-01
  • 2022-01-13
  • 2021-10-26
  • 2021-06-23
相关资源
最近更新 更多