Leetcode. 212 单词搜索II

题目信息

LeetoCode地址: 力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

题目理解

该题目也是匹配字符串,但是高级一点。首先,要找到的字符串不再是单一一个,而是一个列表words,最大有3 *10^4个。其次,我们要在一个二维字符网格board上找寻,不再是若干个单词,即一个一维的字符数组。有点像需要棋盘上从任何一个位置开始走出一条蛇形路径,该路径刚好匹配字符串word。而每一步该怎么走呢?有四个方向,当然就有四个走法,到下一个位置后,同样有四种走法。那终止条件是什么?撞墙或者是四个方向的位置都已经用过了!所以我们每走一步,都要标记该位置以免走重复。而在该位置探索完返回时,要将标记抹除,就像一条蛇沿原路返回回退一样。简单地说,就是DFS+回溯。

写法1

我在刚开始思考这道题时,希望能够将board上的每一种可能路径都包含在Trie树中。在dfs过程中,由于words中每个字符串长度不超过10,所以dfs的深度也可以控制在10层以内。

其次,对于products每个字符串首字母没出现过的字符,都可以在遍历board每一个字符时过滤掉,无需进行dfs。

在构建好trie树后,再依次对words中的每一个元素进行遍历搜索,命中的元素加入到最终结果集中。

在board 为 m*n 大小,words 长度为p,每个字符串平均长度为q的情况下

时间复杂度: O(max(m*n*4^10, p*q)),前者是dfs构建Trie树操作, 后者是搜索words字符串操作

额外空间复杂度: O(max(m*n, p*q)), 前者是保存当前board字符占用的临时树组空间开销,后者是保存Trie树的空间开销。

java 复制代码
Trie root;
    int h;
    int w;
    char[][] board;
    Set<String> searched;
    public List<String> findWords(char[][] board, String[] words) {
        h = board.length;
        w = board[0].length;
        searched = new HashSet<>();
        this.board = board;
        root = new Trie();
        int[] firstCharArray = new int[26];
        for (String word : words) {
            firstCharArray[word.charAt(0) - 'a'] = 1;
        }
        for (int i = 0; i < h; i++) {
            for (int j = 0; j < w; j++) {
                if (firstCharArray[board[i][j] - 'a'] == 0) {
                    continue;
                }
                int[][] usedMap = new int[h][w];
                StringBuilder sb = new StringBuilder(board[i][j]);
                dfs(sb, i, j, usedMap);
            }
        }
        List<String> result = new ArrayList<>();
        for (String word : words) {
            Trie current = root;
            for (char c : word.toCharArray()) {
                if (current.nextList[c-'a'] != null) {
                    current = current.nextList[c-'a'];
                } else {
                    current = null;
                    break;
                }
            }
            if (current != null && current.end) {
                result.add(word);
            }
        }
        return result;
    }

    private void dfs(StringBuilder sb, int i, int j, int[][] usedMap) {
        if (sb.length() >= 10 || i < 0 || i >= h || j < 0 || j>=w || usedMap[i][j] == 1) {
            String s = sb.toString();
            if (!searched.contains(s)) {
                searched.add(s);
                addWord(s);
            }
            return;
        }
        usedMap[i][j] = 1;
        sb.append(board[i][j]);
        boolean hasWay = false;
        if(i - 1 >=0) {
            hasWay = true;
            dfs(sb, i-1, j, usedMap);
        }
        if(i + 1 < h) {
            hasWay = true;
            dfs(sb, i+1, j, usedMap);
        }
        if(j - 1 >=0) {
            hasWay = true;
            dfs(sb, i, j-1, usedMap);
        }
        if(j + 1 < w) {
            hasWay = true;
            dfs(sb, i, j+1, usedMap);
        }
        if (!hasWay && !searched.contains(sb.toString())) {
            searched.add(sb.toString());
            addWord(sb.toString());
        }
        usedMap[i][j] = 0;
        sb.delete(sb.length()-1, sb.length());
    }

    public void addWord(String word) {
        Trie current = root;
        for (char c : word.toCharArray()) {
            if (current.nextList[c - 'a'] != null) {
                current = current.nextList[c - 'a'];
            } else {
                Trie next = new Trie();
                next.end = true;
                current.nextList[c - 'a'] = next;
                current = next;
            }
        }
        current.value = word;
        current.end = true;
    }

    class Trie {
        boolean end;

        String value;
        Trie[] nextList;
        public Trie() {
            this.end = false;
            this.nextList = new Trie[26];
        }
    }

写法2

除了对board进行Trie树构建,还有一种方法是对words树组进行Trie树构建,并在此树上通过对board进行dfs+回溯搜索的做法。这种做法时间复杂度上更有优势,因为在dfs时能够更加快速的收敛。写法1可能会遍历很多无效的字符串。

必须强调的是,由于回溯会将占用的字符复原,我们其实可以在board上记录哪些字符被占用,这节省了一部分空间。

而且Trie树节点也无需用end标识该节点是否是字符串终止节点,可以直接拿value的值替代,如果value为空,则没有字符串在该字符上终止,否则有。

java 复制代码
Trie root;
    int h;
    int w;
    char[][] board;
    List<String> result;
    int[][] directions;
    public List<String> findWords(char[][] board, String[] words) {
        h = board.length;
        w = board[0].length;
        this.board = board;
        root = new Trie();
        directions = new int[][]{{1, 0}, {-2, 0}, {1, 1}, {0, -2}};
        for (String word : words) {
            addWord(word);
        }
        result = new ArrayList<>();
        for (int i = 0; i < h; i++) {
            for (int j = 0; j < w; j++) {

                dfs(i,j,root);
            }
        }
        return result;
    }

    private void dfs(int i, int j, Trie root) {
        char temp = board[i][j];
        if (board[i][j] == '$' || root.nextList[temp - 'a'] == null) {
            return;
        }

        Trie trie = root.nextList[temp - 'a'];
        if (trie.value != null) {
            result.add(trie.value);
            trie.value = null;
        }
        board[i][j] = '$';
        for (int[] direction : directions) {
            i += direction[0];
            j += direction[1];
            if (i < 0 || i >= h || j < 0 || j>=w) {
                continue;
            }
            dfs(i, j, trie);
        }
        board[i][j+1] = temp;
    }


    public void addWord(String word) {
        Trie current = root;
        for (char c : word.toCharArray()) {
            if (current.nextList[c - 'a'] == null) {
                current.nextList[c - 'a'] = new Trie();
            }
            current = current.nextList[c - 'a'];
        }
        current.value = word;
    }

    class Trie {
        boolean end;

        String value;
        Trie[] nextList;
        public Trie() {
            this.end = false;
            this.nextList = new Trie[26];
        }
    }

在board 为 m*n 大小,words 长度为p,每个字符串平均长度为q的情况下:

时间复杂度: O(max(m*n*3^10), p*q),前者是dfs构建Trie树操作, 后者是搜索words字符串操作

额外空间复杂度: O(p*q), 保存Trie树的空间开销。

相关推荐
程序员-King.5 小时前
day158—回溯—全排列(LeetCode-46)
算法·leetcode·深度优先·回溯·递归
月挽清风6 小时前
代码随想录第七天:
数据结构·c++·算法
小O的算法实验室6 小时前
2026年AEI SCI1区TOP,基于改进 IRRT*-D* 算法的森林火灾救援场景下直升机轨迹规划,深度解析+性能实测
算法·论文复现·智能算法·智能算法改进
小郭团队7 小时前
2_1_七段式SVPWM (经典算法)算法理论与 MATLAB 实现详解
嵌入式硬件·算法·硬件架构·arm·dsp开发
充值修改昵称7 小时前
数据结构基础:从二叉树到多叉树数据结构进阶
数据结构·python·算法
Deepoch7 小时前
Deepoc数学大模型:发动机行业的算法引擎
人工智能·算法·机器人·发动机·deepoc·发动机行业
浅念-8 小时前
C语言小知识——指针(3)
c语言·开发语言·c++·经验分享·笔记·学习·算法
Hcoco_me8 小时前
大模型面试题84:是否了解 OpenAI 提出的Clip,它和SigLip有什么区别?为什么SigLip效果更好?
人工智能·算法·机器学习·chatgpt·机器人
BHXDML8 小时前
第九章:EM 算法
人工智能·算法·机器学习
1314lay_10079 小时前
C# 点击一次api,限流中间件但是X-Rate-Limit-Remaining剩余数量减少2
visualstudio·c#