题目描述
难度分:1200
输入T(≤104)表示T组数据。所有数据的n之和≤2×105。
每组数据输入n(1≤n≤2×105)和长为n的字符串s,仅包含0
到9
。
你可以执行如下操作任意次:
- 把s中的一个字符c删除,然后在任意位置插入min(c+1,9)。
输出你能得到的字典序最小的字符串。
输入样例
4
04829
9
01
314752277691991
输出样例
02599
9
01
111334567888999
算法
思维题
由于9移动到后面去不会变成更大的数,所以所有的9都可以直接移动到末尾排好。而[1,8]中的整数x要移动到后面去都会使得x变成x+1,是有代价的。因为我们希望最终s串中的数字要按照升序排列好,所以要把大的数往后移。
因此我们可以倒序遍历s串,用一个cnt1数组记录子串(i,n]中每个数字的频数,cnt2记录最终方案的每个数字频数。当遍历到s[i]时:
- 如果后面有比s[i]小的数字,那么要把s[i]移到后面去就会变为s[i]+1,最终方案会增加s[i]+1的频数,将最终方案的数字频数记录在cnt2中。
- 否则最终方案就会增加一个s[i],也记录在cnt2中。
倒序遍历完s串后,按照cnt2输出最终方案即可。
复杂度分析
时间复杂度
倒序遍历一遍s串就可以得到最终方案里每个数字的频数,而对于每个数字字符s[i],需要检查它的后面有没有比自己小的数字字符,这时候需要遍历0~9的后缀频数进行检查。因此,算法整体的时间复杂度为O(nΣ),Σ是字符集大小,本题中为10。
空间复杂度
需要两个频数统计表cnt1和cnt2,cnt1是在倒序遍历s的过程中子串(i,n]的数字频数,cnt2是最终方案的每个数字频数。它们两个的空间消耗都是O(Σ),这也是整个算法的额外空间复杂度。
C++ 代码
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 200010;
int T, n, cnt1[10], cnt2[10];
char s[N];
int main() {
scanf("%d", &T);
while(T--) {
scanf("%s", s + 1);
n = strlen(s + 1);
memset(cnt1, 0, sizeof(cnt1));
memset(cnt2, 0, sizeof(cnt2));
for(int i = n; i >= 1; i--) {
int digit = s[i] - '0';
if(digit == 9) {
cnt2[digit]++;
continue;
}
bool has_greater = false;
for(int num = 0; num < digit; num++) {
if(cnt1[num] > 0) {
cnt2[digit + 1]++;
has_greater = true;
break;
}
}
if(!has_greater) cnt2[digit]++;
cnt1[digit]++;
}
for(int digit = 0; digit <= 9; digit++) {
for(int i = 0; i < cnt2[digit]; i++) {
printf("%d", digit);
}
}
puts("");
}
return 0;
}