LeetCode 1417. 重新格式化字符串
题目描述


题意分析
输入只含小写英文字母和数字,需要重新排列全部字符,使相邻两位的类型不同,也就是字母与数字严格交替。允许任意合法排列,不要求保持原顺序。
每个原字符都必须恰好保留一次,不能为了满足交替而删掉某些字符。无法安排时返回空字符串;判断的是字符类型,不是两个相邻字符的值是否相同。
解法:两组缓冲交替拼接
核心思路
[!blue]
先将字符分为字母组和数字组。交替序列每两个位置各使用一种类型,只有最后可能多出一个首类型,因此两组数量差不能超过一;数量差更大就不可能容纳全部字符。
这个条件也足够:两组等长时任选一种开头,轮流各放一个即可;某组多一个时,让它开头和结尾,仍然轮流放置,就能把所有字符都用完。
代码将较长组统一命名为
first,另一组为second。遍历first,每次先追加它的当前字符,再在second还有对应位置时追加另一个字符。因为长度差最多一,second只可能在最后一轮耗尽,不会在中途破坏交替。两个分组缓冲保留各自内部的原顺序,虽然题目不强制这一点,但这样直接依次读取就能保证不丢字符、不重复使用。每个字符进入对应组一次、再进入结果一次,构造完成后自然满足全部约束。
解题步骤
- 扫描字符串,按类型分别放入字母缓冲和数字缓冲。
- 两组数量差大于一时,返回空字符串。
- 令较长组为
first,较短组为second,等长时任选。- 遍历
first,先放一个首类型字符,有对应项时再放一个另一类型字符。- 返回完整结果,多数类若多一个就自然留在最后。
代码实现
class Solution {
public String reformat(String s) {
StringBuilder letters = new StringBuilder();
StringBuilder digits = new StringBuilder();
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (c >= '0' && c <= '9') {
digits.append(c);
} else {
letters.append(c);
}
}
// 交替序列的两类数量最多相差一。
if (Math.abs(letters.length() - digits.length()) > 1) {
return "";
}
StringBuilder first = letters;
StringBuilder second = digits;
// 数量较多的一类必须先放,统一成 first 和 second。
if (digits.length() > letters.length()) {
first = digits;
second = letters;
}
StringBuilder answer = new StringBuilder(s.length());
for (int i = 0; i < first.length(); i++) {
answer.append(first.charAt(i));
// 短组只可能在最后耗尽,保留多数类最后一个字符。
if (i < second.length()) {
answer.append(second.charAt(i));
}
}
return answer.toString();
}
}
func reformat(s string) string {
letters := make([]byte, 0)
digits := make([]byte, 0)
for i := 0; i < len(s); i++ {
if s[i] >= '0' && s[i] <= '9' {
digits = append(digits, s[i])
} else {
letters = append(letters, s[i])
}
}
// 交替序列的两类数量最多相差一。
if len(letters)-len(digits) > 1 || len(digits)-len(letters) > 1 {
return ""
}
first, second := letters, digits
// 数量较多的一类必须先放,统一成 first 和 second。
if len(digits) > len(letters) {
first, second = digits, letters
}
answer := make([]byte, 0, len(s))
for i := 0; i < len(first); i++ {
answer = append(answer, first[i])
// 短组只可能在最后耗尽,保留多数类最后一个字符。
if i < len(second) {
answer = append(answer, second[i])
}
}
return string(answer)
}
复杂度分析
设字符串长度为 $n$。
- 时间复杂度:$O(n)$,分组和构造各扫描一次全部字符。
- 辅助空间复杂度:$O(n)$,两个分组缓冲合计保存全部字符,构造结果也需要线性空间。
关键点总结
[!green]
- 数量差不超过一,是全部字符能够交替的充要条件。
- 多数类型必须放在首尾,等长时两种开头都可行。
- 遍历长组并按需插入短组,自动处理末尾多出的一个字符。
易错点总结
[!yellow]
- 固定让字母开头,会在数字多一个时无法安排完所有字符。
- 只允许两组等长,会拒绝差一个以及单字符的合法输入。
- 只遍历短组,会遗漏多数类最后的一个字符。
- 只检查一个方向的差值,会漏掉另一类型过多的无解情况。
- 相邻字母即使互不相同也不合法,要求交替的是类型。
相似题目
| 题目 | 难度 | 关联与区别 |
|---|---|---|
| 922. 按奇偶排序数组 II | 简单 | 同样把两类元素交替放入不同位置,本题两类数量可相差1,需要让较多的一类先放。 |
转载与许可
许可
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!