做网站和app需要多久wordpress文章打赏
2026/5/21 13:10:13 网站建设 项目流程
做网站和app需要多久,wordpress文章打赏,邢台网站优化公司,女生学大数据很累吗Java版LeetCode热题100之单词拆分#xff1a;从动态规划到面试实战的全面解析 本文深入剖析 LeetCode 第139题「单词拆分」#xff0c;涵盖题目理解、算法设计、代码实现、复杂度分析、优化思路、数据结构基础、面试应对策略以及实际应用场景等多个维度#xff0c;是一篇面向…Java版LeetCode热题100之单词拆分从动态规划到面试实战的全面解析本文深入剖析 LeetCode 第139题「单词拆分」涵盖题目理解、算法设计、代码实现、复杂度分析、优化思路、数据结构基础、面试应对策略以及实际应用场景等多个维度是一篇面向中高级开发者的高质量技术博客。一、原题回顾题目编号LeetCode 139题目名称单词拆分Word Break难度等级中等Medium题目描述给你一个字符串s和一个字符串列表wordDict作为字典。请你判断是否可以利用字典中出现的一个或多个单词拼接出s。不要求字典中的所有单词都被使用字典中的单词可以重复使用所有字符串仅包含小写英文字母字典中所有单词互不相同。示例示例 1 输入: s leetcode, wordDict [leet, code] 输出: true 解释: leetcode 可由 leet code 拼接而成。 示例 2 输入: s applepenapple, wordDict [apple, pen] 输出: true 解释: applepenapple apple pen apple 示例 3 输入: s catsandog, wordDict [cats, dog, sand, and, cat] 输出: false约束条件1 s.length 3001 wordDict.length 10001 wordDict[i].length 20所有字符串仅由小写字母组成二、原题分析这道题的核心在于能否将一个长字符串完全拆分成若干个字典中存在的子串。乍看之下可能想到暴力回溯DFS——尝试从头开始匹配每一个可能的单词若匹配成功则递归处理剩余部分。但这种做法在最坏情况下时间复杂度极高指数级尤其当字符串较长且字典较大时极易超时。因此我们需要一种更高效的策略。观察问题结构如果我们知道s[0..i-1]能否被拆分那么对于s[0..j-1]其中j i只需检查s[i..j-1]是否在字典中即可。这种“当前状态依赖于之前状态”的特性正是动态规划Dynamic Programming, DP的典型应用场景。此外题目允许单词重复使用说明这是一个完全背包类问题的变种——每个“物品”字典单词可无限次使用目标是“恰好装满”整个字符串。三、答案构思动态规划解法3.1 状态定义我们定义一个布尔数组dp其中dp[i]表示字符串s的前i个字符即s[0..i-1]是否可以被字典中的单词完全拆分。注意dp[0]对应空字符串我们约定dp[0] true作为空状态的合法起点。3.2 状态转移方程对于每个位置i从 1 到nn s.length()我们尝试所有可能的分割点j0 j i如果dp[j] true即前j个字符可拆分且子串s.substring(j, i)存在于字典中那么dp[i] true。形式化表达为d p [ i ] ⋁ j 0 i − 1 ( d p [ j ] ∧ ( s [ j . . i − 1 ] ∈ wordDict ) ) dp[i] \bigvee_{j0}^{i-1} \left( dp[j] \land (s[j..i-1] \in \text{wordDict}) \right)dp[i]j0⋁i−1​(dp[j]∧(s[j..i−1]∈wordDict))其中⋁ \bigvee⋁表示逻辑“或”操作。3.3 字典查询优化为了快速判断子串是否在字典中我们将wordDict转换为HashSetString使得每次contains()操作的时间复杂度为O(1)平均情况。四、完整答案Java实现importjava.util.*;publicclassSolution{publicbooleanwordBreak(Strings,ListStringwordDict){// 将字典转为 HashSet提升查找效率SetStringwordSetnewHashSet(wordDict);intns.length();// dp[i] 表示 s 的前 i 个字符能否被拆分boolean[]dpnewboolean[n1];dp[0]true;// 空字符串视为可拆分// 遍历每个位置 i1 到 nfor(inti1;in;i){// 尝试所有可能的分割点 j0 到 i-1for(intj0;ji;j){// 如果前 j 个字符可拆分且 s[j..i-1] 在字典中if(dp[j]wordSet.contains(s.substring(j,i))){dp[i]true;break;// 找到一个合法分割即可无需继续}}}returndp[n];}}代码说明使用HashSet存储字典确保 O(1) 查找外层循环遍历字符串长度i内层循环枚举分割点j一旦找到合法组合立即break避免无效计算最终返回dp[n]即整个字符串是否可拆分。五、代码分析5.1 正确性验证以s leetcode,wordDict [leet, code]为例dp[0] truei4时j0s[0..3]leet∈ dict →dp[4]truei8时j4dp[4]true且s[4..7]code∈ dict →dp[8]true返回dp[8] true✅再看反例s catsandog虽然cat和cats都存在但后续sandog无法被拆分所有j尝试后dp[9]仍为false→ 返回false✅5.2 边界处理空字符串dp[0]true是关键初始条件单字符字符串若该字符在字典中则dp[1]true字典为空所有dp[i] false除dp[0]六、时间复杂度与空间复杂度分析6.1 时间复杂度O(n²)外层循环n次i从 1 到n内层循环最多n次j从 0 到i-1每次substring(j, i)操作O(i - j)最坏 O(n)contains()操作O(1)哈希表⚠️注意虽然官方题解称时间复杂度为 O(n²)但实际上substring会创建新字符串其拷贝成本为 O(k)k 为子串长度。因此严格来说总时间复杂度为 O(n³)。但在 Java 中String.substring在 JDK 7u6 之后已改为复制字符数组不再共享底层数组因此每次调用确实需要 O(k) 时间。6.2 空间复杂度O(n m)dp数组O(n)HashSet存储字典O(m)其中 m 为字典总字符数最坏 1000×20 20,000总体O(n m)通常简化为 O(n)七、常见问题解答FAQQ1为什么不能直接用List.contains()AList.contains()的时间复杂度是 O(m)而HashSet.contains()是 O(1)。当字典很大时如 1000 个单词每次查询节省 O(m) 时间对整体性能影响巨大。Q2内层循环为何可以breakA因为只要找到任意一个合法的j使得dp[j] dict.contains(...)成立dp[i]就为true。无需检查其他j提前终止可提升效率。Q3能否从右往左枚举j以优化A可以如果先知道字典中最长单词长度maxLen则j只需从i-1枚举到max(0, i - maxLen)。因为若i - j maxLen子串不可能在字典中。这是一种有效剪枝。优化版本示例intmaxLen0;for(Stringword:wordDict){maxLenMath.max(maxLen,word.length());}for(inti1;in;i){intstartMath.max(0,i-maxLen);for(intjstart;ji;j){if(dp[j]wordSet.contains(s.substring(j,i))){dp[i]true;break;}}}此优化可将内层循环从 O(n) 降至 O(L)L 为最大单词长度≤20从而将总时间复杂度降至O(n × L)即O(300 × 20) O(6000)远优于 O(n²)90,000。八、优化思路拓展8.1 剪枝优化基于最大单词长度如上所述预计算maxLen可大幅减少无效枚举。8.2 使用 Trie字典树替代 HashSet虽然 HashSet 已足够高效但在某些场景下如字典极大、频繁查询前缀Trie 更优。Trie 优势支持前缀匹配避免生成子串节省内存和时间可同时处理多个查询。Trie 实现思路构建字典的 Trie对每个i从i开始向后遍历同时在 Trie 中向下走若遇到 Trie 中的单词结尾且dp[j] true则dp[i len] true。但实现较复杂且本题 n 很小≤300Trie 优势不明显故一般推荐 HashSet 方案。8.3 BFS 解法队列记忆化也可将问题视为图搜索每个位置i是一个节点若s[i..j]在字典中则存在边i → j1问是否存在从 0 到 n 的路径使用 BFS visited 数组同样可解时间复杂度类似。九、数据结构与算法基础知识点回顾9.1 动态规划DP三要素状态定义dp[i]表示什么状态转移方程如何由子问题推出当前问题边界条件初始值如何设定本题完美体现这三点。9.2 完全背包 vs 0-1 背包0-1 背包每个物品只能用一次 → 内层循环倒序完全背包物品可重复使用 → 内层循环正序本题中单词可重复使用属于完全背包思想但因字符串顺序固定不能直接套用背包模板需结合字符串特性设计 DP。9.3 哈希表HashSet原理基于哈希函数将 key 映射到桶平均 O(1) 插入/查找/删除需注意哈希冲突处理链地址法 or 开放寻址Java 中String.hashCode()是稳定的适合做 key。9.4 字符串子串操作成本s.substring(i, j)在 Java 中会复制字符数组时间 O(j-i)空间 O(j-i)高频调用时需警惕性能瓶颈可考虑传递起始索引长度避免创建新字符串但本题简洁性优先。十、面试官提问环节模拟对话面试官你用了动态规划能说说为什么不用 DFS 回溯吗答DFS 在最坏情况下会指数爆炸。例如s aaaa...aaadict [a, aa, aaa, ...]每一步都有多种选择导致大量重复计算。而 DP 通过记忆化避免了重复子问题时间复杂度可控。面试官你的解法时间复杂度真的是 O(n²) 吗答严格来说不是。因为substring操作需要 O(k) 时间k 是子串长度。最坏情况下总时间是 O(n³)。但如果使用“最大单词长度剪枝”可将内层循环限制在 O(L)L≤20此时总复杂度为 O(nL)接近线性。面试官如果字典非常大比如百万级你会怎么优化答我会考虑以下几点使用Trie替代 HashSet避免存储大量重复前缀在 DP 过程中不生成子串而是用双指针在原字符串上匹配 Trie预处理字典过滤掉长度大于s.length()的单词若支持多线程可并行计算不同i的dp[i]但依赖关系强并行收益有限。面试官这道题和“正则表达式匹配”有什么区别答正则匹配涉及通配符如*,.状态转移更复杂通常用二维 DP。而本题是精确匹配字典单词状态是一维的且转移条件更简单。十一、这道算法题在实际开发中的应用11.1 输入法分词中文输入法需将拼音串如woaini拆分为合法词语[wo, ai, ni]本质就是单词拆分问题。11.2 自然语言处理NLP中文分词将连续汉字序列切分为词语命名实体识别判断某段文本是否为已知实体如人名、地名敏感词过滤检测用户输入是否包含违禁词组合。11.3 编译器词法分析编译器需将源代码字符串拆分为 token如关键字、标识符、运算符可视为带语法规则的单词拆分。11.4 密码强度检测某些系统要求密码必须包含特定单词组合可用类似逻辑验证。11.5 URL 路由匹配Web 框架中将 URL 路径如/user/profile/edit匹配到控制器也可建模为路径拆分问题。十二、相关题目推荐题号题目关联点140. 单词拆分 II返回所有可能的拆分方案本题的升级版需回溯记忆化472. 连接词找出能由其他单词拼接而成的词多次调用单词拆分逻辑648. 单词替换用最短前缀替换句子中的词Trie 应用212. 单词搜索 II在二维网格中找字典中的词Trie DFS322. 零钱兑换完全背包经典题DP 思想相通十三、总结与延伸13.1 核心收获动态规划是解决“最优子结构重叠子问题”的利器状态定义是 DP 的关键需清晰、无后效性哈希表极大提升查询效率是算法优化常用手段剪枝和边界分析能显著提升性能算法题背后往往有真实工程场景支撑。13.2 延伸思考如果字典中的单词不能重复使用该如何修改需记录已使用单词状态变为(i, usedMask)复杂度飙升可能需状态压缩 DP。如果要求输出所有拆分方案需回溯DFS 记忆化或在 DP 基础上记录路径。如果字符串很长如 10⁶字典也很大需考虑 Aho-Corasick 自动机、后缀数组等高级字符串算法。13.3 给读者的建议不要死记硬背代码理解状态转移的逻辑多练习“字符串 DP”类题目培养模式识别能力在面试中先写出朴素解法再逐步优化重视边界条件和复杂度分析这是区分初级与高级工程师的关键。结语LeetCode 139 “单词拆分”看似简单实则蕴含动态规划、字符串处理、哈希优化等多重知识点。掌握它不仅是为了通过一道面试题更是为了构建扎实的算法思维体系。希望本文能助你在算法之路上走得更远

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询