字典序最小的01字符串

4 阅读5分钟

题目背景

小U的目标是通过交换相邻的字符,将一个仅包含0和1的字符串调整为字典序最小的形式。允许的交换次数为 k,每次交换只能改变相邻字符的位置。最终需要在这些限制条件下,输出经过最多 k 次操作后可以得到的字典序最小字符串。

这道题的核心是如何在有限次操作中尽可能优化字符串的字典序。通过分析和模拟,可以发现交换策略直接影响最终结果。


问题拆解与思路分析

1. 字典序与交换操作的关系

字典序最小的字符串,就是尽量将所有的 0 移动到前面。在字符串中,01 的优先级高,越靠左的位置对最终的字典序贡献越大。

交换约束: 每次只能交换相邻字符,这意味着想将某个 0 移动到前面,必须逐步跨越所有阻挡它的 1。每次跨越耗费一次操作。

2. 贪心策略

贪心思想适用于这道题,因为我们总是优先考虑将距离左边最近的 0 移到靠前的位置。具体实现如下:

  • 从左到右扫描字符串,发现 0 时,计算它需要多少步能够移动到当前允许范围内的最左位置。
  • 如果步数小于等于 k,就将其移动到目标位置,并减少对应的操作次数。
  • 如果步数大于 k,则停止对当前 0 的移动,并继续检查后续的字符。
3. 模拟移动的关键点

在贪心策略中,模拟移动时需要注意以下几点:

  1. 记录交换后的字符位置:移动 0 时,其目标位置是基于当前剩余操作数 k 和阻挡它的 1 的位置动态决定的。
  2. 更新剩余操作数:每次实际完成一次交换后,都要减少对应的操作次数。
  3. 终止条件:当剩余操作数用尽时,停止所有移动。

实现步骤与伪代码

  1. 初始化一个字符数组 s_list,方便对字符串 s 进行就地修改。
  2. 使用指针 i 从左到右遍历字符串,寻找每个 0 的可移动范围。
  3. 使用内部循环,模拟将 0 逐步交换到目标位置。
  4. 实时更新剩余操作数 k,并在 k 用尽时终止。
  5. 最后,将字符数组重新转换为字符串输出。

伪代码如下:

function find_min_lexicographical_string(n, k, s):
    s_list = list(s)  # 将字符串转为字符数组
    i = 0

    while i < n and k > 0:
        if s_list[i] == '0':
            # 找到前方阻碍的1,并计算可交换次数
            steps = 0
            while steps < k and i - steps > 0 and s_list[i - steps - 1] == '1':
                steps += 1

            # 移动当前的0到目标位置
            if steps > 0:
                s_list[i], s_list[i - steps] = s_list[i - steps], s_list[i]
                k -= steps
        i += 1

    return ''.join(s_list)

代码实现与详细讲解

下面是完整的Python代码及其解释。

# Python实现:
def solution(n, k, s):
    s_list = list(s)  # 将字符串转换为可修改的字符列表

    for i in range(n):
        if s_list[i] == '0':
            # 计算当前的0能否移动以及移动的步数
            steps = 0

            while steps < k and i - steps > 0 and s_list[i - steps - 1] == '1':
                steps += 1

            # 交换位置,将0前移
            if steps > 0:
                s_list[i], s_list[i - steps] = s_list[i - steps], s_list[i]
                k -= steps

    return ''.join(s_list)

# 测试样例
print(solution(5, 2, "01010"))  # 输出: '00101'
print(solution(7, 3, "1101001"))  # 输出: '0110101'
print(solution(4, 1, "1001"))  # 输出: '0101'
代码核心逻辑拆解
  1. 字符串转列表:由于Python的字符串是不可变的,先将字符串转换为列表,便于就地修改。
  2. 双层循环:外层循环遍历每个字符,内层循环计算当前 0 的移动步数。
  3. 边界处理:保证移动时不会越界,并且不会超过允许的最大操作次数 k
  4. 交换实现:通过简单的交换操作,将 0 向前移动到目标位置。

复杂度分析

  1. 时间复杂度

    • 外层循环最多遍历字符串长度 n
    • 内层循环在最坏情况下需要遍历所有的 1,其次数受到剩余操作数 k 的限制。
    • 因此,总体时间复杂度为 O(n+k)O(n + k)
  2. 空间复杂度

    • 使用了一个字符数组 s_list,其长度为 O(n)O(n)
    • 总体空间复杂度为 O(n)O(n)

优化与改进思考

  • 减少重复操作:可以通过记录每次交换后字符的位置,避免重新扫描。
  • 适配更大的字符串:对于非常长的字符串,结合双指针技术可以进一步优化效率。
  • 多字符支持:虽然题目中仅涉及 01,但类似问题可以扩展到更复杂的字符集合,通过构建优先级规则实现。

思考

通过这道题目,可以深刻理解贪心算法的应用场景。贪心算法的核心在于每次选择局部最优解,同时确保全局最优性。解决类似问题时,需要灵活运用数据结构(如数组或队列),并在模拟过程中优化操作顺序。