青训营X豆包 MarsCode AI|豆包MarsCode AI刷题

90 阅读6分钟

字典序最小的01字符串 | python解法

题目分析

问题描述

小U有一个由0和1组成的字符串,目标是通过最多 k 次相邻字符交换操作,使得最终字符串的字典序最小。我们需要计算在不超过k次交换的条件下,能够得到的字典序最小的字符串。

关键思路

此题的关键在于 贪心算法。目标是将字符串调整为字典序最小的形式,在允许的交换次数范围内尽量将字符串中的0移到前面,因为字典序中0比1小。因此,问题转化为:

  • 尽量将字符串中的0“提到”前面,通过相邻交换的方式进行调整。
  • 每次交换要尽量选取最有利于得到字典序最小结果的位置。

步骤和策略

  1. 贪心选择

    • 从字符串的左边开始,尝试将遇到的0移到最前面。
    • 每次尝试交换时,我们都需要选择距离当前0最远的1进行交换。这样做是为了减少交换次数,从而使得字典序尽可能小。
  2. 限制交换次数

    • 每次交换两个字符,需要检查是否超过了给定的最大交换次数k。如果超过了,就停止交换。
  3. 细节

    • 对于每一个位置,我们都可以寻找最靠近当前位置的0,并将其交换到当前位置。
    • 每次操作时,我们尽量把0移动到最前面,从而逐步构建出字典序最小的字符串。

伪代码

  1. 从字符串的左侧开始遍历每个位置。
  2. 对于当前位置,寻找最远的0,并判断是否可以交换。如果可以交换,就进行交换并更新剩余的交换次数。
  3. 如果交换次数用尽,停止操作。

代码

def solution(n: int, k: int, s: str) -> str:
    input_list = list(s)
    
    # 当前要交换的 '1' 和 '0' 的位置
    index_one = 0
    
    while k > 0 and index_one < n:
        # 找到当前 index_one 之后的第一个 '1'
        while index_one < n and input_list[index_one] != '1':
            index_one += 1
        
        if index_one >= n:  # 如果找不到 '1',说明结束
            break
        
        # 找到当前 '1' 后面的第一个 '0'
        index_zero = index_one + 1
        while index_zero < n and input_list[index_zero] != '0':
            index_zero += 1
        
        if index_zero >= n:  # 如果找不到 '0',说明结束
            break
        
        # 计算 '0' 和 '1' 之间的距离
        dist = index_zero - index_one
        
        # 如果剩余的交换次数足够,交换 '1' 和 '0'
        if k >= dist:
            k -= dist
            input_list[index_one], input_list[index_zero] = input_list[index_zero], input_list[index_one]
            index_one += 1  # 更新 index_one,继续查找下一个 '1'
        else:
            # 如果不能完全交换,则只能交换部分
            input_list[index_one + k], input_list[index_zero] = input_list[index_zero], input_list[index_one + k]
            break  # 完成所有可能的交换后退出
    
    return ''.join(input_list)

代码解释

  1. 初始化

    • 将输入字符串转化为列表 input_list,这样便于修改字符。
    • 初始化 index_one 为 0,用于指向当前需要交换的 1
  2. 主循环

    • k > 0index_one < n 时,继续进行交换。
    • index_one 用于找到当前未处理的第一个 1
    • 内部循环找到 index_one 后面的第一个 0,并计算它们之间的距离。
  3. 交换处理

    • 如果剩余的交换次数 k 足够,交换 10,并减少 k
    • 如果交换次数不足以交换两个字符,则进行部分交换,并终止。
  4. 输出结果

    • 最后,将列表 input_list 转换为字符串并返回。

时间复杂度分析

  • 外层循环:遍历字符串的每个字符,需要O(n)的时间。
  • 内层循环:对于每个字符,我们在剩下的字符中寻找最远的0,最坏情况下内层循环需要遍历剩下的所有字符,所以最坏情况下内层循环的时间复杂度为O(n)。
  • 因此,总的时间复杂度是 O(n^2)。

空间复杂度分析

  • 由于我们将字符串转换为列表进行修改,所以空间复杂度为 O(n),其中n是字符串的长度。

可优化思路

1. 避免重复查找 10

当前代码中,每次在字符串中查找第一个 1 和第一个 0,分别使用了两个循环。每次都重新从当前位置开始查找 10,这种操作是多余的,因为一旦交换了一个字符,10 的位置已经改变,继续重新查找将导致不必要的重复操作。

优化方案

  • 维护一个指向当前未交换的 1 的位置和未交换的 0 的位置,而不是每次都从头开始查找。
  • 一旦 10 交换后,就更新位置,避免重复查找。

2. 避免多次判断 index_oneindex_zero 是否越界

在当前实现中,我们不断判断 index_oneindex_zero 是否越界,但实际上,如果某个位置不满足条件(比如没有找到 10),我们可以立即跳出循环并返回结果。这避免了不必要的判断和循环。

优化方案

  • 可以通过提前判断是否还能进行有效交换,提前跳出循环,避免冗余的判断。

3. 减少不必要的字符交换

如果交换的距离 index_zero - index_one 小于等于 k,就进行交换,但如果 k 的值非常小或者无法完成交换,应该及时跳出循环,而不是进行部分交换。部分交换的操作会使得后续的逻辑更复杂。

优化方案

  • 可以直接判断是否能完全交换,而不是在交换过程中进行中断。

4. 提前判断是否还剩余足够的交换次数

在每次查找 10 时,我们可以直接判断当前剩余的交换次数是否足够。如果不够,可以提前结束,而不必再进行多次操作。

优化方案

  • 在每次查找 10 的时候,提前判断是否剩余足够的交换次数,若不够则直接跳出。

总结

  • 本题通过贪心策略,在允许的交换次数内尽量将0移到字符串的前面,最终得到字典序最小的字符串。
  • 由于采用了两层循环,时间复杂度为 O(n^2),适用于较小的输入规模。
  • 通过状态追踪和字符串交换的方式有效地求解了问题。