我在接受采访时被问到这个问题,我对最佳答案感到好奇。问题是这样的:给你一块装满字母的n x n板。游戏算法想要找到并列出该板上所有可能的单词,其中“单词”被定义为水平或垂直的至少3个字母的字符串。什么是最有效的方法呢?
这个问题中的“单词”不需要是字典中的真正单词。关键是尽可能快地找到所有可接受长度的字符串。我想不出别的什么,除了穿过棋盘上所有空间的野蛮力量方法,并找到所有字符串从该空间中的字母开始,这需要O(n ^ 3)时间。你们会怎么做?
PS。我看到这个问题被忽视了,因为人们认为没有更好的解决方案。然而,这是一个微软的面试问题,我的面试官明确告诉我,我的答案不是最佳的。
答案 0 :(得分:2)
以下是我分阶段解决问题的方法:
尝试在拼图中找到给定的单词:您可以使用DFS在拼图中查找单词。这将是O(n ^ 2),因为我们必须迭代每个行和列字符。
查找拼图中的所有给定单词:如果给出了x个单词,则可以对每个单词使用上述算法。复杂性为O(x * n ^ 2)。
如果有相同前缀的单词,那么我们将重复搜索前缀所做的工作。这可以通过为给定的单词构建Trie结构并将trie的DFS与拼图的DFS结合起来来避免。
这是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-2
或y = 0
,y = 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