字典序最小的01字符串

231 阅读4分钟

这道题目是一个典型的字符串排序问题,通过进行有限次相邻字符交换操作来使得字符串的字典序最小。问题的核心思想是:每次操作都应尽量将较小的字符(0)交换到前面的位置,以使得字典序变小。

问题分析

给定一个由字符 01 组成的字符串,要求在最多 k 次相邻交换操作后,使字符串的字典序最小。字典序是字符串排序的一种方式,简而言之,它是字符串按照字母顺序排列的顺序,因此我们要将 0 移到尽可能靠前的位置,1 移到尽量后的位置。

在实现过程中,交换次数是有限的,因此我们不能无限制地交换,而是需要在每一步优化交换操作的选择,以便用最少的交换次数实现最大的字典序优化。

思路解析

  1. 交换策略

    • 每次操作,尽量把 1 后面最靠近的 0 向前移动。
    • 通过扫描字符串,从前往后依次处理每个字符,当遇到 1 时,在它后面寻找 0,并将其向前交换,直到交换次数 k 用尽。
  2. 局部最优选择

    • 对于每个 1,我们可以在其后 k 个位置内找到一个 0,并把它交换到 1 的位置。通过这种方式,我们希望通过交换得到最小字典序的字符串。
  3. 交换限制

    • 在每次交换时,我们要确保交换次数不超过给定的 k
  4. 策略更新

    • 每次交换后,需要减少剩余的 k,并继续尝试对后面的字符进行交换,直到无法进一步优化。

解法实现

public class Main {
    public static String solution(int n, int k, String s) {
        // 将字符串转换为字符数组以便于操作
        char[] chars = s.toCharArray();
        
        // 遍历字符数组
        for (int i = 0; i < n && k > 0; i++) {
            // 如果当前字符是'1',我们需要找到最小的'0'来交换
            if (chars[i] == '1') {
                int minZeroIndex = -1;
                // 在当前位置之后的k个字符内寻找'0'
                for (int j = i + 1; j < n && j <= i + k; j++) {
                    if (chars[j] == '0') {
                        minZeroIndex = j;
                        break;
                    }
                }
                // 如果找到了'0',进行交换
                if (minZeroIndex != -1) {
                    // 计算需要交换的次数
                    int swapsNeeded = minZeroIndex - i;
                    if (swapsNeeded <= k) {
                        // 进行交换
                        char temp = chars[minZeroIndex];
                        for (int j = minZeroIndex; j > i; j--) {
                            chars[j] = chars[j - 1];
                        }
                        chars[i] = temp;
                        // 更新剩余的交换次数
                        k -= swapsNeeded;
                    }
                }
            }
        }
        
        // 将字符数组转换回字符串并返回
        return new String(chars);
    }

    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"));
    }
}

代码详解

  1. 字符数组转换:我们首先将字符串 s 转换成字符数组 chars,因为字符串是不可变的,而字符数组是可变的,便于我们做交换操作。

  2. 外层循环:外层 for 循环遍历字符数组,每次处理一个字符。如果该字符是 1,我们就尝试在其后最多 k 个位置内寻找 0,并进行交换。

  3. 寻找最小的 '0':对于每个 1,我们在其后最多 k 个字符内搜索第一个出现的 0,如果找到,则将其与当前的 1 交换。

  4. 交换操作:一旦找到合适的 0,我们就进行交换。交换的过程是将该 0 挪动到 1 的位置,其他字符依次后移。这一步需要逐个交换,直到把 0 移到合适位置。

  5. 更新交换次数:每次交换之后,减少剩余的交换次数 k,直到没有足够的交换次数或已经处理完所有字符。

  6. 返回结果:最终,字符数组 chars 被修改为最小字典序的字符串,我们将其转换回字符串并返回。

复杂度分析

  • 时间复杂度:每次操作需要在最多 k 个字符内进行一次查找,内层循环的最坏情况复杂度是 O(k),外层循环遍历 n 个字符。所以总的时间复杂度为 O(n * k)

  • 空间复杂度:我们只使用了一个字符数组来存储字符串,因此空间复杂度是 O(n)

样例分析

  1. 样例1

    • 输入:n = 5, k = 2, s = "01010"
    • 输出:'00101'
    • 解释:最多进行 2 次交换,可以将 01010 转换为 00101
  2. 样例2

    • 输入:n = 7, k = 3, s = "1101001"
    • 输出:'0110101'
    • 解释:最多进行 3 次交换,可以将 1101001 转换为 0110101
  3. 样例3

    • 输入:n = 4, k = 1, s = "1001"
    • 输出:'0101'
    • 解释:最多进行 1 次交换,可以将 1001 转换为 0101

总结

这道题目通过合理利用交换操作,尽量把 0 放到前面的位置,减少交换次数,从而实现字典序最小的字符串。通过逐步优化字符交换的策略,使得解法既简洁又高效。