动态规划-三维-扰乱字符串

LeetCode-Problem87#较典型的三维动态规划问题

1) 题目

在这里插入图片描述在这里插入图片描述

2) 解析(参考自此精选解答
  • 设有两个字符串S和T,S想通过上述扰乱得到T
  • 如果len(S) != len(T),或者S/T中有对方没有的字符(如S=ab/T=ac)或者比双方唯一字符集不等(如S=aab/T=aaa,前者唯一字符集[ab],后者[a])
  • 如果长度一样,S/T可转换,必有某种切分使得S=S1||S2,T=T1||T2(||表示拼接),且
    • 情况一:没交换,S1=>T1 AND S2=>T2
    • 情况二:交换了,S1=>T2 AND S2=>T1在这里插入图片描述
  • 上述“某种切分”意味着一次全局长度遍历;同时,切分得到的局部片段,又蕴含着新的切分和新的局部长度遍历;所以这个问题可视为递归问题,分析角度是从上至下;而计算时由下至上的 递归+缓存 往往就是动态规划;
  • 状态state:根据上述分析,最直截了当的状态就是 s t a t e = d p [ i ] [ k ] [ j ] [ t ] = T r u e / F a l s e state=dp[i][k][j][t]=True/False state=dp[i][k][j][t]=True/False,表示S[i:k]和T[j:t]是否可以转换;考虑可转换前提是长度一样,即 k − i = t − j = l k-i=t-j=l ki=tj=l,则可简化为 s t a t e = d p [ i ] [ j ] [ l ] state=dp[i][j][l] state=dp[i][j][l],l为局部长度
  • 由此,我们最后要求的是 d p [ 0 ] [ 0 ] [ L ] dp[0][0][L] dp[0][0][L],其中 L = l e n ( S ) = l e n ( T ) L=len(S)=len(T) L=len(S)=len(T),整个递归/转移方程如下,描述了上述情况一和情况二:
    d p [ 0 ] [ 0 ] [ L ] = ⋃ 1 ≤ l ≤ L − 1 ( ( d p [ 0 ] [ 0 ] [ l ] ⋂ d p [ 0 + l ] [ 0 + l ] [ L − l ] ) ⋃ ( d p [ 0 ] [ 0 + L − l ] [ l ] ⋂ d p [ 0 + l ] [ 0 ] [ L − l ] ) ) dp[0][0][L]=\bigcup _{1\le l \le L-1} \Bigg(\bigg(dp[0][0][l] \bigcap dp[0+l][0+l][L-l]\bigg) \bigcup \bigg(dp[0][0+L-l][l] \bigcap dp[0+l][0][L-l]\bigg) \Bigg) dp[0][0][L]=1lL1((dp[0][0][l]dp[0+l][0+l][Ll])(dp[0][0+Ll][l]dp[0+l][0][Ll]))
  • 显然上述递归式包含大量重复计算,比如计算 d p [ 0 ] [ 0 ] [ 3 ] dp[0][0][3] dp[0][0][3]和计算 d p [ 0 ] [ 0 ] [ 4 ] dp[0][0][4] dp[0][0][4],后者依赖于前者的结果;其他位置也一样,即对任意 i , j ∈ [ 0 , L − 1 ] i,j\in[0,L-1] i,j[0,L1],要计算 d p [ i ] [ j ] [ l ] dp[i][j][l] dp[i][j][l],必须要先计算 d p [ i ] [ j ] [ 1 ] dp[i][j][1] dp[i][j][1]等长度小于 l l l的情况;由此可知,计算角度是从下至上的
  • 综上,除了 l l l在L上的遍历,还需要两个字符串上各个位置 i / j i/j i/j的遍历,以及局部片段上 l l o c a l l^{local} llocal的遍历,代码如下,时间复杂度 O ( n 4 ) O(n^4) O(n4),空间复杂度 O ( n 3 ) O(n^3) O(n3)
class Solution:
    def isScramble(self, s1: str, s2: str) -> bool:
        # dp[i][j][k]表示s1的pos_i和s2的pos_j是否有长度为k的可转换字符串
        # 返回dp[0][0][L],其中L为len(s1)=len(s2)=L
        # 状态方程 dp[i][j][k] = OR(l in (1,k-1))[dp[i][j][l]&&dp[i+l][j+l][k-l]] OR
        #                       OR(l in (1,k-1))[dp[i][j+k-l][l]]&&dp[i+l][j][k-l])
        # 表示dp[i][j][k]=TRUE需要满足两大类中的某一类(调换或者不调换)
        len_s1, len_s2 = len(s1), len(s2)
        set_s1, set_s2 = set(s1), set(s2)
        # 初步判断结果,节省时间
        if len_s1 != len_s2 or len(set_s1.union(set_s2)) != len(set_s1) or len(set_s1.intersection(set_s2)) != len(set_s1):
            return False
        # 构造dp三维数组
        dp = [[[False for _ in range(len_s1+1)] for _ in range(len_s1+1)] for _ in range(len_s1+1)]
        # 初始化,形成由下至上计算的初始值
        for i in range(0, len_s1):
            for j in range(0, len_s1):
                dp[i][j][1] = s1[i] == s2[j]
        # 基于上述初始值开始回溯
        for l in range(2, len_s1+1):  # 长度遍历(字符串整体遍历)
            for i in range(0, len_s1-l+1):  # 位置遍历(s1),受限于局部子串长度l
                for j in range(0, len_s1-l+1):  # 位置遍历(s2),受限于局部子串长度l
                    for w in range(1, l):  # 长度遍历(局部子串内部遍历)
                        dp[i][j][l] = (dp[i][j][w] and dp[i+w][j+w][l-w]) or (dp[i][j+l-w][w] and dp[i+w][j][l-w])
                        if dp[i][j][l]:  # dp[i][j][l]出现True表示可转换,即可停止计算
                            break
        return dp[0][0][len_s1] 
  • 总结:熟悉三维动态规划的应用场景和状态的意义,熟悉由上至下的递归分析以及由下至上的计算回溯
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值