题目描述
思路解析动画文字版
记住「先判 max ≤ ⌊(n+1)/2⌋,再按频次降序先填偶位后填奇位」,下面每帧都在套它。limit = ⌊(6+1)/2⌋ = 3。
第一步先数清每个字符各出现几次,结果记在右边的频次表里。
扫到第 0 个字符 'a',它的计数变成 1。右边频次表实时更新。
扫到第 1 个字符 'a',它的计数变成 2。右边频次表实时更新。
扫到第 2 个字符 'a',它的计数变成 3。右边频次表实时更新。
扫到第 3 个字符 'b',它的计数变成 1。右边频次表实时更新。
扫到第 4 个字符 'b',它的计数变成 2。右边频次表实时更新。
扫到第 5 个字符 'c',它的计数变成 1。右边频次表实时更新。
数完后看最多的 'a'(3 个)和 limit=3 比:3 ≤ 3,说明排得开,继续填。
下面这条 6 格的带子就是答案的槽位('·' 表示空)。按频次从多到少:'a' → 'b' → 'c',先往偶数位塞。
轮到 'a',按当前 idx 把它放到下标 0(紫色框)。
'a' 落在下标 0(绿色),idx 每次 +2 跳着走,保证同一个字符之间至少隔一格。
轮到 'a',按当前 idx 把它放到下标 2(紫色框)。
'a' 落在下标 2(绿色),idx 每次 +2 跳着走,保证同一个字符之间至少隔一格。
轮到 'a',按当前 idx 把它放到下标 4(紫色框)。
'a' 落在下标 4(绿色),idx 每次 +2 跳着走,保证同一个字符之间至少隔一格。
偶数位 0,2,4 都填满了,下标超出末尾,于是跳回下标 1 开始填奇数位。紫色框就是 'b' 要落的新位置。
'b' 落在下标 1(绿色),idx 每次 +2 跳着走,保证同一个字符之间至少隔一格。
轮到 'b',按当前 idx 把它放到下标 3(紫色框)。
'b' 落在下标 3(绿色),idx 每次 +2 跳着走,保证同一个字符之间至少隔一格。
轮到 'c',按当前 idx 把它放到下标 5(紫色框)。
'c' 落在下标 5(绿色),idx 每次 +2 跳着走,保证同一个字符之间至少隔一格。
全部填完,得到 "ababac"。从头检查相邻:'a'≠'b','b'≠'a','a'≠'b','b'≠'a','a'≠'c',全部不同,合法。
反例 s="aaab":'a' 有 3 个,超过 limit=2,无论怎么排都有两个 'a' 挨着,所以一开始就返回空串。
边界先想清:单字符直接返回、相同字符超半数返回空、持平时交替排。
两个高频追问:堆 vs 计数数组的取舍,以及推广到间隔 k 的做法。
参考代码
class Solution: def reorganizeString(self, s: str) -> str: cnt = [0] * 26 for ch in s: cnt[ord(ch) - 97] += 1 n = len(s) if max(cnt) > (n + 1) // 2: return '' chars = sorted(range(26), key=lambda i: (-cnt[i], i)) ans = [''] * n idx = 0 for c in chars: for _ in range(cnt[c]): if idx >= n: idx = 1 ans[idx] = chr(c + 97) idx += 2 return ''.join(ans)复杂度
- 时间:O(n + 26 log 26),扫一遍数频次 O(n),对 26 个字母排序是常数级,写答案 O(n)
- 空间:O(1),计数数组与排序数组都只有 26,与 n 无关(答案串不计入)
易错点
面试追问把动画讲成自己的话
追问为什么这里用计数数组 + 排序,而不用大顶堆?
追问如果要求相邻字符间隔至少 k(LC358 任务调度类),还能这么填吗?
这道题到这就讲完了。动画和文字是同一套思路——别光看,关掉页面自己默写一遍。然后顺着主线继续:
可以到达的最远建筑
LeetCode 1642 · 中等 · 沿着 堆套路 继续往下推进
把这道题真正学会,再走
图解算法年卡 ¥99 /年
- ✓本题每步动画的吴师兄语音讲解(全站陆续覆盖)
- ✓小欧带 8 步通关训练——追问到不看答案也能写对、能 30 秒讲给面试官听
- ✓学习报告,记录每道题的掌握程度
76k+ GitHub Star · 吴师兄开源图解算法,几十万开发者在看的算法讲解
想成体系刷透这类套路?去图解算法专题