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

59 阅读5分钟

字典序最小的01字符串

问题描述

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

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

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

问题解析

这道题的解题思路非常多,这里我只提供一种我认为大家最好理解的。首先先熟悉题目,要求交换有限次数的字符,使得字符串字典序最小。由于题目中字符串普遍由‘0’、‘1’字符组成,所以这里的问题要可以视作得到最小的二进制字符串,那么如何使其最小呢?根据题目要求,只能交换相邻字符串,那么字符的可能组成只有如下四种

01

10

11

00

后面两种的字符交换是无法改变大小的,所以可以发现只有当'10' -> '01'的情况才会改变二进制字符串所代表的数字大小。那么改变顺序,我们自然而然应该选择从左往右改变,因为左边的字符所代表的数值相比右边会大。

现在思路理清楚了,具体代码该如何写呢?上述思想体现在代码如下

代码详情

    for (int i = 0; i < n; i++) {
        // 如果当前字符是 '0'
        if (chars[i] == '0') {
            // 尝试将前面的 '1' 移动到这里
            for (int j = Math.max(i - k, 0); j < i; j++) {
                // 找到可以移动的 '1'
                if (chars[j] == '1') {
                    // 交换 '1''0'
                    chars[j] = '0';
                    chars[i] = '1';
                    // 更新剩余的操作次数
                    k -= (i - j);
                    break; // 移动完一个 '1' 后就跳出内循环
                }
            }
        }
        // 如果没有剩余的操作次数,直接结束
        if (k <= 0) {
            break;
        }
    }

代码解析

由于是从左向右的改变思路,所以遍历应从0开始向n - 1 出发,同时还需要一个内层遍历,代表当前元素的相邻元素,范围是0到i,为什么呢。由于外层循环是一次性的,只能光顾后面的 10 -> 01 情况,而无法光顾变化后前面是否也符合 10 -> 01 情况。所以可以写出内层循环的代码

  if(chars[i] == '0'){
    for( int j = 0 ; j < i ; j++){ 
    if(chars[j] == '1') {
      chars[i] == '1'
      chars[j] == '0'
      
      k- = (i-j)
    }
 }

解释一下 k- = (i-j)这行代码。我在此举几个例子

10 ------> 交换一次变成 01

110 -----> 交换两次变成 011

1110 ----> 交换两次次变成 0111

........................................

可以发现该规律,当前0前面有几个1,就交换几次达到目标字符串样式。 用代码体现就是k- =(i - j),交换的次数一次性就更新成功了,便可以达到光顾前面代码的效果。

与此同时,我们还应注意到内循环每次查询相邻元素时,总是走重复的路径,我们是否可以优化这段代码让其达到不走重复道路的效果呢。由于题目要求是最多k次交换,那么当最理想状态 10000...(k个0)时,若此时当前索引为i = k + 1,那么此时字符串必然已经达到000000...(k个0)10的效果,且经历过k-1次交换,此时k = 1,那么此时相邻元素索引应该为i-1 = i-k。所以可以推出代码,j=Math.max(i-k, 0)。如i-k小于零,说明还未经过交换,就应该从0开始。

现在核心代码已经分析完毕,接下来展示完整代码。

完整代码详情

public class Main {
public static String solution(int n, int k, String s) {
    char[] chars = s.toCharArray();
    for (int i = 0; i < n; i++) {
        // 如果当前字符是 '0'
        if (chars[i] == '0') {
            // 尝试将前面的 '1' 移动到这里
            for (int j = Math.max(i - k, 0); j < i; j++) {
                // 找到可以移动的 '1'
                if (chars[j] == '1') {
                    // 交换 '1' 和 '0'
                    chars[j] = '0';
                    chars[i] = '1';
                    // 更新剩余的操作次数
                    k -= (i - j);
                    break; // 移动完一个 '1' 后就跳出内循环
                }
            }
        }
        // 如果没有剩余的操作次数,直接结束
        if (k <= 0) {
            break;
        }
    }
    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"));
}
}

收获与总结

这道题涉及非常多的数学推演和规律推演,需要一定的数学功底才有能力将其完成。通过这道题,本人也是显著的锻炼了本人的总结能力以及对代码的优化能力。当完成代码后不再局限于只完成它,而是想方设法的去优化他。总得来说这道题是一道非常有难度的题,本人也是花费了将近两个小时才将其推算出来,但是,一旦知晓其数学原理,写代码部分还是非常简单的。当然对于这道题我还有另外的解法,是我空闲之余想出来的。我将会在接下来几天将其做个归类并发表。以上观点以及代码分析仅为个人观点,若有不满,敬请开喷。