一、背景与问题描述
在算法与字符串处理的世界中,字典序最小问题是一类经典且有趣的挑战。在这个问题中,我们需要对由 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
,从而逐步优化整个字符串的字典序。
博客主页: 总是学不会.