列出拼图中所有单词的最快算法

时间:2012-10-13 17:13:33

标签: algorithm search

我在接受采访时被问到这个问题,我对最佳答案感到好奇。问题是这样的:给你一块装满字母的n x n板。游戏算法想要找到并列出该板上所有可能的单词,其中“单词”被定义为水平或垂直的至少3个字母的字符串。什么是最有效的方法呢?

这个问题中的“单词”不需要是字典中的真正单词。关键是尽可能快地找到所有可接受长度的字符串。我想不出别的什么,除了穿过棋盘上所有空间的野蛮力量方法,并找到所有字符串从该空间中的字母开始,这需要O(n ^ 3)时间。你们会怎么做?

PS。我看到这个问题被忽视了,因为人们认为没有更好的解决方案。然而,这是一个微软的面试问题,我的面试官明确告诉我,我的答案不是最佳的。

4 个答案:

答案 0 :(得分:2)

以下是我分阶段解决问题的方法:

  1. 尝试在拼图中找到给定的单词:您可以使用DFS在拼图中查找单词。这将是O(n ^ 2),因为我们必须迭代每个行和列字符。

  2. 查找拼图中的所有给定单词:如果给出了x个单词,则可以对每个单词使用上述算法。复杂性为O(x * n ^ 2)。

  3. 如果有相同前缀的单词,那么我们将重复搜索前缀所做的工作。这可以通过为给定的单词构建Trie结构并将trie的DFS与拼图的DFS结合起来来避免。

  4. 这是C ++第一步的粗略实现:

    bool FindWordInPuzzle(int i, int j, char nextChar, int nextCharId, string word, int m, int n, bool **mark, char **maze)
    {
        int move[8][2] = { 0, -1, -1, -1, -1, 0, -1, 1, 0, 1, 1, 1, 1, 0, 1, -1 };
        mark[i][j] = 1;
    
        for (int r = 0; r < 8; r++) {
            int g = i + move[r][0];
            int h = j + move[r][1];
    
            if (g > 0 && g < m + 2 && h > 0 && h < m + 2 && mark[g][h] == 0 && maze[g][h] == nextChar) {
                nextCharId++;
    
                if (nextCharId >= word.length()) {
                    return true;
                }
    
                if (FindWordInPuzzle(g, h, word[nextCharId], nextCharId, word, m, n, mark, maze)) {
                    return true;
                }
            }
        }
    
        return false;
    }
    
    bool FindWord(char **maze, bool **mark, int m, int n, string word) {
        char currentChar = word[0];
        int currentCharId = 0;
        for (int row = 1; row < m + 2; row++) {
            for (int col = 1; col < n+2; col++) {
                if (maze[row][col] == currentChar && mark[row][col] == 0) {
                    currentCharId++;
                    if (currentCharId >= word.length()) {
                        return true;
                    }
    
                    if (FindWordInPuzzle(row, col, word[currentCharId], currentCharId, word, m, n, mark, maze)) {
                        return true;
                    }
                }
    
                currentCharId = 0;
                currentChar = word[0];
            }
        }
    
        return false;
    }
    
    int main() {
        string word;
        int m, n;
    
        cin >> word;
        if (word.length() <= 0) return 0;
    
        cin >> m >> n;
        char** maze;
        bool **mark;
    
        // declare arrays
        maze = new char*[m + 2];
        mark = new bool*[m + 2];
        for (int i = 0; i < m + 2; i++) {
            maze[i] = new char[n + 2];
            mark[i] = new bool[n + 2];
        }
    
        // boundaries
        for (int i = 0; i < m + 2; i++) {
            maze[0][i] = ' ';
                maze[i][0] = ' ';
            maze[0][m + 1] = ' ';
            maze[i][m + 1] = ' ';
    
            mark[0][i] = 1;
            mark[i][0] = 1;
            mark[0][m + 1] = 1;
                mark[i][m + 1] = 1;
    
        }
    
        // get values
        for (int i = 1; i < m + 1; i++) {
            for (int j = 1; j < n + 1; j++) {
                cin >> maze[i][j];
                mark[i][j] = 0;
            }
        }
    
        bool val =  FindWord(maze, mark, m, n, word);
        cout << val;
        cin >> word;
    
        return 0;
    }
    

答案 1 :(得分:1)

m(x) = max {0, x}。如果我们使用基于0的索引,则有

s(x,y) = m(x-1) + m(n-x-2) + m(y-1) + m(n-y-2)

从位置(x,y)开始的单词。水平向左,以0, 1, ..., y-2列结尾的那些,以及以x+2, x+3, ..., n-1列结尾的那些。类似于垂直词。

因此,在每个位置都会在2*(n-3)2*(n-2)个字词(包括)之间开始。

更准确地说,在(x,y)位置,当且仅当n-2y = 0y = n-1字时,才开始n-3个水平字。这使得每行2*(n-2) + (n-2)*(n-3) = (n-1)*(n-2)个水平字。每列的垂直字数是相同的,所以总共有

2*n*(n-1)*(n-2)

不一定是网格中不同的单词。假设字母不是太小,重复的比例平均不大,因此复杂度算法不可能低于O(n³)

如果不考虑重复,那就是它,并且只有遍历数组的低级变体。

如果要删除重复项,并且目标是尽可能有效地列出所有不同的单词,那么问题是数据结构允许尽可能有效地删除重复项。我无法回答这个问题,但我认为这样做会非常有效。

答案 2 :(得分:0)

这里有两个问题 - 一个:天真的解决方案暴力不是O(n^3),而是O(n^4)

假设您将每个子字符串复制到列表中的新条目。你有O(n^3)个字。但是,其中每个都是O(n)(平均)本身,因此将所有这些子字符串复制到列表实际上是O(n^4)

二:
一个更有效的解决方案是维护trie数据结构,并使用DFS来填充它,如遍历矩阵中的每个索引(向右和向下)。

这将导致O(n^3)解决方案用O(n^3)字填充特里。

答案 3 :(得分:0)

你为什么说O(n^3)?董事会的平方和正方形为O(n^2)

代码如下:

for(int col=0; col<n-2; ++col) {
    for(int row=0; row<n-2; ++row) {
        // for given (row,col)
        // yield word to right
        // yield word down
        // yield word down-right
    }
}

这是O(3*n^2)

如果你想采取逆转,那么它将是O(6*n^2)

C#中的真实代码

class Program
{
    private static Random rnd = new Random();

    static void Main(string[] args)
    {

        string alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
        char[,] letters = new char[5, 5];
        int counter = 0;

        // filling and printing
        for (int i = 0; i < letters.GetLength(0); ++i)
        {
            for (int j = 0; j < letters.GetLength(1); ++j)
            {
                letters[i, j] = alphabet[rnd.Next(alphabet.Length)];
                Console.Write(letters[i,j]);
            }
            Console.WriteLine("");
        }



        // generating "words"

        for (int i = 0; i < letters.GetLength(0)-2; ++i)
        {
            for (int j = 0; j < letters.GetLength(1)-2; ++j)
            {
                // horizontally
                Console.Write(counter.ToString() + ": ");

                Console.Write(letters[i, j]);
                Console.Write(letters[i, j+1]);
                Console.Write(letters[i, j+2]);

                Console.WriteLine("");
                counter++;

                // vertically
                Console.Write(counter.ToString() + ": ");

                Console.Write(letters[i, j]);
                Console.Write(letters[i+1, j]);
                Console.Write(letters[i+2, j]);

                Console.WriteLine("");
                counter++;

                // diagonally
                Console.Write(counter.ToString() + ": ");

                Console.Write(letters[i , j]);
                Console.Write(letters[i + 1, j+1]);
                Console.Write(letters[i + 2, j+2]);

                Console.WriteLine("");
                counter++;
            }
        }



    }
 }

<强>输出

LWIDM
OWWGR
APVOM
GKECL
TXCPD
0: LWI
1: LOA
2: LWV
3: WID
4: WWP
5: WWO
6: IDM
7: IWV
8: IGM
9: OWW
10: OAG
11: OPE
12: WWG
13: WPK
14: WVC
15: WGR
16: WVE
17: WOL
18: APV
19: AGT
20: AKC
21: PVO
22: PKX
23: PEP
24: VOM
25: VEC
26: VCD

结果数为27 = 3 *(5-2)^ 2