题目大意
给定一个由0和1组成的字符串 ,至多进行 次交换相邻的两个字符的操作,能够得到的字典序最小的字符串是什么。
测试样例(应该不需要解释)
样例1:
输入:
n = 5, k = 2, s = "01010"
输出:'00101'
样例2:
输入:
n = 7, k = 3, s = "1101001"
输出:'0110101'
样例3:
输入:
n = 4, k = 1, s = "1001"
输出:'0101'
解题思路
-
由于要求的是字典序最小,那必然是一个贪心的做法:尽可能的将 中的
0移到前面,1移到后面。由于次数有限,越靠前的0越容易移动到最前端,因此我们考虑在顺序扫描时每碰到一个0都尽可能将其与之前的1进行交换。 -
最简单的想法就是记录一下扫描到当前位置时前面是否存在
1,如果存在且当前位置为0则一路交换到前面不存在1,继续进行扫描,该方法类似于冒泡排序,时间复杂度最坏为 。 -
然后我们想到可以记录下最左侧的
1的位置,每次扫描到0,如果交换次数足够就可以直接将这两者进行交换,然后顺序去找下一个最左侧的1,继续向后扫描;如果交换次数不够再去暴力交换。由于在这种情况下暴力交换次数仍然未知,复杂度难以确定(?)。 -
实际不然。如果我们进一步观察可以发现,只要交换的次数充足,已扫描结束的部分一定是一段
0在前,一段1在后,否则前面就是可以进一步进行交换,使字典序更小的。因此,我们可以确信最后的那次暴力交换只会出现一次,因此时间复杂度是 的,完全可以通过;并且每次交换完下一个最左侧的1就是当前位置加一。 -
既然我们知道了已扫描结束的部分一定是一段
0在前,一段1在后,那么我们其实连那次暴力交换也可以省去,如果我们剩余的次数不足以与最左侧的1进行交换,我们也可以直接算出可以交换的最左侧的1在哪一位,直接将二者交换即可。
代码及代码解释
int l=0;//l用于记录最左侧的1的位置
for(int i=0;i<n&&k;i++){//如果次数用完可以直接结束
if(s[i]=='1')continue;//如果当前位为1则不需要处理
if(k>=i-l){//如果可以与最左侧的1进行交换
k-=i-l;//更新剩余次数
swap(s[l],s[i]);//将二者交换
l++;//更新最左侧的1的位置
}
else{//如果无法最左侧的1进行交换
swap(s[i],s[i-k]);//将其与可以交换的最左侧的1进行交换
k=0;//次数全部用完
}
}
需要注意的是,我将 (即最左侧的1的位置)的初始值设置为 ,但这一位并不一定是 ,但这并不影响结果,因为如果这一位为 就一定会进入 语句,因为 的值为 ,因此剩余交换次数也不会变少,交换也不起作用,但 自增至下一位,再进行同样的步骤,因此不会产生影响。