【761. 特殊的二进制序列】

来源:力扣(LeetCode)

描述:

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

  • 0 的数量与 1 的数量相等。

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

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

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

示例 1:

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

说明:

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

方法:分治

前言

对于本题而言,将 1 看成左括号‘(’,0 看成右括号 ‘)’,那么一个特殊的二进制序列就可以看成一个合法的括号序列。这种「映射」有助于理解题目中的操作,即交换两个相邻且非空的合法括号序列。但为了与题目保持一致,下面的部分仍然使用 1/0 进行叙述。

思路与算法

对于一个特殊序列而言,它一定以 1 开始,以 0 结束。这是因为:

长度为 1 的前缀中 1 的数量一定要大于等于 0 的数量,所以首位一定是 1;

由于 0 和 1 的数量相等,并且任意前缀中 1 的数量一定大于等于 0 的数量,那么任意后缀中 0 的数量一定大于等于 1 的数量,因此与上一条类似,末位一定是 0。

如果给定的字符串是一个「整体」的特殊序列,也就是说,它无法完整地拆分成多个特殊序列,那么它的首位 1 和末位 0 是不可能在任何交换操作中出现的。这里给出首位 1 的证明,末位 0 的证明是类似的:

如果首位 1 可以在交换操作中出现,那么包含它的子串是给定字符串(特殊序列)的一个前缀,同时这个子串也是一个特殊序列。对于字符串中剩余的后缀部分,0 和 1 的数量相等(因为给定字符串和前缀子串的 0 和 1 数量均相等)并且满足「每一个前缀中 1 的数量大于等于 0 的数量」(因为后缀部分的每一个前缀可以映射为给定字符串在同一位置结束的前缀,再扣掉前缀子串,由于前缀子串中 0 和 1 的数量相等,因此扣除后仍然满足要求),那么后缀部分也是一个特殊序列,这就说明给定字符串可以拆分成两个特殊序列,与假设相矛盾。

因此,我们可以把首位 1 和末位 0 直接移除,进一步考虑剩余的字符串。

如果给定的字符串可以拆分成多个特殊序列(这里规定每一个拆分出来的特殊序列都是一个「整体」,不能继续进行拆分),那么我们可以「分别」进一步考虑每一个特殊序列,即把某个特殊序列的首位 1 和末位 0 移除后,递归地进行相同的拆分操作。

在递归返回后,我们可以进行「合并」操作:将所有的特殊序列按照字典序进行降序排序,再拼接起来,就可以得到字典序最大的字符串。由于每一次我们可以交换两个相邻的特殊序列,因此按照冒泡排序的方法,我们可以将这些特殊序列任意进行的排列,也就一定能得到字典序最大的字符串。

细节

在编写代码时,我们可以使用一个计数器,并从头遍历给定的字符串。当我们遇到 1 时计数器加 1,遇到 0 时计数器减 1。当计数器为 0 时,我们就拆分除了一个「整体」的特殊序列。

当递归到的字符串长度小于等于 2 时,说明字符串要么为空,要么为 10,此时字符串就是字典序最大的结果,可以直接返回。

代码:

class Solution {
public:
    string makeLargestSpecial(string s) {
        if (s.size() <= 2) {
            return s;
        }
        int cnt = 0, left = 0;
        vector<string> subs;
        for (int i = 0; i < s.size(); ++i) {
            if (s[i] == '1') {
                ++cnt;
            }
            else {
                --cnt;
                if (cnt == 0) {
                    subs.push_back("1" + makeLargestSpecial(s.substr(left + 1, i - left - 1)) + "0");
                    left = i + 1;
                }
            }
        }

        sort(subs.begin(), subs.end(), greater<string>{});
        string ans = accumulate(subs.begin(), subs.end(), ""s);
        return ans;
    }
};

执行用时:0 ms, 在所有 C++ 提交中击败了100.00%的用户
内存消耗:6.3 MB, 在所有 C++ 提交中击败了19.70%的用户
复杂度分析
时间复杂度:O(n2),其中 n 是字符串 s 的长度。在最坏的情况下,s 由 n/2 个 1 接着 n/2 个 0 拼接而成,每次递归仅减少 2 的字符串长度,需要进行 n/2 次递归。同时每次递归需要 O(n) 的时间进行拼接并返回答案,因此总时间复杂度为O(n2)。
空间复杂度:O(n),即为递归需要的栈空间以及存储递归返回的字符串需要的临时空间。
author:LeetCode-Solution

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

千北@

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值