☰
LeetCode 1416:动态规划解决字符串分割问题
2026/10/14 14:42:14 网站建设 项目流程

1. 题目背景与核心问题解析

这道LeetCode困难题编号1416,标题为"Restore The Array"(恢复数组)。题目描述了一个看似简单但实际颇具挑战性的字符串处理问题:给定一个由数字组成的字符串s和一个整数k,我们需要计算出所有可能的方案数,将这个字符串分割成若干个子串,使得每个子串表示的数字都不大于k,并且不包含前导零(除了数字0本身)。

我第一次看到这个题目时,被它的简洁描述所迷惑,以为只是一个简单的字符串分割问题。但深入分析后才发现,这实际上是一个典型的动态规划应用场景,涉及到字符串处理、数值边界判断和模运算等多个计算机科学核心概念。

2. 问题分析与解法思路

2.1 题目具体要求拆解

题目给出的具体条件是:

  1. 输入字符串s只包含数字('0'-'9')
  2. 整数k的范围是1到10^9
  3. 需要返回所有有效分割方案的数量,结果对10^9+7取模
  4. 有效分割必须满足:
    • 每个子串表示的数字≤k
    • 子串不能有前导零(即长度大于1的子串不能以'0'开头)

2.2 暴力解法与复杂度分析

最直观的解法是尝试所有可能的分割方式,然后统计符合条件的方案数。这种方法的时间复杂度是O(2^n),其中n是字符串长度。对于n=100的情况(题目给出的上限),这样的复杂度显然无法接受。

注意:在实际编码面试中,即使你能想到暴力解法,也应该立即意识到这不是最优解,并主动向面试官说明这一点。

2.3 动态规划思路建立

通过分析,我们可以发现这个问题具有最优子结构性质:

  • 整个字符串的有效分割数,可以分解为考虑不同长度的前缀的有效分割数
  • 子问题的解可以被重复利用

这提示我们可以使用动态规划来解决。定义dp[i]表示前i个字符(s[0..i-1])的有效分割方案数。我们的目标是求dp[n],其中n是字符串长度。

3. 动态规划实现详解

3.1 状态转移方程推导

对于每个位置i,我们需要考虑所有可能的j(j < i),使得子串s[j..i-1]满足:

  1. 没有前导零(除非子串就是"0")
  2. 数值不超过k

如果满足条件,那么dp[i] += dp[j]

初始条件是dp[0] = 1(空字符串有1种分割方式)

3.2 边界条件处理

实现时需要特别注意几个边界条件:

  1. 子串长度不能超过k的位数(因为k最多是10^9,即最多10位)
  2. 处理大数比较(当子串长度等于k的位数时)
  3. 前导零的特殊处理

3.3 代码实现示例

MOD = 10**9 + 7 def numberOfArrays(s: str, k: int) -> int: n = len(s) dp = [0] * (n + 1) dp[0] = 1 max_len = len(str(k)) for i in range(1, n + 1): for j in range(i - 1, max(-1, i - max_len - 1), -1): if s[j] == '0': continue num = int(s[j:i]) if num <= k: dp[i] = (dp[i] + dp[j]) % MOD else: break return dp[n]

4. 算法优化与复杂度分析

4.1 时间复杂度优化

原始实现中,内层循环最多执行min(i, max_len+1)次,其中max_len是k的位数(最多10)。因此总时间复杂度是O(n * 10) = O(n),对于n=1e5也完全可以接受。

4.2 空间复杂度优化

dp数组只需要O(n)空间,没有使用额外数据结构,空间复杂度已经是理论下限。

4.3 大数处理技巧

当k接近10^9时,直接使用int转换可能会在某些语言中导致溢出。可以提前比较字符串长度和k的位数:

  1. 如果子串长度 > k的位数,直接跳过
  2. 如果长度相等,可以逐位比较字符串和k的字符串表示

5. 常见错误与调试技巧

5.1 前导零处理不当

常见错误是忘记处理前导零的情况。例如:

  • 输入"001", k=1
  • 错误解法可能会认为"0|0|1"是有效分割
  • 正确解法应该识别到"00"开头的子串无效

5.2 数值越界问题

当k=1e9时,某些语言中int可能无法处理更大的数。解决方案:

  1. 提前比较字符串长度
  2. 使用更高精度的整数类型

5.3 模运算遗漏

必须在每次加法后立即取模,否则中间结果可能溢出。例如:

# 错误写法 dp[i] += dp[j] dp[i] %= MOD # 正确写法 dp[i] = (dp[i] + dp[j]) % MOD

6. 测试用例设计建议

6.1 常规测试用例

  • "1317", k=2000 → 8
  • "2020", k=30 → 1
  • "1234", k=50 → 5

6.2 边界测试用例

  • "0", k=0 → 1
  • "1000000000", k=1000000000 → 1
  • "1000", k=10 → 0

6.3 性能测试用例

  • 长字符串(1e5个'1'),k=1e9 → 应快速返回结果

7. 实际应用场景延伸

虽然这是一个算法题,但类似的字符串分割问题在实际开发中很常见,例如:

  1. IP地址恢复(LeetCode 93)
  2. 文本分词处理
  3. 序列化数据解析

掌握这类动态规划解法,可以帮助我们更好地处理各种字符串解析问题。我在实际工作中就曾遇到过类似的日志解析需求,需要将连续的日志标识符分割成有效的消息块,当时的解决方案就借鉴了这道题的思路。

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

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

立即咨询