问题描述
在算法与字符串处理的世界中,字典序最小问题是一类经典且有趣的挑战。在这个问题中,我们需要对由 0 和 1 组成的字符串进行最多 k 次操作,每次操作可以交换相邻的两个字符,目标是得到字典序最小的字符串。
字典序是字符串比较大小的一种方式,类似于词典中单词的排列顺序。例如,001 小于 010,因为 0 的优先级高于 1。在此问题中,我们需要通过有限次数的操作使得字符串尽可能靠近这种排列顺序。
例子分析
样例1: 输入字符串为 01010,最多可以进行 2 次相邻交换操作。 我们可以这样移动:
- 第一次交换字符串变为
00110; - 第二次交换字符串变为
00101。 最终,字典序最小的结果是00101。
样例2: 输入字符串为 1101001,最多可以进行 3 次相邻交换操作。 通过以下操作,我们可以得到结果:
- 第一次交换,
1101001→1011001; - 第二次交换,
1011001→0111001; - 第三次交换,
0111001→0110101。 最终结果为0110101。
分析与解决思路
问题建模
将问题分解,可以归纳出以下特性:
- 只需要关心
0和1的相对顺序,目标是将0尽可能提前。 - 操作次数有限,因此不可能将所有的
0都移到最前面,必须优先处理更靠后的0。 - 每次操作仅能交换相邻字符,因此无法直接跳跃,只能模拟每次交换的过程。
贪心策略
通过分析可以发现,贪心策略是本问题的最佳选择:
- 遍历字符串,从左到右找到每一个
0; - 如果
0前面有1,并且当前操作次数k足够,将这个0与前面的1交换,直到无法再交换或用尽操作次数; - 优先移动靠后的
0,因为前面的0已经在靠前的位置,对结果的影响较小。
Java 实现代码
public class Main {
public static String solution(int n, int k, String s) {
char[] chars = s.toCharArray(); // 转为字符数组
int i = 0; // 遍历指针
while (i < n && k > 0) {
if (chars[i] == '0') {
int pos = i;
// 将当前的 '0' 尽可能往前移动
while (pos > 0 && chars[pos - 1] == '1' && k > 0) {
chars[pos] = chars[pos - 1];
chars[pos - 1] = '0';
pos--; // 向左移动
k--; // 消耗一次操作
}
}
i++; // 移动到下一个字符
}
return new String(chars); // 转为字符串返回
}
public static void main(String[] args) {
System.out.println(solution(5, 2, "01010").equals("00101")); // true
System.out.println(solution(7, 3, "1101001").equals("0110101")); // true
System.out.println(solution(4, 1, "1001").equals("0101")); // true
}
}
算法复杂度分析
-
时间复杂度
- 遍历字符串的复杂度为 (O(n));
- 每次移动最多需要 (O(k)) 次操作,但总的移动次数受
k的限制,因此整体复杂度为 (O(n))。
-
空间复杂度
- 使用了字符数组存储字符串,额外空间为 (O(n))。
问题特性总结
- 局部最优即全局最优: 因为操作是有限的,并且只允许相邻交换,因此每次只需关心当前的最优决策,而无需考虑全局调整。
- 启发式策略: 本问题中的启发式是尽量将
0提前,并优先移动靠后的0,从而逐步优化整个字符串的字典序。