刷题笔记-字典序最小的二进制字符串 | 豆包MarsCode AI刷题

95 阅读4分钟

字典序最小的二进制字符串的求解

问题描述

给定一个长度为 n 的仅由字符 '0''1' 组成的字符串 s,你可以进行最多 k 次操作。每次操作可以交换相邻的两个字符。目标是通过这些操作,使得最终得到的字符串字典序最小。

举例说明

  • 输入:n = 5, k = 2, s = "01010"
  • 输出:"00101"

在这个例子中,通过最多 2 次相邻字符交换,可以将字符串调整为 "00101",这是可以得到的字典序最小的字符串。

问题分析

要在有限的操作次数内使二进制字符串的字典序最小,需要尽可能将 '0' 移动到字符串的左侧。由于每次只能交换相邻字符,每次将 '0' 向左移动一位需要一次操作。因此,我们需要在操作次数限制内,尽可能多地将 '0' 移动到左边。

关键点

  1. 交换相邻字符的代价:将位于位置 i'0' 移动到位置 jj < i)需要 i - j 次交换。

  2. 贪心策略:每次尽可能将当前的 '0' 移动到最左边,直到操作次数耗尽。

  3. 操作限制:由于操作次数有限,我们需要在不超过 k 次操作的情况下,安排 '0' 的移动。

解题思路

基于以上分析,我们可以采用以下贪心算法:

  • 初始化

    • 创建一个结果数组 res,用于构建最终的字符串。
    • 设置变量 write_pos,表示下一个可以放置 '0' 的位置。
    • 记录剩余的操作次数 k_remaining
  • 遍历字符串

    • 对于每个字符,如果是 '0'

      • 计算该 '0' 可以向左移动的最大步数:

        moves_possible = min(k_remaining, index - write_pos)
        
        • index 是当前 '0' 在原字符串中的位置。
        • write_pos 是当前可以放置 '0' 的最左位置。
      • 更新 '0' 的新位置:

        new_pos = index - moves_possible
        
      • '0' 放置在 resnew_pos 位置。

      • 更新剩余的操作次数:

        k_remaining -= moves_possible
        
      • 更新下一个 '0' 可以放置的位置:

        write_pos += 1
        
    • 如果是 '1',暂时跳过,稍后再填充。

  • 填充 '1'

    • 在遍历完成后,将 res 中未被填充的位置用 '1' 补上。
  • 生成结果字符串

    • res 转换为字符串并返回。

算法实现

def solution(n: int, k: int, s: str) -> str:
    res = [None] * n  # 结果数组
    write_pos = 0     # 下一个可放置 '0' 的位置
    k_remaining = k   # 剩余操作次数

    for index in range(n):
        if s[index] == '0':
            # 计算当前 '0' 可以移动的最大步数
            moves_possible = min(k_remaining, index - write_pos)
            new_pos = index - moves_possible
            res[new_pos] = '0'
            k_remaining -= moves_possible
            write_pos += 1  # 下一个 '0' 的放置位置右移
        # 对于 '1',暂不处理

    # 填充剩余位置为 '1'
    for i in range(n):
        if res[i] is None:
            res[i] = '1'

    return ''.join(res)

if __name__ == '__main__':
    print(solution(5, 2, "01010") == '00101')    # 输出 True
    print(solution(7, 3, "1101001") == '0110101')  # 输出 True
    print(solution(4, 1, "1001") == '0101')     # 输出 True

复杂度分析

  • 时间复杂度:O(n),其中 n 是字符串的长度。遍历字符串和结果数组各一次。

  • 空间复杂度:O(n),需要额外的数组 res 存储结果。

正确性证明

  • 贪心选择性质

    在每一步,我们都将当前的 '0' 尽可能向左移动,这样可以保证在当前情况下字典序最小。

  • 全局最优性

    由于每个 '0' 都尽可能向左移动,在操作次数允许的范围内,没有其他策略能比这个策略得到字典序更小的字符串。

  • 操作次数限制

    通过 k_remaining 确保总的交换次数不超过 k

总结

这道题要求在有限次数的相邻交换操作下,得到字典序最小的字符串。通过贪心算法,我们每次将 '0' 尽可能向左移动,直到操作次数耗尽。该算法充分利用了交换相邻字符的特点,操作简单,时间效率高,适用于大规模数据。

扩展思考

  • 如果字符集扩大

    如果字符串中包含多个不同的字符,比如小写字母,那么需要采用更复杂的算法,如优先队列或树状数组。

  • 如果操作不限于相邻交换

    如果允许交换任意位置的字符,那么可以直接对字符串排序。

  • 优化空间复杂度

    可以尝试在原字符串上进行修改,减少空间开销。