题目
难度困难
特殊的二进制序列是具有以下两个性质的二进制序列:
- 0 的数量与 1 的数量相等。
- 二进制序列的每一个前缀码中 1 的数量要大于等于 0 的数量。
给定一个特殊的二进制序列 S,以字符串形式表示。定义一个操作 为首先选择 S 的两个连续且非空的特殊的子串,然后将它们交换。(两个子串为连续的当且仅当第一个子串的最后一个字符恰好为第二个子串的第一个字符的前一个字符。)
在任意次数的操作之后,交换后的字符串按照字典序排列的最大的结果是什么?
示例 1:
输入: S = "11011000"
输出: "11100100"
解释:
将子串 "10" (在S[1]出现) 和 "1100" (在S[3]出现)进行交换。
这是在进行若干次操作后按字典序排列最大的结果。
思路
这道 hard 题感觉还是挺难的,主要问题是刚开始的思路不好找,要直接 “发现” 的点有点多
这也是 hard 题的一个特点吧
首先是发现特殊二进制序列特殊在什么地方,分析他的两个特点后可以往 “合法的括号序列” 这上面想,对照发现两者的性质是完全一样的,这个类比很有助于理解
理解好题目新定义的东西后就要往如何实现排序上思考了:可以将给定的序列看成若干特殊子串的组合,将这些特殊子串按字典序排序后重新组合就能得到想要的排列
有一个忽略的前提条件被我们忽略了,我们需要保证原序列拆分后的特殊子串本身是按照字典序排列的最大的结果,才能保证这些子串排序后重新组合得到的序列是按照字典序排列的最大的结果
!!这不正是和题目中所要求的目标完全一致吗,这就是一个典型用递归解决的问题
既然是递归操作就要先考虑递归的边界:对于长度小于 2 的字符串,它只可能是空串或者 10 这两种情况,它们本身已经没办法进行交换操作了,直接返回
对于一个特殊子串,首字符肯定是 1,因为这是长度为 1 的前缀;尾字符肯定是 0 ,因为 0 和 1 数目相等的同时任何前缀中 1 的数目都大于等于 0 就意味着任何尾缀中 1 的数目都小于等于 0 ,尾字符是长度为 1 的尾缀
由于开头的 1 和结尾的 0 是一个合法的特殊序列所必要的,所以将开头和结尾删除后对内部的特殊子串进行排列后再补回来是不会影响最终结果的。而这一步操作就能实现在递归过程中,使得要计算的目标序列,从长特殊二进制序列往短二进制序列、大问题到子问题这个方向分割
那就只剩下最后一个小问题了:如何将将给定的序列看成若干特殊子串的组合?
既然都联想到了合法的括号序列,就可以用同样的方法
设定一个计数器,遇到 1 加 1,遇到 0 减 1,当计数器为 0 表明刚发现一个特殊的二进制序列
问题的各个点我们都解决了,现在需要做的就是串联一下,编写出合适的代码 :)
实现
func makeLargestSpecial(s string) string {
if len(s) < 2 {
return s
}
cnt, st := 0, 0
sList := []string{}
for i, pos := range s {
if pos == '1' {
cnt++
} else {
cnt--
if cnt == 0 {
sList = append(sList, "1" + makeLargestSpecial(s[st+1:i]) + "0")
st = i + 1
}
}
}
sort.Slice(sList, func(i, j int) bool {
return sList[i] > sList[j]
})
return strings.Join(sList, "")
}