0087. 扰乱字符串
大约 4 分钟
---
0087. 扰乱字符串
- 标签:字符串、动态规划
- 难度:困难
题目链接
题目大意
描述:
使用下面描述的算法可以扰乱字符串 得到字符串 :
- 如果字符串的长度为 ,算法停止。
- 如果字符串的长度 > ,执行下述步骤:
- 在一个随机下标处将字符串分割成两个非空的子字符串。即,如果已知字符串 ,则可以将其分成两个子字符串 和 ,且满足 。
- 随机决定是要「交换两个子字符串」还是要「保持这两个子字符串的顺序不变」。即,在执行这一步骤之后, 可能是 或者 。
- 在 和 这两个子字符串上继续从步骤 开始递归执行此算法。
给定两个长度相等的字符串 和 。
要求:
判断 是否是 的扰乱字符串。如果是,返回 ;否则,返回 。
说明:
- 。
- 。
- 和 由小写英文字母组成。
示例:
- 示例 1:
输入:s1 = "great", s2 = "rgeat"
输出:true
解释:s1 上可能发生的一种情形是:
"great" --> "gr/eat" // 在一个随机下标处分割得到两个子字符串
"gr/eat" --> "gr/eat" // 随机决定:「保持这两个子字符串的顺序不变」
"gr/eat" --> "g/r / e/at" // 在子字符串上递归执行此算法。两个子字符串分别在随机下标处进行一轮分割
"g/r / e/at" --> "r/g / e/at" // 随机决定:第一组「交换两个子字符串」,第二组「保持这两个子字符串的顺序不变」
"r/g / e/at" --> "r/g / e/ a/t" // 继续递归执行此算法,将 "at" 分割得到 "a/t"
"r/g / e/ a/t" --> "r/g / e/ a/t" // 随机决定:「保持这两个子字符串的顺序不变」
算法终止,结果字符串和 s2 相同,都是 "rgeat"
这是一种能够扰乱 s1 得到 s2 的情形,可以认为 s2 是 s1 的扰乱字符串,返回 true
- 示例 2:
输入:s1 = "abcde", s2 = "caebd"
输出:false
解题思路
思路 1:动态规划
核心思想:
使用三维动态规划来解决扰乱字符串问题。定义 表示 从位置 开始长度为 的子串,是否可以通过扰乱操作得到 从位置 开始长度为 的子串。
算法步骤:
- 状态定义: 表示 是否可以通过扰乱得到 。
- 边界条件:当 时,。
- 状态转移:对于长度 的子串,尝试所有可能的分割点 ():
- 不交换情况:
- 交换情况:
- 最终结果: 即为答案。
关键点:
- 需要枚举所有可能的分割点 ,其中 表示左子串的长度
- 对于每个分割点,考虑交换和不交换两种情况
- 使用记忆化搜索或自底向上的动态规划来避免重复计算
思路 1:代码
class Solution:
def isScramble(self, s1: str, s2: str) -> bool:
"""
判断 s2 是否是 s1 的扰乱字符串
使用动态规划方法
"""
n = len(s1)
if n != len(s2):
return False
# 三维 DP 数组:dp[i][j][k] 表示 s1[i:i+k] 是否可以通过扰乱得到 s2[j:j+k]
dp = [[[False] * (n + 1) for _ in range(n)] for _ in range(n)]
# 边界条件:长度为 1 的子串
for i in range(n):
for j in range(n):
dp[i][j][1] = (s1[i] == s2[j])
# 动态规划:枚举所有可能的长度和分割点
for k in range(2, n + 1): # 子串长度从 2 到 n
for i in range(n - k + 1): # s1 的起始位置
for j in range(n - k + 1): # s2 的起始位置
# 尝试所有可能的分割点 l (1 <= l < k)
for l in range(1, k):
# 情况 1:不交换,左子串对应左子串,右子串对应右子串
if dp[i][j][l] and dp[i + l][j + l][k - l]:
dp[i][j][k] = True
break
# 情况 2:交换,左子串对应右子串,右子串对应左子串
if dp[i][j + k - l][l] and dp[i + l][j][k - l]:
dp[i][j][k] = True
break
return dp[0][0][n]
思路 1:复杂度分析
- 时间复杂度:,其中 是字符串长度。需要四重循环:长度 、起始位置 和 、分割点 ,每重循环最多 次。
- 空间复杂度:,需要 空间存储三维 DP 数组。