题目解析:最小字典序字符串
题目描述了一个非常经典的字符串排序问题,要求我们通过最多 k
次相邻字符交换的操作,得到字符串字典序最小的排列。每次交换只能是相邻的字符,但操作次数有限,目标是将给定的字符串调整成字典序最小的形式。
1. 字典序最小的字符串
字典序最小的字符串,简单来说,就是将字符串中所有的字符按升序排列。对于由 0
和 1
组成的字符串来说,显然,所有的 0
应该尽量排在前面,所有的 1
排在后面。
例如,给定字符串 "01010"
,字典序最小的字符串应该是 "00101"
。如果没有操作限制,我们只需要将所有的 0
移到前面即可。
2. 交换操作的限制
在实际题目中,我们可以进行最多 k
次相邻字符交换操作。虽然目标是字典序最小,但我们不能无节制地交换所有字符。因此,我们需要在限制的交换次数内,尽可能地将字符排列为字典序最小。
思路分析
-
从左到右逐步调整:
- 我们从字符串的左端开始,尽量将每个位置上的字符调整为最小的字符(
0
),如果有足够的交换次数。 - 对于每个位置,寻找从当前位置到字符串末尾范围内的最小字符,如果这个最小字符的位置离当前字符不远,那么交换它到当前位置。
- 每次找到最小字符并交换到当前位置,都会消耗一定的交换次数,直到达到最大交换次数
k
或者遍历完整个字符串。
- 我们从字符串的左端开始,尽量将每个位置上的字符调整为最小的字符(
-
优先将
0
交换到前面:- 在寻找最小字符时,我们尽量选择
0
,因为0
在字典序中比1
小。
- 在寻找最小字符时,我们尽量选择
-
贪心策略:
- 每次寻找当前未排序部分中的最小字符并将其移到当前位置。这是一种典型的贪心策略,因为我们每次选择最小的字符,使得局部最优,从而可以接近全局最优。
算法步骤
- 从左到右遍历字符串。
- 对于每个位置
i
,检查剩余字符串中是否有更小的字符0
。 - 如果找到了
0
,且它的位置可以通过不超过剩余交换次数来移动到当前位置,就交换它。 - 每次交换时,更新剩余的交换次数
k
。 - 当交换次数用尽或字符串已经排好时,结束操作。
代码实现
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"
代码解析
-
字符串转为列表:我们将字符串
s
转为列表,因为在 Python 中,字符串是不可变的,而列表是可变的,因此更方便进行字符交换操作。 -
遍历字符串:我们从左到右遍历字符串,对于每个位置
i
,我们寻找当前位置后面最小的0
,并且确定是否能在剩余的交换次数内把这个0
移到当前的位置。 -
交换操作:如果找到了可以交换的
0
,我们通过逐步交换相邻的字符,把0
移到当前位置。每次交换都减少相应的操作次数k
。 -
停止条件:当
k
次操作已经用完或者字符串已经达到最小字典序时,停止操作。
复杂度分析
-
时间复杂度:
- 遍历字符串
s
需要O(n)
的时间,其中n
是字符串的长度。 - 对于每个位置,我们最多需要查找从当前位置到字符串末尾的字符,这个操作的最坏时间复杂度是
O(n)
。因此,总的时间复杂度是O(n^2)
。
- 遍历字符串
-
空间复杂度:
- 由于我们将字符串转化为列表来进行操作,因此空间复杂度为
O(n)
,其中n
是字符串的长度。
- 由于我们将字符串转化为列表来进行操作,因此空间复杂度为
测试用例解析
-
测试用例1:
- 输入:
n = 5, k = 2, s = "01010"
- 结果:
"00101"
- 解析:最初的字符串是
01010
,我们可以将第二个字符1
和第三个字符0
交换一次,然后再交换第三个字符1
和第四个字符0
,最终得到00101
。
- 输入:
-
测试用例2:
- 输入:
n = 7, k = 3, s = "1101001"
- 结果:
"0110101"
- 解析:我们可以通过最多三次操作,将第一个
1
和第二个0
交换一次,再将第二个1
和第三个0
交换,最后将第四个0
和第五个1
交换,得到最终的字典序最小的字符串0110101
。
- 输入:
-
测试用例3:
- 输入:
n = 4, k = 1, s = "1001"
- 结果:
"0101"
- 解析:只需要将第一个
1
和第二个0
交换一次,即可得到字典序最小的字符串0101
。
- 输入:
总结与建议
关键知识点:
- 贪心算法:通过每次选择最小的字符进行调整,确保得到字典序最小的字符串。
- 字符串处理技巧:将字符串转化为列表,便于修改字符。
- 操作次数限制:在贪心策略中,必须考虑到最大操作次数
k
的限制,不能无节制地交换字符。
对初学者的建议:
- 理解问题本质:本题的核心在于理解字典序最小字符串的构成方式,以及如何在有限的交换次数内达到目标。
- 多做练习:掌握贪心策略,理解如何在问题中应用贪心算法解决优化问题。
- 调试与优化:遇到复杂问题时,可以先写出简单的实现,再逐步进行优化和调试,直到能够在时间复杂度上做出权衡。
学习方法:
- 分阶段学习:先学习简单的字符串处理问题,再挑战更复杂的有操作限制的字符串问题。
- 总结经验:每完成一道题目,都要总结出自己的解决思路和算法,并对比其他人或平台的解法,找到优化空间。
通过以上的方法,不仅可以解决本题,也能为今后遇到类似问题积累经验,掌握高效的算法设计技巧。