19. 字典序最小的01字符串 解析 | 豆包MarsCode AI刷题

44 阅读3分钟

题目大意

给定一个由0和1组成的字符串 SS,至多进行 kk 次交换相邻的两个字符的操作,能够得到的字典序最小的字符串是什么。

测试样例(应该不需要解释)

样例1:

输入:n = 5, k = 2, s = "01010"
输出:'00101'

样例2:

输入:n = 7, k = 3, s = "1101001"
输出:'0110101'

样例3:

输入:n = 4, k = 1, s = "1001"
输出:'0101'

解题思路

  1. 由于要求的是字典序最小,那必然是一个贪心的做法:尽可能的将 SS 中的 0 移到前面,1 移到后面。由于次数有限,越靠前的 0 越容易移动到最前端,因此我们考虑在顺序扫描时每碰到一个 0 都尽可能将其与之前的 1 进行交换。

  2. 最简单的想法就是记录一下扫描到当前位置时前面是否存在 1 ,如果存在且当前位置为 0 则一路交换到前面不存在 1,继续进行扫描,该方法类似于冒泡排序,时间复杂度最坏为 O(n2)O(n^2)

  3. 然后我们想到可以记录下最左侧的 1 的位置,每次扫描到 0 ,如果交换次数足够就可以直接将这两者进行交换,然后顺序去找下一个最左侧的 1 ,继续向后扫描;如果交换次数不够再去暴力交换。由于在这种情况下暴力交换次数仍然未知,复杂度难以确定(?)。

  4. 实际不然。如果我们进一步观察可以发现,只要交换的次数充足,已扫描结束的部分一定是一段 0 在前,一段 1 在后,否则前面就是可以进一步进行交换,使字典序更小的。因此,我们可以确信最后的那次暴力交换只会出现一次,因此时间复杂度是 O(n)O(n) 的,完全可以通过;并且每次交换完下一个最左侧的 1 就是当前位置加一。

  5. 既然我们知道了已扫描结束的部分一定是一段 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;//次数全部用完
        }
    }

需要注意的是,我将 ll(即最左侧的1的位置)的初始值设置为 00 ,但这一位并不一定是 11 ,但这并不影响结果,因为如果这一位为 00 就一定会进入 ifif 语句,因为 ili-l 的值为 00 ,因此剩余交换次数也不会变少,交换也不起作用,但 ll 自增至下一位,再进行同样的步骤,因此不会产生影响。