LeetCode 2712: 使所有字符相等的最小成本 - 贪心算法解析
问题描述
一个二进制字符串 s
,需要通过一系列操作使字符串中的所有字符相等(全为 '0' 或全为 '1')。可执行的操作有:
- 选择一个下标
i
,反转从下标 0 到下标i
的所有字符(包括下标 0 和i
),成本为i + 1
- 选择一个下标
i
,反转从下标i
到下标n - 1
的所有字符(包括下标i
和n - 1
),成本为n - i
需要计算使所有字符相等所需的最小成本。
贪心算法思路
本题可以使用贪心算法高效求解。关键洞察是:对于字符串中每一对相邻但不相等的字符,必须执行一次操作使它们相等。
贪心策略分析
考虑字符串中的每一对相邻字符 s[i]
和 s[i+1]
(其中 i
从 0 到 n-2
):
- 如果
s[i] == s[i+1]
,这对字符已经相等,不需要操作 - 如果
s[i] != s[i+1]
,必须执行一次操作使它们相等。有两种选择:- 反转前缀
[0...i]
:成本为i + 1
- 反转后缀
[i+1...n-1]
:成本为n - (i+1)
=n - i - 1
- 反转前缀
贪心选择是:选择成本较小的那个操作 ,即 min(i + 1, n - i - 1)
。
为什么贪心算法有效?
可能会有疑问:前面的操作会影响后面的字符,为什么简单地对每个不同的相邻字符对独立做决策是正确的?
关键在于理解:
- 每次操作只会影响连续的一段子串中的字符
- 对于每对相邻不同的字符,必须通过某种方式使它们相等
- 无论选择反转前缀还是后缀,最终目标都是使所有字符相等
- 贪心策略确保了每次都选择成本最小的操作
局部最优的选择在这个问题中确实能导致全局最优解,因为:
- 每对相邻但不同的字符都至少需要一次操作
- 的操作不会创造新的不同相邻字符对
- 即使前面的操作改变了后面的字符,仍然只关心相邻字符是否相同
代码实现
python
class Solution:
def minimumCost(self, s: str) -> int:
n = len(s)
total_cost = 0
for i in range(n-1):
if s[i] != s[i+1]:
# 选择反转前缀或后缀的最小成本
total_cost += min(i+1, n-i-1)
return total_cost
复杂度分析
- 时间复杂度:O(n),只需要遍历字符串一次,对每对相邻字符做一次简单比较和计算。
- 空间复杂度:O(1),只使用了几个变量,不随输入规模增长。
示例详解
通过一个例子来理解算法的执行过程。以输入 s = "010101"
为例:
- 长度 n = 6
- 遍历字符串的相邻字符对:
- i=0: s[0]=0, s[1]=1,不相等,选择 min(0+1, 6-0-1) = min(1, 5) = 1,累计成本 = 1
- i=1: s[1]=1, s[2]=0,不相等,选择 min(1+1, 6-1-1) = min(2, 4) = 2,累计成本 = 1+2 = 3
- i=2: s[2]=0, s[3]=1,不相等,选择 min(2+1, 6-2-1) = min(3, 3) = 3,累计成本 = 3+3 = 6
- i=3: s[3]=1, s[4]=0,不相等,选择 min(3+1, 6-3-1) = min(4, 2) = 2,累计成本 = 6+2 = 8
- i=4: s[4]=0, s[5]=1,不相等,选择 min(4+1, 6-4-1) = min(5, 1) = 1,累计成本 = 8+1 = 9
- 最终结果是 9
这与示例中给出的期望输出相符。
总结
贪心算法是解决这个问题的最佳方法:
- 遍历字符串中的每对相邻字符
- 对于不相等的相邻字符对,选择成本最小的操作(反转前缀或后缀)
- 累加所有操作的成本得到最终结果
这个解法的关键在于识别出局部最优选择在这个问题中能够导致全局最优解。贪心策略在这里非常有效,因为操作不会增加新的不同相邻字符对的数量,而且每一对不同的相邻字符对都至少需要一次操作来解决。