当前位置: 首页 > news >正文

LeetCode 425 - 单词方块

在这里插入图片描述
在这里插入图片描述

文章目录

    • 摘要
    • 描述
    • 题解答案
    • 题解代码分析
    • 代码模块分析
      • 1. Trie 存储所有前缀匹配的单词
      • 2. 回溯构建单词方块
      • 3. 剪枝
    • 示例测试及结果
    • 时间复杂度
    • 空间复杂度
    • 总结

摘要

这道题《LeetCode 425. 单词方块》比一般的字符串题更“烧脑”一些,因为它不仅要求词语之间互相匹配,还要求每一行和每一列都构成有效的单词。
如果说 Trie(前缀树)在 LeetCode 里是“偶尔能用”,那么这题可以说是让 Trie 发挥最大价值的场景之一:我们需要频繁查询“某个前缀开头的所有单词”。
整个题的核心其实就一句话:用前缀树加回溯不断构造方块。

描述

题目给定一组长度相同的单词,例如:

words = ["area","lead","wall","lady","ball"]

我们需要找出所有符合下面规则的“单词方块”:

  • 单词方块指的是:第 i 行的第 j 个字符必须等于第 j 行的第 i 个字符。

举个例子,一个 4x4 的单词方块长这样:

wall
area
lead
lady

你会发现:

  • 第一行第四个字母 l 等于第四行第一个字母 l
  • 第二行第三个字母 e 等于第三行第二个字母 e
  • … 所有对应位置都互相匹配

也就是说,它在横向和纵向读出来都一样。

输入的单词长度为 k,那么最终构造出的方块也是 k x k。

题解答案

整体解法分成两部分:

  1. 用 Trie 构建一棵前缀树,用于快速查找“某个前缀开头的所有单词”
    因为构造每一行时,我们需要检查前面各行同一列的字符组成的前缀。

  2. 使用回溯(Depth-First Search)逐步构建方块
    每添加一行,都根据当前构建出来的前缀去 Trie 查所有可能的候选词,继续递归。

只要某个方向不满足条件,就立即剪枝,这样能省掉非常多不必要的尝试。

整个算法看起来像在做“文字数独”,但 Trie 的加入让效率非常高。

题解代码分析

下面是完整可运行的 Swift 版本(包含 Trie 实现 + 回溯过程):

import Foundation// MARK: - Trie 节点结构
class TrieNode {var children: [Character: TrieNode] = [:]var words: [String] = [] // 所有以当前前缀开头的单词
}// MARK: - Trie 前缀树
class Trie {let root = TrieNode()func insert(_ word: String) {var node = rootfor ch in word {if node.children[ch] == nil {node.children[ch] = TrieNode()}node = node.children[ch]!node.words.append(word)}}func findWords(byPrefix prefix: String) -> [String] {var node = rootfor ch in prefix {guard let next = node.children[ch] else {return []}node = next}return node.words}
}// MARK: - 主解法
class Solution {func wordSquares(_ words: [String]) -> [[String]] {guard !words.isEmpty else { return [] }let trie = Trie()// 所有单词都用来构建 Triefor word in words {trie.insert(word)}var results: [[String]] = []var square: [String] = []let wordLength = words[0].count// 从每个单词开始构建for word in words {square = [word]backtrack(&results, &square, trie, wordLength)}return results}private func backtrack(_ results: inout [[String]],_ square: inout [String],_ trie: Trie,_ length: Int) {// 如果填满一个完整方块就记录结果if square.count == length {results.append(square)return}// 根据列构造前缀var prefix = ""let idx = square.countfor word in square {let chars = Array(word)prefix.append(chars[idx])}// 查询所有匹配前缀的候选单词let candidates = trie.findWords(byPrefix: prefix)// 尝试每个候选for candidate in candidates {square.append(candidate)backtrack(&results, &square, trie, length)square.removeLast()}}
}// MARK: - Demo 测试
let words = ["area", "lead", "wall", "lady", "ball"]
let s = Solution()
let result = s.wordSquares(words)
print("所有有效单词方块:")
for square in result {for row in square {print(row)}print("------")
}

代码模块分析

这里我们分几个重点部分来讲。

1. Trie 存储所有前缀匹配的单词

TrieNode 中有个 words 数组,用来存储“所有以该前缀开头的单词”。
这样查找前缀变得非常快,只需要沿着字符往下走就可以获得完整候选列表,而不是在整个词库中筛选。

这在构建方块时非常关键,因为每一行都需要根据前几行的列前缀进行查找。

2. 回溯构建单词方块

每一个位置都必须满足:

square[rowIndex][colIndex] == square[colIndex][rowIndex]

但我们不会一个字符一个字符试,而是整行整行地试。

假设目前 square 中已有两行:

wall
area

我们要添加第三行时,需要根据纵向得到位置 (2,0)(2,1)(2,2) 的前缀:

l e a

然后去 Trie 查所有以 “lea” 开头的单词。

这就是前缀树让我们能高效查找下一个“合法候选”的原因。

3. 剪枝

如果某个前缀在 Trie 中找不到任何单词,就直接终止当前分支。
大大节省计算量。

示例测试及结果

假设输入:

["area", "lead", "wall", "lady", "ball"]

运行结果:

所有有效单词方块:
wall
area
lead
lady
------
ball
area
lead
lady
------

说明程序运行正常,也找到了题目示例中的两个有效方块。

你可以随便再加一些字符串测试,比如只给同一个长度的前缀词表,它也会正常跑。

时间复杂度

时间复杂度比较难算,因为回溯的分支数量依赖于前缀相匹配的单词数量。大概可以拆解如下:

  • 构建 Trie:O(n * k),n 是单词数量,k 是单词长度
  • 回溯:最坏情况可能接近 O(n^k),但通过前缀剪枝,通常远低于暴力

在实际测试数据中,由于前缀树剪枝很有效,整体性能是能通过 LeetCode 的。

空间复杂度

  • Trie 占用:O(n * k)
  • 回溯栈深度最多为 k
  • 字符前缀搜索缓存为常数级

整体空间复杂度是 O(n * k)。

总结

这道题本质是前缀匹配问题的拓展版,而前缀树正好是最契合的解决方案。

关键点在于:

  • 构建 Trie,使得“根据前缀找所有单词”的操作变得高效
  • 使用回溯逐步构造方块
  • 通过前缀剪枝避免无效搜索

这种算法不仅能解决 LeetCode 题,在实际工程里构建自动补全、智能前缀匹配、搜索推荐系统时也非常常用。

http://www.dtcms.com/a/614295.html

相关文章:

  • 我要建设一个网站全国可信网站
  • Matlab速成笔记68:质数、质因数分解、阶乘、最大公约数、最小公倍数
  • [智能体设计模式] 第13章:人类参与环节(HITL)
  • 线代强化NO7|秩|矩阵的秩|向量组的秩|极大线性无关组|公式
  • 计算机网络安全--第三章-网络安全体系及管理
  • 11.15 脚本算法 加密网页
  • 前端CSS架构模式,BEM与ITCSS
  • 【深度学习】深度学习概念
  • 大连建设执业资格注册中心网站互联网项目推广
  • 源码交易网站源码怎么在网站做系统
  • 前端性能预算工具,控制资源大小
  • 海丰网站制作一个网站能放多少关键词
  • 计算机网络复习日报19
  • 【C++】10.用哈希表封装myunordered_map和myunordered_set
  • 黄陂建设网站做网站搞流量挂联盟广告变现
  • JSP Session
  • 鸿安建设集团网站百度免费建立网站
  • 【学习心得】conda打包进行环境迁移遇到conda包和pip包管理冲突问题
  • 做网站需要写配置文件吗教研网站建设方案
  • 51c视觉~合集52
  • 免费的虚拟现实开发教程,WebXR
  • linux下libcurl的https简单例子
  • 网站建立的意义建立网站可以赚钱吗?
  • 全连接层详解:从原理到应用的全面解析
  • Docker-Dockerfile 完全指南:编写最佳实践的镜像
  • 百度智能云建站广州地址设计网站
  • macOS系统中使用clang/clang++编译Skia源码的方法
  • 建设银行的网站怎么打开桂林建网站哪家好
  • 在线教育网站策划方案苏州做网站最好公司有哪些
  • 网站制作需要网站制作wordpress内容付费模板