问题描述
小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'
首先我们需要先明白字典序的规则(字典序比较规则简单来说就是:从左到右逐字符比较,字符优先级基于ASCII码值,长度较短的字符串在字典序上较小。),了解规则后我们发现这里只有0和1两个数,所以我们只要经过k次操作尽可能使1往后靠。
==示例==
-
"01010" 和 "00101":
- 第一个字符相同,都是'0'。
- 第二个字符不同,'1' > '0',所以 "01010" 在字典序上大于 "00101"。
-
"1001" 和 "0101":
- 第一个字符不同,'1' > '0',所以 "1001" 在字典序上大于 "0101"。
可以通过贪心算法的思想,每次尽可能地将1往后移动,以达到字典序最小的目标。
public class Main {
public static String solution(int n, int k, String s) {
int[] arr=new int[n],index;
int i,j,start1,start0,end1,end0,sum1,sum0,tmp;
for(i=0;i<n;i++){
if(s.charAt(i)=='1'){
arr[i]=1;
}
}
while(k>0){
index=queryIndex(n,arr);
start1=index[0];
end1=index[1];
start0=index[2];
end0=index[3];
if(end1==n){//等价于start0=n
break;
}
sum1=end1-start1;
sum0=end0-start0;//区间里1和0各自的数量
if(sum1<=0){
break;
}
//System.out.println("第一个1的区间:["+start1+","+end1+"] 数量:"+sum1+";第一个0的区间:["+start0+","+end0+"] 数量:"+sum0);
//连续的1的个数比k大
if(k<=sum1){
arr[end1]=1;
arr[end1-k]=0;
break;
}
tmp=sum1;
for(i=1;i<=sum0&&tmp<=k;){
i++;//因为i从1开始,所以要先+1,下个值才会被拿来做判断
tmp=sum1*i;
}
i--;
//System.out.println("k="+k+" ,i="+i);
//必须先改1后再改0,否则会多出1,比如1000移动3位,如果先0后1会变成0111
for(j=start0;j<start0+i;j++){
arr[j]=1;
}
for(j=start1;j<start1+i;j++){
arr[j]=0;
}
k-=i*sum1;
}
StringBuilder sb=new StringBuilder();
for(i=0;i<n;i++){
sb.append(arr[i]);
}
String res=sb.toString();
//System.out.println(sb);
return res;
}
private static int[] queryIndex(int n,int[] arr){
int start1=0,start0=0,end1=0,end0=0;
start1=0;
while(arr[start1]!=1){
start1++;
if(start1==n){
return new int[]{start1,end1,start0,end0};
}
}
end1=start1;
while(arr[end1]==1){
end1++;
if(end1==n){
return new int[]{start1,end1,start0,end0};
}
}
//到这里我们有[start1,end1)这个左闭右开的区间里面的元素都是1
start0=end1;
end0=start0;
while(arr[end0]==0){
end0++;
if(end0==n){
return new int[]{start1,end1,start0,end0};
}
}
//到这里我们可以得到[start0,end0)这段区间里面的值是0
return new int[]{start1,end1,start0,end0};
}
public static void main(String[] args) {
System.out.println(solution(5, 2, "01010").equals("00101"));
System.out.println(solution(7, 3, "1101001").equals("0110101"));
System.out.println(solution(4, 1, "1001").equals("0101"));
}
}
可以先写一个queryIndex方法用于查询字符串中第一个连续的1的区间和随后的0的区间。
-
主循环:
- 程序进入一个while循环,循环条件是k大于0,即还有操作次数。
- 在循环中,首先调用queryIndex方法查询当前字符串中第一个连续的1的区间和随后的0的区间。
- 如果没有更多的1可以移动(即所有1都在字符串末尾),则退出循环。
-
区间操作:
- 计算连续1的区间长度sum1和随后的0的区间长度sum0。
- 如果sum1小于等于0,说明没有1可以移动,退出循环。
- 如果k小于sum1,说明无法将整个连续的1区间移动到0区间,因此只移动k个1,然后退出循环。
- 通过循环计算可以移动的1的数量,直到移动的1的数量乘以0的区间长度超过k。
-
交换操作:
- 根据计算出的可以移动的1的数量,将1和0在数组中进行交换。
- 更新剩余的操作次数k。
- 这里注意先改0还是先改1覆盖的问题:必须先改1后再改0,否则会多出1,比如1000移动3位,如果先0后1会变成0111