区分黑球与白球

标签: 贪心 双指针 字符串

难度: Medium

桌子上有 n 个球,每个球的颜色不是黑色,就是白色。

给你一个长度为 n 、下标从 0 开始的二进制字符串 s,其中 10 分别代表黑色和白色的球。

在每一步中,你可以选择两个相邻的球并交换它们。

返回「将所有黑色球都移到右侧,所有白色球都移到左侧所需的 最小步数」。

示例 1:

输入:s = "101"
输出:1
解释:我们可以按以下方式将所有黑色球移到右侧:
- 交换 s[0] 和 s[1],s = "011"。
最开始,1 没有都在右侧,需要至少 1 步将其移到右侧。

示例 2:

输入:s = "100"
输出:2
解释:我们可以按以下方式将所有黑色球移到右侧:
- 交换 s[0] 和 s[1],s = "010"。
- 交换 s[1] 和 s[2],s = "001"。
可以证明所需的最小步数为 2 。

示例 3:

输入:s = "0111"
输出:0
解释:所有黑色球都已经在右侧。

提示:

  • 1 <= n == s.length <= 105
  • s[i] 不是 '0',就是 '1'

Submission

运行时间: 60 ms

内存: 17.1 MB

class Solution:
    def minimumSteps(self, s: str) -> int:
        b_cnt = 0
        ans = 0
        for digit in s:
            if digit == '0':
                ans += b_cnt
            else:
                b_cnt += 1
        return ans

Explain

题解的思路是通过一次遍历来统计将所有白球移至左侧(或黑球移至右侧)所需的最小交换次数。具体实现为:初始化两个变量,`b_cnt` 用来记录至当前位置为止遇到的黑球数量,`ans` 用来累计总的交换次数。当遇到一个白球(`'0'`)时,意味着这个白球需要通过交换越过之前已经统计过的所有黑球,因此要将`b_cnt`(当前已经遇到的黑球数量)加到`ans`上。当遇到黑球(`'1'`)时,仅将`b_cnt`加一,表示又遇到了一个黑球,不需要额外操作。

时间复杂度: O(n)

空间复杂度: O(1)

class Solution:
    def minimumSteps(self, s: str) -> int:
        b_cnt = 0  # 统计黑球数量
        ans = 0    # 累计所需的最小交换次数
        for digit in s:  # 遍历字符串中每个字符
            if digit == '0':  # 如果当前是白球
                ans += b_cnt  # 移动这个白球需要越过所有已遇到的黑球
            else:  # 如果当前是黑球
                b_cnt += 1  # 增加黑球计数
        return ans  # 返回总的最小交换次数

Explore

在遇到白球时,需要将之前遇到的黑球数加到总交换次数中,是因为每个白球需要移动到所有先前遇到的黑球的左侧。这意味着每个白球需要越过之前统计的所有黑球,每越过一个黑球就需要进行一次交换。因此,当前白球的移动次数等于之前遇到的黑球数量。

在字符串s遍历过程中,累积的黑球数量b_cnt直接决定了每个遇到的白球需要进行的交换次数。具体来说,每当遇到一个白球时,其需要通过的交换次数正是当时已经累积的黑球数量b_cnt。因此,b_cnt越大,任何后续遇到的白球都需要更多的交换次数来移到所有黑球的左侧。

如果输入的字符串s中全部是黑球,那么没有白球需要移动,所以算法的输出结果将是0,不需要任何交换。同样的,如果全部是白球,也不需要进行任何交换,因为所有白球已经位于其应在的位置,输出结果也是0。这表明在这两种情况下,算法正确地识别出不需要进行任何交换。

该算法在处理非常长的字符串时表现良好,因为它只需要进行一次遍历,即具有O(n)的时间复杂度,其中n是字符串的长度。即便字符串长度达到百万级别,该算法仍然能以线性时间完成处理,保持高效。算法的空间复杂度也很低,只需要常数级的额外空间。因此,该算法适合处理长字符串,性能保持高效。