📅  最后修改于: 2023-12-03 15:07:58.287000             🧑  作者: Mango
给定一个长度为$n(n<=10^6)$的二进制字符串$S$,删除其中至少包含一个"1"的子字符串"01"和"00"后,计算长度与$S$相同的二进制字符串。
我们可以观察到,若一个子字符串中包含了'00'或'01',则它必然携带了一位无法被删除的'0'。因此,只要我们将$S$中的所有'1'截去,就可以将问题转化为从剩下的'0'中选出若干个构成字符串的问题。
我们考虑相邻两个'1'之间的'0'的个数,这些'0'中除掉'00'和'01'这两种子字符串,其余的可以直接看作是独立的'0'。因此,我们可以将他们看作是独立的物品,从中选出一个集合,使得集合中的物品的个数为$|S|-1$,并且集合中的物品从左到右的顺序与$S$中的'0'的顺序相同。
考虑使用记忆化搜索或动态规划解决这个问题。设$dp[i][j]$表示前$i$个'0'中选出$j$个的方案数,$cnt_i$表示第$i$个'0'与它后面的'1'之间的'0'的个数。
状态转移方程为:$$dp[i][j]=\begin{cases}dp[i-1][j],&cnt_i>j\dp[i-1][j]+\displaystyle \binom{j-cnt_i-1}{i-cnt_i-1},&cnt_i\leq j\end{cases}$$ 其中$\binom{a}{b}$表示组合数。
时间复杂度:$O(n^2)$
空间复杂度:$O(n^2)$
def count_str(s: str) -> int:
zeros = [i for i, c in enumerate(s) if c == '0']
n = len(zeros)
dp = [[0] * (n + 1) for _ in range(n + 1)]
dp[0][0] = 1
for i in range(1, n + 1):
cnt = zeros[i - 1] - zeros[i - 2] - 1 if i >= 2 else zeros[0]
for j in range(n + 1):
dp[i][j] = dp[i - 1][j]
if cnt <= j:
dp[i][j] += dp[i - 1][j - cnt] * comb(j - cnt - 1, i - cnt - 1)
return dp[n][n]