问题描述
小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 次的相邻字符交换,使得一个由 '0' 和 '1' 组成的字符串 s 的字典序尽可能小。字典序的比较本质上是从左到右逐字符比较,找到第一个不同的字符后即可判定较小者。为了实现字典序最小化,我们的核心思路是尽可能将 '0' 提前。
解题思路
将问题细化为以下几个步骤:
- 核心交换策略:
'0' 的优先级高于 '1',因此每当遇到 '0' 时,尝试将它向左移动,直到用尽 kk\ 次交换或无法进一步移动为止。 - 约束分析:
每次交换相邻字符会减少 k 的次数,同时只能将当前 '0' 左移一位。因此,移动次数受限于当前 '0' 左边的 '1' 的数量 cnt 以及剩余交换次数 k。
若 ,可以将当前 '0' 完全移动至其左边所有 '1' 的前方;
若 ,只能部分移动,通过调整最终达到最佳效果。 - 逐步构造结果:
遍历字符串时,动态记录左侧累计的 '1' 的数量 cntcnt,并按条件决定当前字符是插入结果字符串的何处。最终的结果字符串是通过模拟移动过程构造的。
代码解析
以下是提供代码的逐步解释:
def solution(n: int, k: int, s: str) -> str:
cnt, flag = 0, 0 # cnt 记录遇到的 '1' 的数量,flag 表示是否已经进入不再调整的阶段
ans = [] # 存储最终结果字符串
for i in s:
if flag: # 如果已经进入无法继续调整的阶段,直接将字符加入结果
ans.append(i)
elif i == '1': # 当前字符为 '1',累加计数器
cnt += 1
elif k >= cnt: # 当前字符为 '0',且可以完全移到所有 '1' 的前面
ans.append('0')
k -= cnt # 减少交换次数
else: # 当前字符为 '0',但 ( k < cnt ),无法完全移动
flag = 1 # 设置标志位,表示从此刻开始所有字符直接拼接
# 将前 cnt-k 个 '1' 拼接到结果中
for idx in range(cnt - k):
ans.append('1')
ans.append('0') # 插入当前的 '0'
# 剩余的 ( k ) 个 '1' 直接拼接
for idx in range(k):
ans.append('1')
cnt = 0 # 重置计数器
k = 0 # 用完所有操作次数
# 最后将剩余的 '1' 补充到结果中
for idx in range(cnt):
ans.append('1')
return "".join(ans)
复杂度分析
-
时间复杂度:
- 遍历字符串 s 一次,时间复杂度为 O(n)。
- 每次移动 '0' 时的拼接操作为常数时间,整体复杂度仍为O(n)。
综合复杂度:O(n)。
-
空间复杂度:
- 使用一个额外数组 ans 存储结果,空间复杂度为 O(n)。
总结
该解法通过贪心思想,逐字符判断并动态调整结果字符串的构造,同时保持了 O(n)O(n) 的线性复杂度,非常高效,适用于大规模输入数据。