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

63 阅读5分钟

问题描述

小U拥有一个由0和1组成的字符串,她可以进行最多k次操作,每次操作可以交换相邻的两个字符。目标是通过这些操作,使得最终得到的字符串字典序最小。

例如,小U当前有一个字符串 01010,她最多可以进行 2 次相邻字符交换操作。通过这些操作,她可以将字符串调整为 00101,这是可以通过不超过2次操作得到的字典序最小的字符串。

现在,小U想知道,经过最多k次操作后,能够得到的字典序最小的字符串是什么。


思路

给定一个由0和1组成的字符串,我们可以通过最多k次相邻字符交换操作,使得最终得到的字符串字典序最小。为了实现这一点,我们可以使用贪心算法:

具体思路是:每次寻找在允许的操作次数范围内可以移动到最前面的最小字符,并将其移到最前面。重复此过程直到达到最大操作次数或者字符串已经是最小字典序。

  1. 遍历每个位置:对于字符串中的每一个位置i,尝试将该位置上的字符变为当前允许范围内(即当前位置到当前位置加上剩余操作次数)的最小可能字符。
  2. 找到最小字符:在允许的操作次数范围内找到最小的字符,并计算将其移动到当前位置所需的交换次数(代价)。
  3. 判断代价:如果代价小于等于剩余的操作次数,则进行移动并减少相应的操作次数;否则跳过该位置。
  4. 移动字符:通过位移的方式将找到的最小字符移动到当前位置。

比如,假设输入字符串为 "01010",最大操作次数为 2:

  • 初始状态:"01010"
  • 第一步:i=0, 最小字符为'0'(已经在位置0),无需移动。
  • 第二步:i=1, 最小字符为'0'(在位置2),移动代价为1,执行移动:"00110"
  • 第三步:i=2, 最小字符为'1'(已经在位置2),无需移动。
  • 结束条件:操作次数用完或无法再优化。

详细解释

  1. 将输入字符串转换为字符数组

    char[] str = s.toCharArray();
    

    这一步将输入的字符串 s 转换为一个字符数组 str,这样可以方便地对单个字符进行读取和修改。

  2. 遍历每个位置i

    for (int i = 0; i < n && k > 0; i++) {
    

    使用一个循环遍历字符串的每一个位置 i

  3. 找到在允许的操作次数范围内可以移动到位置i的最小字符

    int minIndex = i;
    for (int j = i + 1; j < n && j <= i + k; j++) {
        if (str[j] < str[minIndex]) {
            minIndex = j;
        }
    }
    
    • 初始化 minIndex 为当前索引 i
    • 内层循环从 i+1 开始遍历到 n-1 或者 i+k(以较小者为准),寻找在允许的操作次数范围内的最小字符。
    • 如果找到更小的字符,则更新 minIndex
  4. 计算将最小字符移动到位置i所需的交换次数(代价)

    int cost = minIndex - i;
    if (cost > k) continue; // 如果代价大于剩余的操作次数,则跳过
    
    • 计算将字符从 minIndex 移动到 i 所需的交换次数 cost
    • 如果 cost 大于剩余的操作次数 k,则跳过本次循环,继续下一个位置的处理。
  5. 将字符从minIndex位置移动到i位置,并进行相应的字符位移

    char c = str[minIndex];
    for (int j = minIndex; j > i; j--) {
        str[j] = str[j - 1];
    }
    str[i] = c;
    
    • 保存 minIndex 位置的字符 c
    • 通过一个内层循环将 minIndex 到 i 之间的所有字符向右移动一位。
    • 最后将字符 c 放置在位置 i 上。
  6. 减少剩余的操作次数

    k -= cost;
    

    根据实际使用的交换次数 cost 减少剩余的操作次数 k

  7. 返回处理后的字符串

    return new String(str);
    

    将处理后的字符数组转换回字符串并返回。

具体代码

public class Main {
    public static String solution(int n, int k, String s) {
        // 将输入字符串转换为字符数组以便于操作
        char[] str = s.toCharArray();
        
        // 遍历每个位置i,尝试将该位置上的字符变为最小可能的字符
        for (int i = 0; i < n && k > 0; i++) {
            // 找到在允许的操作次数范围内可以移动到位置i的最小字符
            int minIndex = i;
            for (int j = i + 1; j < n && j <= i + k; j++) {
                if (str[j] < str[minIndex]) {
                    minIndex = j;
                }
            }
            
            // 计算将最小字符移动到位置i所需的交换次数(代价)
            int cost = minIndex - i;
            if (cost > k) continue; // 如果代价大于剩余的操作次数,则跳过

            // 将字符从minIndex位置移动到i位置,并进行相应的字符位移
            char c = str[minIndex];
            for (int j = minIndex; j > i; j--) {
                str[j] = str[j - 1];
            }
            str[i] = c;

            // 减少剩余的操作次数
            k -= cost;
        }
        
        // 返回处理后的字符串
        return new String(str);
    }

    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. 字符串和字符数组

  • 字符串:不可变的字符序列。
  • 字符数组:可变的字符序列,适合进行字符级别的操作。

2. 贪心算法

  • 概念:贪心算法是一种在每一步选择中都采取在当前状态下最好或最优的选择,从而希望导致结果是全局最好或最优的算法。
  • 应用场景:适用于那些可以通过局部最优解达到全局最优解的问题,如本题中的字符串字典序最小化问题。

3. 时间复杂度分析

  • 时间复杂度:衡量算法执行时间随输入规模变化的趋势。
  • 本题的时间复杂度:O(n^2),因为在最坏情况下,每次都需要遍历剩余的部分来找到最小字符。

4. 字符交换和位移

  • 字符交换:将一个字符从一个位置移动到另一个位置。
  • 位移操作:通过循环将一系列字符向右或向左移动一位。