跳到主要内容

761. 特殊的二进制字符串

题目描述​

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

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

给定一个特殊的二进制序列 S,以字符串形式表示。定义一个操作:对于两个特殊字符串 a 和 b,如果 a 的字典序大于 b,可以将 ab 替换为 ba。

要求返回经过任意次操作后,能得到的字典序最大的字符串。

示例 1:

输入:s = "11011000"
输出:"11100100"
解释:
"11011000" 去掉最外层括号后,内部是 "101100",可以分解为 "10" 和 "1100" 两个特殊子串
"1100" 的字典序大于 "10",交换后内部变为 "1100" + "10" = "110010"
装回外层括号得到 "1" + "110010" + "0" = "11100100"

示例 2:

输入:s = "10"
输出:"10"

解题思路​

第一步:理解问题本质​

特殊二进制字符串 = 合法括号序列

  • 把 1 看作左括号 (
  • 把 0 看作右括号 )

这样,特殊二进制字符串的条件就转化为:

  1. 左右括号数量相等
  2. 任意前缀中左括号数量 >= 右括号数量

这正是合法括号序列的定义!

第二步:理解操作的本质​

题目允许的操作:如果 a > b(字典序),可以将 ab 替换为 ba。

关键洞察: 这个操作实际上就是交换相邻的特殊子串!

通过不断交换,可以将子串按任意顺序排列。因此,问题转化为: 将特殊字符串分解为若干子串,排序后拼接得到最大字典序。

第三步:分解特殊字符串​

不可再分的特殊子串:

  • 类似于「原子」的概念,不能再分解为更小的特殊子串
  • 在括号表示中,就是最外层的匹配括号对

分解方法:

  • 使用「平衡因子」diff = 1 的个数 - 0 的个数
  • 当 diff 从 0 变回 0 时,就找到了一个完整的特殊子串

第四步:递归处理​

对于每个特殊子串:

  1. 去掉外层括号(首位的 1 和末位的 0)
  2. 递归处理内部
  3. 加上外层括号
  4. 所有子串排序后拼接
class Solution:
def makeLargestSpecial(self, s: str) -> str:
# 空串和 "10" 都没有可交换的内部,本身就是最优解
if len(s) <= 2:
return s

substrings = []
# diff = 1 的个数 - 0 的个数,相当于括号序列的平衡因子
diff = 0
start = 0

for i, ch in enumerate(s):
if ch == '1':
diff += 1
else:
diff -= 1
if diff == 0:
# 找到一个完整的特殊子串 [start, i]
# 首位的 1 和末位的 0 是最外层括号,只有内部可以重排
inner = self.makeLargestSpecial(s[start+1:i])
# 内部调成最大后,再把外层括号装回去
substrings.append("1" + inner + "0")
# 下一个子串从 i + 1 开始
start = i + 1

# 子串之间可以自由交换,把大的排前面才能让整体字典序最大
substrings.sort(reverse=True)
return ''.join(substrings)

完整代码实现​

算法可视化 · 逐帧回放这道题的代码6 帧 · 录制输入 "11011000"
class Solution:
"""
特殊的二进制字符串 - 递归 + 排序

核心思路:
1. 特殊二进制字符串可以看作「合法括号序列」
2. 分解为不可再分的特殊子串
3. 每个子串去掉外层括号后递归处理
4. 按字典序降序排列后拼接

时间复杂度: O(n^2 log n)
空间复杂度: O(n)
"""
def makeLargestSpecial(self, s: str) -> str:
if len(s) <= 2:
return s

substrings = []
diff = 0
start = 0

for i, ch in enumerate(s):
if ch == '1':
diff += 1
else:
diff -= 1
if diff == 0:
inner = self.makeLargestSpecial(s[start+1:i])
substrings.append("1" + inner + "0")
start = i + 1

substrings.sort(reverse=True)
return ''.join(substrings)

示例推演​

以 S = "11011000" 为例。diff 记录「1 比 0 多几个」,每回到 0 就切出一个特殊子串。

第一步:切出最外层子串

下标字符diff说明
011
112
201
312
413
502
601
700diff 归零,切出 [0, 7]

中途 diff 从未归零,说明整个串只有一个最外层括号对: "11011000" = "1" + "101100" + "0"。

第二步:递归处理内部 "101100"

  • 下标 0..1 的 "10":长度为 2,直接命中基本情况,返回 "10"
  • 下标 2..5 的 "1100":去掉外层得到 "10",递归返回 "10",再装回外层得 "1100"

这一步得到两个子串 ["10", "1100"]。按字典序降序排列("1100" > "10"), 拼接成 "110010"。

第三步:装回最外层

"1" + "110010" + "0" = "11100100",与示例输出一致。


复杂度分析​

解法时间复杂度空间复杂度说明
递归+排序O(n^2 log n)O(n)每层递归都需要遍历和排序

说明:

  • n 是字符串长度
  • 最坏情况下(如 "111...000..."),递归深度为 O(n)
  • 每层需要 O(n) 的遍历和 O(n log n) 的排序

易错点总结​

1. 分解条件的判断​

# 当 diff 回到 0 时,找到一个完整的特殊子串
if diff == 0:
# 子串范围是 [start, i]
# 内部是 [start+1, i-1],去掉外层括号
# 中途 diff 从未归零,说明这段不能再拆,是最外层的括号对

2. 递归的边界条件​

if len(s) <= 2:
return s # "10" 是最简单的特殊字符串
# 少了这个出口,单字符 "1" 会被拆成空串返回,"10" 也要多绕一层递归

3. 排序的方向​

# 降序排列得到最大字典序
substrings.sort(reverse=True)
# 升序会得到最小字典序,方向反了答案就完全相反

扩展思考​

1. 与括号问题的联系​

这道题本质上是括号问题的变形:

  • 特殊二进制字符串 = 合法括号序列
  • 分解子串 = 找最外层括号对
  • 排序 = 通过交换得到最大字典序

2. 如果要求最小字典序?​

只需将排序改为升序即可:

substrings.sort()  # 升序
# 与求最大字典序相比,只改了排序方向,其余逻辑完全一样

3. 相关题目​


相关题目​

自测​

加载评论中...