212. 单词搜索 II

1. 题号和题目名称

  1. 单词搜索 II

2. 题目叙述

给定一个 m x n 二维字符网格 board 和一个单词(字符串)列表 words,找出所有同时在二维网格和字典中出现的单词。
单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母在一个单词中不允许被重复使用。

3. 模式识别

本题可以结合字典树(Trie 树)和回溯算法来解决。字典树用于高效地存储和查找单词列表,回溯算法用于在二维网格中搜索可能的单词路径。

4. 考点分析

  • 字典树的构建与应用:需要构建字典树来存储单词列表,以便在网格搜索过程中快速判断是否匹配到单词。
  • 回溯算法:在二维网格中进行深度优先搜索(DFS),尝试所有可能的路径,同时要注意避免重复使用单元格。
  • 剪枝优化:在搜索过程中,及时剪掉不可能产生有效单词的分支,提高搜索效率。

5. 所有解法

解法一:朴素回溯
  • 思路:对于每个单词,在二维网格中进行回溯搜索,检查是否能找到该单词。时间复杂度较高,因为对于每个单词都要进行一次完整的网格搜索。
解法二:字典树 + 回溯
  • 思路:首先构建字典树来存储所有单词,然后在二维网格的每个单元格上启动回溯搜索,利用字典树来指导搜索过程,一旦发现当前路径无法构成有效单词,就停止搜索,避免不必要的计算。这是本题的最优解法。

6. 最优解法(字典树 + 回溯)的 C 语言代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>

#define ALPHABET_SIZE 26

// 定义字典树节点结构体
typedef struct TrieNode {
    struct TrieNode *children[ALPHABET_SIZE];  // 子节点数组,每个元素对应一个字母
    bool isEndOfWord;  // 标记该节点是否为一个单词的结尾
    char *word;  // 存储完整的单词
} TrieNode;

// 创建一个新的字典树节点
TrieNode* createNode() {
    TrieNode* node = (TrieNode*)malloc(sizeof(TrieNode));
    for (int i = 0; i < ALPHABET_SIZE; i++) {
        node->children[i] = NULL;  // 初始化子节点数组为 NULL
    }
    node->isEndOfWord = false;  // 初始标记不是单词结尾
    node->word = NULL;  // 初始时不存储单词
    return node;
}

// 向字典树中添加单词
void insert(TrieNode* root, char *word) {
    TrieNode* current = root;
    for (int i = 0; word[i] != '\0'; i++) {
        int index = word[i] - 'a';  // 计算字符对应的索引
        if (current->children[index] == NULL) {
            current->children[index] = createNode();  // 如果子节点不存在,创建新节点
        }
        current = current->children[index];  // 移动到下一个节点
    }
    current->isEndOfWord = true;  // 标记单词结尾
    current->word = strdup(word);  // 存储完整的单词
}

// 回溯搜索函数
void backtrack(char** board, int row, int col, int m, int n, TrieNode* node, char ***result, int *resultSize) {
    if (row < 0 || row >= m || col < 0 || col >= n || board[row][col] == '#') {
        return;  // 越界或已访问过,返回
    }
    char originalChar = board[row][col];
    int index = originalChar - 'a';
    TrieNode* child = node->children[index];
    if (child == NULL) {
        return;  // 该路径无法构成有效单词,返回
    }
    if (child->isEndOfWord) {
        // 找到一个单词
        (*result)[(*resultSize)++] = child->word;
        child->isEndOfWord = false;  // 避免重复添加
    }
    board[row][col] = '#';  // 标记为已访问
    // 向四个方向进行搜索
    backtrack(board, row - 1, col, m, n, child, result, resultSize);  // 上
    backtrack(board, row + 1, col, m, n, child, result, resultSize);  // 下
    backtrack(board, row, col - 1, m, n, child, result, resultSize);  // 左
    backtrack(board, row, col + 1, m, n, child, result, resultSize);  // 右
    board[row][col] = originalChar;  // 恢复原始字符
}

// 主函数,查找所有匹配的单词
char **findWords(char** board, int boardSize, int* boardColSize, char **words, int wordsSize, int* returnSize) {
    TrieNode* root = createNode();
    // 构建字典树
    for (int i = 0; i < wordsSize; i++) {
        insert(root, words[i]);
    }
    char **result = (char**)malloc(wordsSize * sizeof(char*));
    *returnSize = 0;
    // 在二维网格的每个单元格上启动回溯搜索
    for (int i = 0; i < boardSize; i++) {
        for (int j = 0; j < boardColSize[i]; j++) {
            backtrack(board, i, j, boardSize, boardColSize[i], root, &result, returnSize);
        }
    }
    return result;
}

// 释放字典树的内存
void freeTrie(TrieNode* node) {
    if (node == NULL) {
        return;
    }
    for (int i = 0; i < ALPHABET_SIZE; i++) {
        freeTrie(node->children[i]);  // 递归释放子节点
    }
    if (node->word != NULL) {
        free(node->word);  // 释放存储的单词
    }
    free(node);  // 释放当前节点
}

// 以下是测试代码示例
int main() {
    char *board[] = {"oaan", "etae", "ihkr", "iflv"};
    int boardSize = 4;
    int boardColSize[] = {4, 4, 4, 4};
    char *words[] = {"oath", "pea", "eat", "rain"};
    int wordsSize = 4;
    int returnSize;
    char **result = findWords(board, boardSize, boardColSize, words, wordsSize, &returnSize);
    for (int i = 0; i < returnSize; i++) {
        printf("%s\n", result[i]);
    }
    // 释放结果数组的内存
    for (int i = 0; i < returnSize; i++) {
        free(result[i]);
    }
    free(result);
    return 0;
}

7. 复杂度分析

  • 时间复杂度 O ( m ∗ n ∗ 3 L ) O(m * n * 3^L) O(mn3L),其中 m m m n n n 分别是二维网格的行数和列数, L L L 是单词的最大长度。在最坏情况下,对于每个单元格,都有 3 个方向可以继续搜索(因为不能回到上一个单元格),并且需要对每个单元格启动搜索。
  • 空间复杂度 O ( k ∗ L ) O(k * L) O(kL),其中 k k k 是单词的数量, L L L 是单词的最大长度。主要用于存储字典树的节点。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

请向我看齐

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值