问题描述
小 U 和 小 R 各自拥有一个长度相等的二进制字符串 A 和 B。现在,他们想要将这两个字符串修改成相同的字符串。每次修改可以选择以下两种操作:
- 交换同一个字符串中的任意两个字符,交换操作的成本为它们索引之差的绝对值
|i - j|。 - 对某个字符进行取反操作,取反的成本为
2。
小 U 和 小 R 想知道,将字符串 A 和 B 修改为相同字符串的最小总成本是多少?
思路与方法
贪心操作
为了最小化修改成本,我们可以只操作 A 中与 B 不同的元素使成本最小。首先,我们证明可以只操作A中的元素,事实上,如果我们对B进行了,某项操作,我们将这项操作改为对A操作,由于0和1的对称性,A和B最终仍然会相同。
下面我们再证明可以只操作不同的元素,这一点可以通过反证法证明:如果我们对某个A、B中相同的字符进行了操作,由于取反使无意义的,这个操作只可能是交换操作,且交换的对象应该是A、B中不同的字符。同时,它们应该是相邻的,否则直接对另一个元素取反的代价更小。但交换后我们会发现,不同的字符串数并没有减少,我们可以省略这一步操作。
动态规划
基于上面贪心操作的分析,我们可以构建一个列表 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]
代码时间复杂度为,其中 n 是字符串的长度。我们只需要遍历一次字符串来找出不同的位置,并对每个位置进行动态规划计算,因此时间复杂度是线性的。