字符串趋同最小代价问题题解 | 豆包MarsCode AI刷题

51 阅读3分钟

问题描述

小 U 和 小 R 各自拥有一个长度相等的二进制字符串 AB。现在,他们想要将这两个字符串修改成相同的字符串。每次修改可以选择以下两种操作:

  1. 交换同一个字符串中的任意两个字符,交换操作的成本为它们索引之差的绝对值 |i - j|
  2. 对某个字符进行取反操作,取反的成本为 2

小 U 和 小 R 想知道,将字符串 AB 修改为相同字符串的最小总成本是多少?

思路与方法

贪心操作

为了最小化修改成本,我们可以只操作 A 中与 B 不同的元素使成本最小。首先,我们证明可以只操作A中的元素,事实上,如果我们对B进行了,某项操作,我们将这项操作改为对A操作,由于0和1的对称性,A和B最终仍然会相同。

下面我们再证明可以只操作不同的元素,这一点可以通过反证法证明:如果我们对某个AB中相同的字符进行了操作,由于取反使无意义的,这个操作只可能是交换操作,且交换的对象应该是AB中不同的字符。同时,它们应该是相邻的,否则直接对另一个元素取反的代价更小。但交换后我们会发现,不同的字符串数并没有减少,我们可以省略这一步操作。

动态规划

基于上面贪心操作的分析,我们可以构建一个列表 diff_id 来记录 A 和 B 中不同字符的位置索引。这一步是非常关键的,因为我们只需要关注这些位置,从而简化了问题的复杂度。

下面我们来考虑如何进行状态转移,设dp[i]表示处理包含前i个不同字符索引所需的最少操作数,显然,初始条件dp[0]=0

如果对第i个元素进行取反操作,则有dp[i]=dp[i-1]+2

接下来我们来考虑交换操作,如果和前一个A、B不同的下标对应的元素交换,这就要求这两处下标对应的数不同,且对应的成本为dp[i-2]+diff_id[i-1]-diff_id[i-2])

那么,是否有可能和更前面的元素进行交换呢?乍一想是这样会带来更高的成本,但实际上,测试样例给了我们一种可能的情况,即A='1100',B='0011',此时交换(1,3)和(2,4)可以花费最小的成本。

由于交换距离超过3后,使用取反的收益总是不低于交换的收益,因此我们只用考虑上面两种情况——相邻的不同diff_id下标进行交换,或连续四个diff_id下标进行交叉交换。

代码

def solution(str1, str2):
    # Edit your code here
    n=len(str1)
    diff_id=[]
    for i in range(n):
        if str1[i]!=str2[i]:
            diff_id.append(i)
    m=len(diff_id)
    if m==0:
        return 0
    dp=[0]*(m+1)
    dp[1]=2

    for i in range(2,m+1):
        dp[i]=dp[i-1]+2
        if str1[diff_id[i-2]]!=str1[diff_id[i-1]]:
            dp[i]=min(dp[i],dp[i-2]+diff_id[i-1]-diff_id[i-2])
        if i>=4 and str1[diff_id[i-3]]!=str1[diff_id[i-1]] and str1[diff_id[i-4]]!=str1[diff_id[i-2]]:
            dp[i]=min(dp[i],dp[i-4]+diff_id[i-1]+diff_id[i-2]-diff_id[i-3]-diff_id[i-4])
    return dp[m]

代码时间复杂度为O(n)O(n),其中 n 是字符串的长度。我们只需要遍历一次字符串来找出不同的位置,并对每个位置进行动态规划计算,因此时间复杂度是线性的。