“最小字典序字符串”| 豆包MarsCode AI刷题

13 阅读6分钟

题目解析:最小字典序字符串

题目描述了一个非常经典的字符串排序问题,要求我们通过最多 k 次相邻字符交换的操作,得到字符串字典序最小的排列。每次交换只能是相邻的字符,但操作次数有限,目标是将给定的字符串调整成字典序最小的形式。

1. 字典序最小的字符串

字典序最小的字符串,简单来说,就是将字符串中所有的字符按升序排列。对于由 01 组成的字符串来说,显然,所有的 0 应该尽量排在前面,所有的 1 排在后面。

例如,给定字符串 "01010",字典序最小的字符串应该是 "00101"。如果没有操作限制,我们只需要将所有的 0 移到前面即可。

2. 交换操作的限制

在实际题目中,我们可以进行最多 k 次相邻字符交换操作。虽然目标是字典序最小,但我们不能无节制地交换所有字符。因此,我们需要在限制的交换次数内,尽可能地将字符排列为字典序最小。

思路分析

  1. 从左到右逐步调整

    • 我们从字符串的左端开始,尽量将每个位置上的字符调整为最小的字符(0),如果有足够的交换次数。
    • 对于每个位置,寻找从当前位置到字符串末尾范围内的最小字符,如果这个最小字符的位置离当前字符不远,那么交换它到当前位置。
    • 每次找到最小字符并交换到当前位置,都会消耗一定的交换次数,直到达到最大交换次数 k 或者遍历完整个字符串。
  2. 优先将0交换到前面

    • 在寻找最小字符时,我们尽量选择 0,因为 0 在字典序中比 1 小。
  3. 贪心策略

    • 每次寻找当前未排序部分中的最小字符并将其移到当前位置。这是一种典型的贪心策略,因为我们每次选择最小的字符,使得局部最优,从而可以接近全局最优。

算法步骤

  1. 从左到右遍历字符串。
  2. 对于每个位置 i,检查剩余字符串中是否有更小的字符 0
  3. 如果找到了 0,且它的位置可以通过不超过剩余交换次数来移动到当前位置,就交换它。
  4. 每次交换时,更新剩余的交换次数 k
  5. 当交换次数用尽或字符串已经排好时,结束操作。

代码实现

def solution(n: int, k: int, s: str) -> str:
    # 将字符串转化为列表方便交换
    s = list(s)
    
    # 从左到右遍历
    for i in range(n):
        # 如果剩余的操作次数为0,则退出
        if k <= 0:
            break
        
        # 寻找当前位置后面的最小的0
        min_pos = -1
        for j in range(i + 1, n):
            if s[j] == '0':
                min_pos = j
                break
        
        # 如果找到了0,并且它可以通过k次交换达到位置i
        if min_pos != -1 and min_pos - i <= k:
            # 通过交换将0移到位置i
            for j in range(min_pos, i, -1):
                s[j], s[j - 1] = s[j - 1], s[j]
            k -= (min_pos - i)  # 减去消耗的操作次数
    
    return ''.join(s)

# 测试用例
print(solution(5, 2, "01010"))  # 输出 "00101"
print(solution(7, 3, "1101001"))  # 输出 "0110101"
print(solution(4, 1, "1001"))  # 输出 "0101"

代码解析

  1. 字符串转为列表:我们将字符串 s 转为列表,因为在 Python 中,字符串是不可变的,而列表是可变的,因此更方便进行字符交换操作。

  2. 遍历字符串:我们从左到右遍历字符串,对于每个位置 i,我们寻找当前位置后面最小的 0,并且确定是否能在剩余的交换次数内把这个 0 移到当前的位置。

  3. 交换操作:如果找到了可以交换的 0,我们通过逐步交换相邻的字符,把 0 移到当前位置。每次交换都减少相应的操作次数 k

  4. 停止条件:当 k 次操作已经用完或者字符串已经达到最小字典序时,停止操作。

复杂度分析

  • 时间复杂度

    • 遍历字符串 s 需要 O(n) 的时间,其中 n 是字符串的长度。
    • 对于每个位置,我们最多需要查找从当前位置到字符串末尾的字符,这个操作的最坏时间复杂度是 O(n)。因此,总的时间复杂度是 O(n^2)
  • 空间复杂度

    • 由于我们将字符串转化为列表来进行操作,因此空间复杂度为 O(n),其中 n 是字符串的长度。

测试用例解析

  1. 测试用例1

    • 输入:n = 5, k = 2, s = "01010"
    • 结果:"00101"
    • 解析:最初的字符串是 01010,我们可以将第二个字符 1 和第三个字符 0 交换一次,然后再交换第三个字符 1 和第四个字符 0,最终得到 00101
  2. 测试用例2

    • 输入:n = 7, k = 3, s = "1101001"
    • 结果:"0110101"
    • 解析:我们可以通过最多三次操作,将第一个 1 和第二个 0 交换一次,再将第二个 1 和第三个 0 交换,最后将第四个 0 和第五个 1 交换,得到最终的字典序最小的字符串 0110101
  3. 测试用例3

    • 输入:n = 4, k = 1, s = "1001"
    • 结果:"0101"
    • 解析:只需要将第一个 1 和第二个 0 交换一次,即可得到字典序最小的字符串 0101

总结与建议

关键知识点:

  1. 贪心算法:通过每次选择最小的字符进行调整,确保得到字典序最小的字符串。
  2. 字符串处理技巧:将字符串转化为列表,便于修改字符。
  3. 操作次数限制:在贪心策略中,必须考虑到最大操作次数 k 的限制,不能无节制地交换字符。

对初学者的建议:

  1. 理解问题本质:本题的核心在于理解字典序最小字符串的构成方式,以及如何在有限的交换次数内达到目标。
  2. 多做练习:掌握贪心策略,理解如何在问题中应用贪心算法解决优化问题。
  3. 调试与优化:遇到复杂问题时,可以先写出简单的实现,再逐步进行优化和调试,直到能够在时间复杂度上做出权衡。

学习方法:

  1. 分阶段学习:先学习简单的字符串处理问题,再挑战更复杂的有操作限制的字符串问题。
  2. 总结经验:每完成一道题目,都要总结出自己的解决思路和算法,并对比其他人或平台的解法,找到优化空间。

通过以上的方法,不仅可以解决本题,也能为今后遇到类似问题积累经验,掌握高效的算法设计技巧。