字典序最小的01字符串 | python解法
题目分析
问题描述
小U有一个由0和1组成的字符串,目标是通过最多 k 次相邻字符交换操作,使得最终字符串的字典序最小。我们需要计算在不超过k次交换的条件下,能够得到的字典序最小的字符串。
关键思路
此题的关键在于 贪心算法。目标是将字符串调整为字典序最小的形式,在允许的交换次数范围内尽量将字符串中的0移到前面,因为字典序中0比1小。因此,问题转化为:
- 尽量将字符串中的0“提到”前面,通过相邻交换的方式进行调整。
- 每次交换要尽量选取最有利于得到字典序最小结果的位置。
步骤和策略
-
贪心选择:
- 从字符串的左边开始,尝试将遇到的0移到最前面。
- 每次尝试交换时,我们都需要选择距离当前0最远的1进行交换。这样做是为了减少交换次数,从而使得字典序尽可能小。
-
限制交换次数:
- 每次交换两个字符,需要检查是否超过了给定的最大交换次数k。如果超过了,就停止交换。
-
细节:
- 对于每一个位置,我们都可以寻找最靠近当前位置的0,并将其交换到当前位置。
- 每次操作时,我们尽量把0移动到最前面,从而逐步构建出字典序最小的字符串。
伪代码
- 从字符串的左侧开始遍历每个位置。
- 对于当前位置,寻找最远的0,并判断是否可以交换。如果可以交换,就进行交换并更新剩余的交换次数。
- 如果交换次数用尽,停止操作。
代码
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)
代码解释
-
初始化:
- 将输入字符串转化为列表
input_list,这样便于修改字符。 - 初始化
index_one为 0,用于指向当前需要交换的1。
- 将输入字符串转化为列表
-
主循环:
- 在
k > 0且index_one < n时,继续进行交换。 index_one用于找到当前未处理的第一个1。- 内部循环找到
index_one后面的第一个0,并计算它们之间的距离。
- 在
-
交换处理:
- 如果剩余的交换次数
k足够,交换1和0,并减少k。 - 如果交换次数不足以交换两个字符,则进行部分交换,并终止。
- 如果剩余的交换次数
-
输出结果:
- 最后,将列表
input_list转换为字符串并返回。
- 最后,将列表
时间复杂度分析
- 外层循环:遍历字符串的每个字符,需要O(n)的时间。
- 内层循环:对于每个字符,我们在剩下的字符中寻找最远的0,最坏情况下内层循环需要遍历剩下的所有字符,所以最坏情况下内层循环的时间复杂度为O(n)。
- 因此,总的时间复杂度是 O(n^2)。
空间复杂度分析
- 由于我们将字符串转换为列表进行修改,所以空间复杂度为 O(n),其中n是字符串的长度。
可优化思路
1. 避免重复查找 1 和 0
当前代码中,每次在字符串中查找第一个 1 和第一个 0,分别使用了两个循环。每次都重新从当前位置开始查找 1 和 0,这种操作是多余的,因为一旦交换了一个字符,1 和 0 的位置已经改变,继续重新查找将导致不必要的重复操作。
优化方案:
- 维护一个指向当前未交换的
1的位置和未交换的0的位置,而不是每次都从头开始查找。 - 一旦
1和0交换后,就更新位置,避免重复查找。
2. 避免多次判断 index_one 和 index_zero 是否越界
在当前实现中,我们不断判断 index_one 和 index_zero 是否越界,但实际上,如果某个位置不满足条件(比如没有找到 1 或 0),我们可以立即跳出循环并返回结果。这避免了不必要的判断和循环。
优化方案:
- 可以通过提前判断是否还能进行有效交换,提前跳出循环,避免冗余的判断。
3. 减少不必要的字符交换
如果交换的距离 index_zero - index_one 小于等于 k,就进行交换,但如果 k 的值非常小或者无法完成交换,应该及时跳出循环,而不是进行部分交换。部分交换的操作会使得后续的逻辑更复杂。
优化方案:
- 可以直接判断是否能完全交换,而不是在交换过程中进行中断。
4. 提前判断是否还剩余足够的交换次数
在每次查找 1 和 0 时,我们可以直接判断当前剩余的交换次数是否足够。如果不够,可以提前结束,而不必再进行多次操作。
优化方案:
- 在每次查找
1和0的时候,提前判断是否剩余足够的交换次数,若不够则直接跳出。
总结
- 本题通过贪心策略,在允许的交换次数内尽量将0移到字符串的前面,最终得到字典序最小的字符串。
- 由于采用了两层循环,时间复杂度为 O(n^2),适用于较小的输入规模。
- 通过状态追踪和字符串交换的方式有效地求解了问题。