今天刷的是一道难度特别难的题目:字典序最小的01字符串
问题描述
小U拥有一个由0和1组成的字符串,她可以进行最多k次操作,每次操作可以交换相邻的两个字符。目标是通过这些操作,使得最终得到的字符串字典序最小。
例如,小U当前有一个字符串 01010,她最多可以进行 2 次相邻字符交换操作。通过这些操作,她可以将字符串调整为 00101,这是可以通过不超过2次操作得到的字典序最小的字符串。
现在,小U想知道,经过最多k次操作后,能够得到的字典序最小的字符串是什么。
测试样例
样例1:
输入:
n = 5, k = 2, s = "01010"
输出:'00101'
样例2:
输入:
n = 7, k = 3, s = "1101001"
输出:'0110101'
样例3:
输入:
n = 4, k = 1, s = "1001"
输出:'0101'
让我来详细说明这个问题的解决方案,包括背后的思路、操作步骤以及如何处理各类情况。我们的目标是将给定的字符串通过最多 k 次相邻字符交换操作,得到字典序最小的字符串。为了实现这个目标,我们将采取一个贪心的策略,通过逐步优化字符串来尽量减少字典序。
1. 问题分析
首先,我们要明确几个重要的概念:
-
字典序最小:字典序最小的字符串对于每一对字符来说,左边的字符要尽量小。因此,字符
0应该尽可能出现在字符串的前面,字符1应该尽量出现在字符串的后面。 -
操作次数限制:我们只能进行最多
k次相邻字符交换,每次操作只能交换两个相邻的字符。因此,每次操作都会把某个字符逐步交换到它的目标位置,并且消耗掉一定的操作次数。 -
贪心策略:为了使得字符串字典序最小,我们希望每次都尽量将当前能够交换到最前面的
0移到当前位置。我们将从左到右遍历字符串,逐步将字符0推到前面,以保证字典序最小。
2. 贪心策略的详细步骤
为了实现上述目标,贪心策略的关键点是:
- 从字符串的左边开始扫描,尽量将每个位置上的
1替换成0,但交换操作的次数不能超过k。 - 每当我们遇到一个
1时,我们检查其后面的位置,看看是否存在更小的字符(即0)。如果找到了一个可以交换的0,我们会将其逐步交换到当前1之前,并消耗掉相应的操作次数。
3. 具体操作步骤
下面详细描述算法的步骤:
1. 初始化
- 将字符串转换为一个列表
s,因为字符串是不可变的,而列表是可变的,便于操作。 - 设定一个变量
k来记录剩余的交换次数。
2. 从左到右遍历字符串
我们从左到右扫描字符串,并在每个位置尝试将尽可能小的字符(即 0)推到当前位置。如果当前位置的字符是 1,我们就寻找它后面的位置,找到第一个 0 并交换。
3. 找到最左边的 0
在当前位置之后查找第一个 0,如果这个 0 可以通过交换到当前位置(即交换次数不超过 k),就将它移动到当前位置。交换的时候,我们需要将这个 0 和它前面的字符逐一交换,每次交换都会消耗掉 1 次操作。
4. 更新剩余交换次数
每次交换后,更新剩余的操作次数 k,直到 k 为 0 或者没有可以交换的字符为止。
5. 停止条件
如果遍历完字符串或者剩余操作次数为 0,则停止。
6. 返回最终结果
将列表 s 转换回字符串并返回。
4. 算法实现
def min_lexicographic_string(n, k, s):
# 将字符串转为列表以便进行交换操作
s = list(s)
# 遍历字符串的每个位置
for i in range(n):
# 如果操作次数已经用完,直接退出
if k == 0:
break
# 当前字符是'1'时,我们想要找到一个'0'并将其交换到当前位置
if s[i] == '1':
# 在当前位置之后找到第一个'0'
for j in range(i + 1, n):
if s[j] == '0':
# 检查是否有足够的交换次数
if j - i <= k:
# 交换字符
for p in range(j, i, -1): # 从 j 到 i 交换
s[p], s[p - 1] = s[p - 1], s[p]
# 更新剩余操作次数
k -= (j - i)
break
# 将列表转换回字符串并返回
return ''.join(s)
# 测试样例
print(min_lexicographic_string(5, 2, "01010")) # 输出 '00101'
print(min_lexicographic_string(7, 3, "1101001")) # 输出 '0110101'
print(min_lexicographic_string(4, 1, "1001")) # 输出 '0101'
5. 详细步骤示例
示例 1:n = 5, k = 2, s = "01010"
- 初始字符串:
01010 - 剩余操作次数:
k = 2 - 从左到右遍历:
- 第 1 位是
0,不用做任何交换。 - 第 2 位是
1,找到第 3 位的0,可以交换,将0移动到第 2 位。剩余操作次数变为k = 1。 - 第 3 位是
1,找到第 5 位的0,可以交换,将0移动到第 3 位。剩余操作次数变为k = 0。
- 第 1 位是
- 最终字符串:
00101
示例 2:n = 7, k = 3, s = "1101001"
- 初始字符串:
1101001 - 剩余操作次数:
k = 3 - 从左到右遍历:
- 第 1 位是
1,找到第 2 位的0,交换,将0移到第 1 位。剩余操作次数k = 2。 - 第 2 位是
1,找到第 4 位的0,交换,将0移到第 2 位。剩余操作次数k = 1。 - 第 3 位是
1,找到第 5 位的0,交换,将0移到第 3 位。剩余操作次数k = 0。
- 第 1 位是
- 最终字符串:
0110101
6. 时间复杂度分析
-
时间复杂度:每次查找从当前位置到后面的字符的过程最多需要遍历一次,因此对于每个字符,我们最多做
O(n)次操作。因此总体时间复杂度为O(n^2),其中n是字符串的长度。 -
空间复杂度:我们将字符串转为列表,空间复杂度为
O(n)。
7. 总结
这个解法使用了贪心策略,从左到右遍历字符串,尽量将较小的字符(0)移动到较前的位置。通过每次交换相邻字符,使得字典序逐步优化。