返回

征服 LeetCode 79:单词搜索的巧妙解法

见解分享

征服 LeetCode 79:破解单词搜索谜团

简介

LeetCode 79:单词搜索是算法爱好者中颇具挑战的一道题,考验着解决复杂字符串匹配问题的技能。作为算法之旅中的一大障碍,这道题让初学者望而生畏。但是,掌握回溯算法的精髓,你将能轻松解开这个谜团。

回溯算法:分而治之的利器

单词搜索本质上是一个匹配问题。在一个二维网格中,你需要找出给定的目标单词是否存在。暴力枚举法虽然简单,但效率低下,尤其是在网格庞大的情况下。

回溯算法提供了一种更优雅的解决方案。它通过分而治之的方法,逐层深入网格,探索不同的搜索路径。当发现当前路径不可行,算法回溯到上一步,尝试不同的路径。

JavaScript 实现:一步一步征服

用 JavaScript 实现单词搜索算法非常简单。我们定义一个函数 searchWord,接受两个参数:board(网格)和 word(目标单词)。

function searchWord(board, word) {
  // ... 算法实现
}

递归回溯:探索所有可能性

searchWord 函数通过递归调用实现回溯。在每层,算法检查当前单元格是否与目标单词的第一个字母匹配。如果是,继续递归探索相邻单元格,检查下一个字母。当发现路径不可行,算法回溯,尝试不同路径。

递归终止条件:成功或失败

递归终止有两种情况:

  1. 找到单词: 遍历完目标单词所有字母,且全部匹配时,成功找到单词,返回 true
  2. 遍历所有路径: 遍历完网格所有单元格,未找到单词时,返回 false

代码实现:清晰简洁

function searchWord(board, word) {
  const m = board.length;
  const n = board[0].length;

  for (let i = 0; i < m; i++) {
    for (let j = 0; j < n; j++) {
      if (board[i][j] === word[0]) {
        if (dfs(i, j, 0)) {
          return true;
        }
      }
    }
  }

  return false;
}

function dfs(row, col, index) {
  if (index === word.length - 1) {
    return true;
  }

  if (row < 0 || row >= m || col < 0 || col >= n || board[row][col] !== word[index]) {
    return false;
  }

  // 标记当前单元格已访问
  const temp = board[row][col];
  board[row][col] = '*';

  const found =
    dfs(row + 1, col, index + 1) ||
    dfs(row - 1, col, index + 1) ||
    dfs(row, col + 1, index + 1) ||
    dfs(row, col - 1, index + 1);

  // 恢复当前单元格的值
  board[row][col] = temp;

  return found;
}

复杂度分析:高效解决大规模问题

单词搜索算法的时间复杂度为 O(mn4^wordLength),其中 m 和 n 是网格的行数和列数,wordLength 是目标单词的长度。这种复杂度是因为算法对每个网格单元格进行四次递归调用(向上、下、左、右)。

常见问题解答

  1. 回溯算法的优势是什么?
    回溯算法允许探索所有可能的搜索路径,并回溯到以前的状态,有效解决组合问题。

  2. 为什么暴力枚举法效率低下?
    暴力枚举法逐个检查所有可能的路径,当网格很大时,搜索空间变得庞大,导致效率低下。

  3. 回溯算法如何避免重复探索?
    算法通过标记访问过的单元格来避免重复探索,确保只探索未访问的路径。

  4. 算法如何处理目标单词不存在的情况?
    算法遍历完网格所有单元格,如果未找到匹配的单词,则返回 false

  5. 回溯算法的其他应用是什么?
    回溯算法广泛用于解决组合问题,如八皇后问题、数独问题和排列组合问题。