解题思路:字典序最小的01字符串
解决该问题的核心在于如何通过有限次相邻字符交换,将字符串调整为字典序最小的形式。以下是具体解题思路的逐步推导:
一、问题理解
首先需要理解题目中的关键点:
- 字典序最小:意味着字符串在排序规则上尽可能小。对于
0和1组成的字符串,将0尽可能提前移动会得到更小的字典序。 - 操作限制:每次只能交换相邻的两个字符,且最多允许
k次操作。这是限制算法设计的关键条件。 - 目标:在有限次操作内,通过交换使字符串字典序最小化。
二、问题分解与思路推导
1. 如何实现字典序最小化?
为了使字典序最小,应优先将 0 移动到字符串的左侧。因此,可以从左到右扫描字符串,逐步将后续的较小字符移到当前字符的位置上。
贪心思路的启发:
- 局部最优解:在当前位置,选择从当前位置向后最多
k步范围内的最小字符,并将其移动到当前位置。 - 全局最优解:由于每次都尽可能优化当前字符的字典序,在扫描完成后可保证整个字符串的字典序是最小的。
2. 如何设计操作步骤?
为了实现上述贪心策略,需要分步完成:
- 扫描字符串: 从左到右遍历,每个位置尝试寻找当前范围内的最小字符。
- 查找范围:
在当前位置向后最多
k步范围内寻找最小字符的位置。 - 交换字符: 将找到的最小字符通过相邻交换移动到当前位置。
- 减少剩余次数:
每次交换都会消耗一定的
k,需要动态更新剩余可用次数。
三、为什么选择贪心法?
在这个问题中,贪心法是一个合适的选择,原因如下:
- 每一步决策是独立的: 在当前位置选择范围内最小字符的操作,不会影响后续决策的可行性。
- 最优子结构: 贪心策略能够保证当前局部最优结果,最终通过累积局部最优达到全局最优。
- 交换次数有限:
由于最多只能交换
k次,因此需要在每一步中最大化操作的收益(即字典序的减小幅度)。
四、细节分析与优化
1. 如何查找范围内最小字符?
- 对于当前位置
i,只需遍历s[i+1]到s[min(i + k + 1, n)]范围内的字符,找到其中最小值及其位置。 - 遍历范围至多为
k,复杂度为O(k)。
2. 如何移动最小字符到当前位置?
- 假设找到的最小字符位置为
min_index,则将其通过逐步交换前移到i位置。 - 交换次数为
min_index - i,每次交换消耗 1 次操作次数。
3. 何时终止操作?
- 当剩余操作次数
k <= 0时,提前结束循环。
五、解题方法总结
核心思路
- 按照从左到右的顺序扫描字符串。
- 在当前位置范围内找到字典序更小的字符,并尽可能将其移动到当前位置。
- 遍历完成后,字符串的字典序已是最小化。
算法伪代码
1. 将字符串转为字符列表 s
2. 遍历字符串的每个字符 i:
a. 如果剩余操作次数 k <= 0,则退出循环。
b. 找到从 i 到 min(i + k, n - 1) 范围内的最小字符及其索引。
c. 如果最小字符的索引 min_index 不等于 i:
- 将 min_index 处的字符逐步前移到 i。
- 更新剩余操作次数 k -= (min_index - i)。
3. 将字符列表转回字符串,返回结果。
六、代码实现
以下是题目的解答代码:
def solution(n: int, k: int, s: str) -> str:
# 转换字符串为列表,便于操作
s = list(s)
for i in range(n):
if k <= 0:
break
# 找到从当前位置开始,k步范围内的最小字符及其位置
min_char = s[i]
min_index = i
for j in range(i + 1, min(i + k + 1, n)):
if s[j] < min_char:
min_char = s[j]
min_index = j
# 如果找到比当前字符小的字符,则将其前移
if min_index != i:
# 将最小字符前移到当前位置
for j in range(min_index, i, -1):
s[j], s[j - 1] = s[j - 1], s[j]
# 减少剩余的交换次数
k -= (min_index - i)
return ''.join(s)