1.lc761:特殊的二进制序列
来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/special-binary-string
题目:特殊的二进制序列是具有以下两个性质的二进制序列:
0 的数量与 1 的数量相等。
二进制序列的每一个前缀码中 1 的数量要大于等于 0 的数量。
给定一个特殊的二进制序列 S,以字符串形式表示。定义一个操作 为首先选择 S 的两个连续且非空的特殊的子串,然后将它们交换。(两个子串为连续的当且仅当第一个子串的最后一个字符恰好为第二个子串的第一个字符的前一个字符。)
在任意次数的操作之后,交换后的字符串按照字典序排列的最大的结果是什么?
示例 :
输入: S = "11011000"
输出: "11100100"
解释:
将子串 "10" (在S[1]出现) 和 "1100" (在S[3]出现)进行交换。
这是在进行若干次操作后按字典序排列最大的结果。
思路:【官方题解】
作者:LeetCode-Solution
链接:https://leetcode.cn/problems/special-binary-string/solution/te-shu-de-er-jin-zhi-xu-lie-by-leetcode-sb7ry/
来源:力扣(LeetCode)
对于一个特殊序列而言,它一定以 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 移除后,递归地进行相同的拆分操作。
在递归返回后,我们可以进行「合并」操作:将所有的特殊序列按照字典序进行降序排序,再拼接起来,就可以得到字典序最大的字符串。由于每一次我们可以交换两个相邻的特殊序列,因此按照冒泡排序的方法,我们可以将这些特殊序列任意进行的排列,也就一定能得到字典序最大的字符串。
细节:在编写代码时,我们可以使用一个计数器,并从头遍历给定的字符串。当我们遇到 11 时计数器加 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;
}
};