leetcode-特殊的二进制序列

215 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第9天,点击查看活动详情

题目描述

特殊的二进制序列是具有以下两个性质的二进制序列:

  • 0 的数量与 1 的数量相等。
  • 二进制序列的每一个前缀码中 1 的数量要大于等于 0 的数量。

给定一个特殊的二进制序列 S,以字符串形式表示。定义一个操作 为首先选择 S 的两个连续且非空的特殊的子串,然后将它们交换。(两个子串为连续的当且仅当第一个子串的最后一个字符恰好为第二个子串的第一个字符的前一个字符。)

在任意次数的操作之后,交换后的字符串按照字典序排列的最大的结果是什么?

示例 1:

输入: S = "11011000"
输出: "11100100"
解释:
将子串 "10" (在S[1]出现) 和 "1100" (在S[3]出现)进行交换。
这是在进行若干次操作后按字典序排列最大的结果。

说明:

  • S 的长度不超过 50。
  • S 保证为一个满足上述定义的特殊 的二进制序列。

思路

这题有不少人都看不懂题解,说说我自己的一些理解。首先,一个符合条件的特殊的二进制序列,一定是以1开头,以0结尾的,这是理解本题的基础。这个结论可以使用反证法来得到,如果不是1开头,那就是0开头,那么0开头长度为1的子串明显就不符合“每一个前缀码中 1 的数量要大于等于 0 的数量”。如果不是0结尾,就是1结尾,那么既然整串的1和0是相等的,那么去掉最后一个1的前缀部分,0比1多1个,这样就不符合“每一个前缀码中 1 的数量要大于等于 0 的数量”。
有了这个初步的结论后,我们可以想象,把原始的大串分成若干个不能再分割的小串后,再对小串按照字典排序和拼接就好了,但是这样忽略了一个地方:任意一个小串不能完整分割,但是可能可以部分分割出符合条件的小串再进行交换,只不过不可能包含最前面的1和最后的0了,其他部分可能还是可以做部分分割。那为什么不能包含最前面的1和最后的0呢?还是使用反证法,如果包含最前面的1,那么相当于这个子串可以分出一部分符合条件的子子串,这个跟子串不能再次完整分割就矛盾了。
有了上面2个重要结论,对子串掐头去尾后,可能还可以分割出部分进行交换,我们可以用递归的方式调用本身。至于分成的n个子串为什么可以按照字典序排列,因为我们可以做无数次的两两相邻交换,这其实就是冒泡的过程,你可以把这个排序成任何顺序的。

Java版本代码

class Solution {
    public String makeLargestSpecial(String s) {
        if (s.length() <= 2) {
            return s;
        }
        List<String> list = new ArrayList<>();
        int score = 0;
        int start = 0;
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            if ('1' == c) {
                score++;
            } else {
                score--;
                if (score == 0) {
                    list.add("1" + makeLargestSpecial(s.substring(start+1, i)) + "0");
                    start = i + 1;
                }
            }
        }
        list.sort(new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                return o2.compareTo(o1);
            }
        });
        StringBuilder ans = new StringBuilder();
        for (String item : list) {
            ans.append(item);
        }
        return ans.toString();
    }
}