问题背景与分析
问题描述
小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次相邻字符交换操作,使字符串的字典序最小。 - 操作限制:每次只能交换相邻的两个字符。
- 字典序最小:字典序是指字符串在字典中的顺序,例如
"00101"的字典序小于"01001"。
解决方案设计
思路
- 优先移动0:由于0在字典序中排在1之前,因此优先将0尽可能地向前移动,可以使字符串的字典序最小。
- 贪心算法:每次找到最靠前的1,并在其后面找到最近的0,然后将这个0移动到1的前面,直到用完所有的操作次数
k或者无法再进行有效的移动。
具体步骤
- 初始化:将输入字符串
s转换为字符数组chars,以便于操作。 - 外层循环:遍历字符数组
chars,直到处理完所有字符或k次操作用完。 - 内层循环:如果当前字符是
'1',则在当前位置i之后寻找第一个'0'。 - 计算交换次数:计算从
'0'的位置j到当前位置i的距离swapCount。 - 执行交换:如果
swapCount小于等于剩余的k次操作次数,则进行交换操作,将'0'移动到当前位置i,并将中间的字符依次向后移动。 - 更新操作次数:更新
k的值,减去已经使用的交换次数。 - 返回结果:最终将字符数组
chars转换回字符串并返回。
代码实现
public String solution(int n, int k, String s) {
char[] chars = s.toCharArray();
for (int i = 0; i < n && k > 0; i++) {
if (chars[i] == '0') {
continue;
}
for (int j = i + 1; j < n && k > 0; j++) {
if (chars[j] == '0') {
int swapCount = j - i;
if (swapCount <= k) {
char temp = chars[j];
for (int m = j; m > i; m--) {
chars[m] = chars[m - 1];
}
chars[i] = temp;
k -= swapCount;
}
break;
}
}
}
return new String(chars);
}
代码解析
-
初始化:
- 将输入字符串
s转换为字符数组chars,以便于操作。
- 将输入字符串
-
外层循环:
- 遍历字符数组
chars,直到处理完所有字符或k次操作用完。 - 如果当前字符是
'0',则跳过本次循环,继续检查下一个字符。
- 遍历字符数组
-
内层循环:
-
如果当前字符是
'1',则在当前位置i之后寻找第一个'0'。 -
计算从
'0'的位置j到当前位置i的距离swapCount。 -
如果
swapCount小于等于剩余的k次操作次数,则进行交换操作:- 将
'0'移动到当前位置i,并将中间的字符依次向后移动。 - 更新
k的值,减去已经使用的交换次数。
- 将
-
交换完成后,跳出内层循环,继续处理下一个字符。
-
-
返回结果:
- 最终将字符数组
chars转换回字符串并返回。
- 最终将字符数组
个人思考与分析
贪心算法的有效性
在这个问题中,贪心算法是非常有效的。因为每次将 '0' 尽可能地向前移动,可以确保每一步都使字符串的字典序变小。这种局部最优的选择最终会导全局最优解。
时间复杂度分析
- 外层循环:遍历字符串的每个字符,时间复杂度为
O(n)。 - 内层循环:在最坏情况下,每次都需要遍历剩余的所有字符,时间复杂度为
O(n^2)。
总体时间复杂度为 O(n^2),对于较小的字符串长度是可以接受的。如果字符串长度非常大,可以考虑使用更高效的数据结构(如双端队列)来优化交换操作。
空间复杂度分析
- 字符数组:将字符串转换为字符数组,空间复杂度为
O(n)。 - 其他变量:常数级别的额外空间,空间复杂度为
O(1)。
总体空间复杂度为 O(n)。