[5455. 最多 K 次交换相邻数位后得到的最小整数]
给你一个字符串 num
和一个整数 k
。其中,num
表示一个很大的整数,字符串中的每个字符依次对应整数上的各个 数位 。
你可以交换这个整数相邻数位的数字 最多 k
次。
请你返回你能得到的最小整数,并以字符串形式返回。
示例 1:
输入:num = "4321", k = 4
输出:"1342"
解释:4321 通过 4 次交换相邻数位得到最小整数的步骤如上图所示。
示例 2:
输入:num = "100", k = 1
输出:"010"
解释:输出可以包含前导 0 ,但输入保证不会有前导 0 。
示例 3:
输入:num = "36789", k = 1000
输出:"36789"
解释:不需要做任何交换。
示例 4:
输入:num = "22", k = 22
输出:"22"
示例 5:
输入:num = "9438957234785635408", k = 23
输出:"0345989723478563548"
提示:
1 <= num.length <= 30000
num
只包含 数字 且不含有 前导 0 。1 <= k <= 10^9
暴力肯定不行,一种高效的代替方法是做预处理:记录下所有的0-9的位置然后用这些预处理的位置来进行查找,好处是每次查找时不需要重头开始遍历。比如上一次0的所有下标我已经判断到了第四个,那下一次再对0进行判断时直接从第五个开始就行,因为前面已经被用了或者不满足条件,这样就提高了效率,所有查找过程的总时间复杂度为O(n),均摊为O(1)。
那这种方案带来的问题是不好计算置换次数(暴力法好计算是因为切实地挨个做了置换!),比如我们处理的数组中间有4个数:…, 7,1,2,4, …,而且假设此时我们已经获悉7要和4置换,其中1和2都已经被置换过了,此时的置换次数是1,因此计算置换次数需要我们记录所有已经被用过的元素并对范围进行求和。可以使用线段树或者树状数组记录这个值
public class Test {
public static void main(String[] args) {
String num = "4321";
int k = 4;
System.out.println(new Test().minInteger(num, k));
}
public String minInteger(String num, int k) {
List<Integer>[] list = new List[10];
for (int i = 0; i < list.length; i++) {
list[i] = new ArrayList<>();
}
int[] listIndex = new int[10];
boolean[] flag = new boolean[num.length()];
StringBuilder res = new StringBuilder();
for (int i = 0; i < num.length(); i++) {
int n = num.charAt(i) - '0';
list[n].add(i);
}
FenwickTree ft = new FenwickTree(new int[num.length()]);
outer: for (int i = 0; i < num.length(); i++) {
if(flag[i]){
continue;
}
int n = num.charAt(i)-'0';
//这里要分为找到和没有找到
for (int j = 0; j < n; j++) {
while (listIndex[j] < list[j].size() && list[j].get(listIndex[j]) < i){
listIndex[j]++;
}
if (listIndex[j] == list[j].size()){
continue ;
}
int index = list[j].get(listIndex[j]);
int count = ft.getPreSum(index-1);
//找到那么下一个找的值还是要和这个i比较,i--
if(k >= index-count){
listIndex[j]++;
k -= (index-count);
res.append(num.charAt(index));
flag[index] = true;
ft.updateBit(index,1);
i--;
continue outer;
}
}
// 没有找到,就是说当前i的值就是最佳值
res.append(n);
flag[i] = true;
ft.updateBit(i, 1);
}
for (int i = 0; i < num.length(); i++) {
if (!flag[i]){
res.append(num.charAt(i));
}
}
return res.toString();
}
class FenwickTree{
int[] num;
int[] sum;
/**
* num从0开始
* sum从1开始
* @param num
*/
public FenwickTree(int[] num) {
sum = new int[num.length+1];
this.num = num;
for (int i = 0; i < num.length; i++) {
updateBit(1, num[i]);
}
}
/**
* 获得l~r的和
* @param l
* @param r
* @return
*/
public int getSum(int l ,int r){
return getPreSum(r)-getPreSum(l-1);
}
/**
* 获得0~i的和
* @param i
* @return
*/
public int getPreSum(int i){
i += 1;
int res = 0;
while (i > 0){
res += sum[i];
i -= lowBit(i);
}
return res;
}
/**
* 当第i位增加diff时修改会影响到的值
* @param i
* @param diff 和原值的差
* @return
*/
public void updateBit(int i, int diff){
i = i+1;
while(i < sum.length){
sum[i] += diff;
i += lowBit(i);
}
}
/**
* 返回为1的最低位的值
* @param i
* @return
*/
public int lowBit(int i){
return i&(-i);
}
}
}