87. 扰乱字符串
考点
- 区间DP
思路
1. 问题描述
给定两个长度相同的字符串 \(s_1\) 与 \(s_2\),判断 \(s_2\) 是否可以由 \(s_1\) 通过若干次“扰乱操作”得到。
扰乱操作定义如下:
- 若字符串长度为 1,则不能再拆分;
- 否则,可以将字符串拆分为左右两个非空连续子串;
- 对左右子串可以选择:
- 保持原顺序;
- 或交换左右子串;
- 对子串递归地继续进行扰乱。
问题要求判断:是否存在一系列合法的拆分与交换操作,使得 \(s_1\) 最终变为 \(s_2\)。
2. 建模思路:区间 DP 的必要性
2.1 递归结构的本质
扰乱字符串的定义本身具有明显的递归结构:
- 每一次扰乱,都是将一个字符串区间拆成两个更小的连续区间;
- 判断两个字符串是否“可扰乱”等价于:
- 是否存在某种切分方式,使左右子区间两两对应(允许交换)且均可扰乱。
这天然对应区间动态规划(Interval DP)。
2.2 状态设计的关键问题
在区间 DP 中,核心在于回答两个问题:
- 一个状态应当描述什么子问题?
- 状态在转移时是否“封闭”?
本题中,必须同时刻画:
- \(s_1\) 中的一个连续子串;
- \(s_2\) 中与之比较的一个连续子串;
- 两者的长度必须相同。
因此,最小且封闭的状态设计为: \[ \boxed{ f[i][j][\ell] \;=\; \text{表示 } s_1[i\,..\,i+\ell-1] \text{ 是否可以扰乱成 } s_2[j\,..\,j+\ell-1] } \] 其中:
- \(i\):\(s_1\) 中子串起点;
- \(j\):\(s_2\) 中子串起点;
- \(\ell\):子串长度。
这是本题区间 DP 的核心建模结论。
3. 边界条件(Base Case)
当子串长度为 1 时,不再允许拆分: \[ f[i][j][1] = \begin{cases} \text{true}, & s_1[i] = s_2[j] \\ \text{false}, & \text{otherwise} \end{cases} \] 这一步对应 DP 的初始化。
4. 状态转移方程的推导
4.1 区间拆分
考虑长度为 \(\ell \ge 2\) 的区间:
- \(s_1[i\,..\,i+\ell-1]\)
- \(s_2[j\,..\,j+\ell-1]\)
选择一个切分点 \(k\)(左半段长度): \[ 1 \le k < \ell \] 将 \(s_1\) 拆为:
- 左:\(s_1[i\,..\,i+k-1]\),长度 \(k\)
- 右:\(s_1[i+k\,..\,i+\ell-1]\),长度 \(\ell-k\)
4.2 不交换的情况
左右子串保持顺序对应: \[ \begin{cases} s_1[i\,..\,i+k-1] \leftrightarrow s_2[j\,..\,j+k-1] \\ s_1[i+k\,..\,i+\ell-1] \leftrightarrow s_2[j+k\,..\,j+\ell-1] \end{cases} \] 对应 DP 条件: \[ f[i][j][k] \;\land\; f[i+k][j+k][\ell-k] \]
4.3 交换的情况
左右子串在 \(s_2\) 中发生交换: \[ \begin{cases} s_1[i\,..\,i+k-1] \leftrightarrow s_2[j+\ell-k\,..\,j+\ell-1] \\ s_1[i+k\,..\,i+\ell-1] \leftrightarrow s_2[j\,..\,j+\ell-k-1] \end{cases} \] 对应 DP 条件: \[ f[i][j+\ell-k][k] \;\land\; f[i+k][j][\ell-k] \]
4.4 完整状态转移方程
综合两种情况,只要存在某个 \(k\) 使得条件成立即可: \[ \boxed{ f[i][j][\ell] = \bigvee_{k=1}^{\ell-1} \left( \left( f[i][j][k] \land f[i+k][j+k][\ell-k] \right) \;\lor\; \left( f[i][j+\ell-k][k] \land f[i+k][j][\ell-k] \right) \right) } \] 这正是扰乱字符串问题的标准区间 DP 转移公式。
5. 计算顺序与复杂度分析
5.1 计算顺序
由于 \(f[i][j][\ell]\) 依赖于更小的长度:
- 外层按 \(\ell = 1 \to n\) 枚举长度;
- 内层枚举 \(i, j\);
- 最内层枚举切分点 \(k\)。
这是典型的按区间长度递增的区间 DP。
5.2 时间与空间复杂度
- 状态数:\(O(n^3)\)
- 每个状态枚举切分点:\(O(n)\)
\[ \text{时间复杂度} = O(n^4), \quad n \le 30 \text{ 可接受} \]
6. 参考实现(基于区间 DP)
下面给出一份与上述建模与转移一一对应的实现,并对代码逐行加注释。
1 | class Solution { |
7. 总结
- 扰乱字符串问题的核心在于区间拆分 + 交换的递归结构;
- 正确的 DP 状态必须同时记录:
- 两个字符串的子串起点;
- 子串长度;
- 状态转移本质上是对“是否存在某个切分点 \(k\)”的枚举;
- 三维区间 DP 是该问题最直接、最稳定的建模方式。