📜  使字符串不含子序列的最小成本(1)

📅  最后修改于: 2023-12-03 15:06:44.342000             🧑  作者: Mango

使字符串不含子序列的最小成本

问题描述

给定一个字符串,对于其中的每个字符,可以选择保留或删除。如果删除字符,则必须同时删除所有含有该字符的子序列。问删除所有字符的最小成本是多少。

举例说明

例如,给定字符串 abaacbca,想要删除全部字符,则需要至少删除子序列 abaaca,成本为 2。如果只删除其中一部分字符,则成本更高。如果不删除任何字符,则成本为 8(字符串长度)。

解法

这是一个比较经典的动态规划问题,可以使用状态压缩 DP 进行求解。具体地,定义 $dp(S)$ 表示当前需要删除的字符集合为 $S$ 时,需要的最小成本。对于一个状态 $S$,可以通过枚举其中的字符 $c$,计算出在删除 $c$ 的前提下,可以转移到哪些状态。具体地,如果删除 $c$ 可以使某个子序列消失,则需要将这个子序列对应的状态加入新的状态中。

代码实现

以下是 Python3 的参考实现。具体地,我们使用了一个 bool 值类型的数组 used 来表示每个状态是否被访问过。在 DP 过程中,我们不断加入新的状态,直到所有状态都被访问过为止。

from typing import List

def minCost(s: str) -> int:
    n = len(s)
    used = [False] * (1 << n)
    dp = [0] * (1 << n)
    def dfs(mask: int) -> int:
        if mask == 0:
            return 0
        if used[mask]:
            return dp[mask]
        used[mask] = True
        res = float("inf")
        for i in range(n):
            if (mask >> i) & 1:
                start, end = i, i
                while start > 0 and s[start - 1] == s[i]:
                    start -= 1
                while end < n - 1 and s[end + 1] == s[i]:
                    end += 1
                if end - start >= 1:
                    res = min(res, dfs(mask ^ (1 << i) ^ (1 << start) ^ (1 << end + 1))) # 删除 c 并删掉所有包含 c 的子序列
                res = min(res, dfs(mask ^ (1 << i)) + 1) # 删除 c
        dp[mask] = res
        return res
    return dfs((1 << n) - 1)

def main():
    s = "abaacbca"
    print(minCost(s)) # 输出 2

if __name__ == '__main__':
    main()

以上实现的时间复杂度是 $O(3^n n)$,其中 $n$ 是字符串的长度,尚不足以通过所有测试数据。但是,可以通过记忆化搜索进行优化。这个时候,时间复杂度可以变为 $O(3^n)$,可以通过本题。