【LeetCode】139. 单词拆分
解题思路
本问题可以通过 动态规划 结合 哈希表优化 解决。核心思想是定义 dp[i] 表示字符串 s 的前 i 个字符是否可以被字典中的单词拼接而成。通过预处理字典中的单词,利用哈希表快速查询子串,并结合动态规划逐步推导结果。
关键步骤
-
预处理字典:
- 将字典中的单词存入哈希表,便于快速查询子串是否存在。
- 记录字典中单词的最大长度
maxLen,减少无效的子串检查。
-
动态规划初始化:
- 定义
dp数组,dp[0] = true表示空字符串可以被拼接。
- 定义
-
状态转移:
- 遍历字符串
s的每个位置i(从1到n)。 - 对于每个位置
i,检查所有可能的子串长度l(从1到maxLen)。 - 若子串
s[j:i]存在于字典且dp[j]为true,则标记dp[i] = true。
- 遍历字符串
-
结果判断:
- 最终返回
dp[n](n为s的长度)。
- 最终返回
复杂度分析
- 时间复杂度:
O(n * maxLen)
其中n是字符串s的长度,maxLen是字典中最长单词的长度。每个位置最多检查maxLen次子串。 - 空间复杂度:
O(n + m)n为dp数组的长度,m为字典中单词的总数(哈希表存储)。
代码实现
func wordBreak(s string, wordDict []string) bool { // 预处理字典:存储单词并记录最大长度 wordSet := make(map[string]bool) maxLen := 0 for _, word := range wordDict { wordSet[word] = true if len(word) > maxLen { maxLen = len(word) } } n := len(s) dp := make([]bool, n+1) dp[0] = true // 空字符串初始化为true for i := 1; i <= n; i++ { // 检查所有可能的子串长度(1到maxLen) start := max(0, i - maxLen) // 防止越界 for j := start; j < i; j++ { if dp[j] && wordSet[s[j:i]] { dp[i] = true break // 找到一个可行解即可跳出 } } } return dp[n] } func max(a, b int) int { if a > b { return a } return b }
代码注释
- 字典预处理
使用哈希表存储字典中的单词,并记录最长单词长度,减少无效子串遍历。 - 动态规划边界
dp[0] = true表示空字符串的初始状态。 - 状态转移优化
通过start := max(0, i - maxLen)限制子串检查范围,避免无效遍历。 - 子串查询
利用哈希表wordSet快速判断子串s[j:i]是否存在于字典。
运行示例
func main() { fmt.Println(wordBreak("leetcode", []string{"leet", "code"})) // true fmt.Println(wordBreak("applepenapple", []string{"apple", "pen"})) // true fmt.Println(wordBreak("catsandog", []string{"cats", "dog", "sand", "and", "cat"})) // false }
算法扩展
- 字典树优化
若字典规模极大,可用字典树(Trie)替代哈希表,将子串查询时间优化为O(l)(l为子串长度)。 - 剪枝策略
在动态规划过程中,若dp[i]已为true,可提前终止后续子串检查,进一步减少计算量。

浙公网安备 33010602011771号