LeetCode 791中等哈希/排序
自定义字符串排序 图解题解
这道题到底在问什么
order 中字符互不相同,给出字符的优先级。重排 s,使出现在 order 里的字符按 order 的相对顺序排列,其余字符保留在后面。
- 输入
- order="cba", s="abcd"
- 输出
- "cbad" (c、b、a 在前,d 在后)
- 输入
- order="bcafg", s="abcd"
- 输出
- "bcad" (d 不在 order 中,放末尾)
最优解:一步一步想明白
- 3记住「先数次数,再按 order 成串输出,最后补 order 外字符」,下面每帧都在套它。
- 4第一段:从左到右扫 s,每遇到一个字符,就在「字符 → 出现次数」表里给它加一。
- 5扫到第 0 个,是字符 'a'(绿色是它之前出现过的位置)。给它的出现次数加一。
- 6字符 'a' 的出现次数更新到 1(高亮行)。继续往后扫。
- 7扫到第 1 个,是字符 'b'(绿色是它之前出现过的位置)。给它的出现次数加一。
- 8字符 'b' 的出现次数更新到 1(高亮行)。继续往后扫。
- 9扫到第 2 个,是字符 'a'(绿色是它之前出现过的位置)。给它的出现次数加一。
- 10字符 'a' 的出现次数更新到 2(高亮行)。继续往后扫。
- 11扫到第 3 个,是字符 'c'(绿色是它之前出现过的位置)。给它的出现次数加一。
- 12字符 'c' 的出现次数更新到 1(高亮行)。继续往后扫。
- 13扫到第 4 个,是字符 'b'(绿色是它之前出现过的位置)。给它的出现次数加一。
- 14字符 'b' 的出现次数更新到 2(高亮行)。继续往后扫。
- 15扫到第 5 个,是字符 'd'(绿色是它之前出现过的位置)。给它的出现次数加一。
- 16字符 'd' 的出现次数更新到 1(高亮行)。继续往后扫。
- 17第一段扫完。各字符出现次数是 'a'→2,'b'→2,'c'→1,'d'→1。接下来第二段:按 order="cab" 的顺序把字符成串输出。
- 18第二段:沿着 order="cab" 从左到右走。每个字符在表里有几个,就连续输出几个,然后把它的次数清零。
- 19order 走到 'c',计数表里有 1 个 'c'(绿色是它在 s 中的位置)。一次性输出 1 个 'c',输出串变成 "c"。
- 20把 'c' 的次数清零(高亮行已归 0,灰色表示这些位置已消化)。order 里没有重复字符,'c' 不会再被处理。
- 21order 走到 'a',计数表里有 2 个 'a'(绿色是它在 s 中的位置)。一次性输出 2 个 'a',输出串变成 "caa"。
- 22把 'a' 的次数清零(高亮行已归 0,灰色表示这些位置已消化)。order 里没有重复字符,'a' 不会再被处理。
- 23order 走到 'b',计数表里有 2 个 'b'(绿色是它在 s 中的位置)。一次性输出 2 个 'b',输出串变成 "caabb"。
- 24把 'b' 的次数清零(高亮行已归 0,灰色表示这些位置已消化)。order 里没有重复字符,'b' 不会再被处理。
- 25order="cab" 全部走完,输出串是 "caabb"。但表里 'd' 的次数还没清零,因为它不在 order 里。第三步:再扫一遍 s,把这些剩下的字符补到末尾。
- 26扫到 s[5]='d',它不在 order 里、表里还剩 1 个(红色是它的位置)。直接把 1 个 'd' 补到输出串末尾 → "caabbd",再清零。
- 27表里所有次数都清零了,说明 s 的每个字符都进了输出串。最终重排结果 = "caabbd"。order 里的 'c'、'a'、'b' 按优先级在前,'d' 在最后。
⚠️ 容易写错的地方
✗ 错:用比较器把 s 真排序
✓ 对:不必排序,按 order 顺序成串输出即可
排序是 O(n log n),计数法只要 O(n + |order|)
✗ 错:忘记输出 order 里没有的字符
✓ 对:order 外的字符要原样补到末尾
题目要求其余字符保留在后面,漏掉会丢字符
✗ 错:同一字符可能出现多次只输出一个
✓ 对:次数是几就连续输出几个
count 记的是出现次数,要成串输出而非只输出一次
完整代码(Python / C++ / Java)
Python
class Solution:
def customSortString(self, order: str, s: str) -> str:
cnt = {ch: 0 for ch in set(s)}
for ch in s:
cnt[ch] = cnt.get(ch, 0) + 1
ans = []
for ch in order:
ans.append(ch * cnt.get(ch, 0))
cnt[ch] = 0
for ch in s:
if cnt.get(ch, 0):
ans.append(ch * cnt[ch])
cnt[ch] = 0
return ''.join(ans)C++
#include <string>
#include <vector>
using namespace std;
class Solution {
public:
string customSortString(string order, string s) {
vector<int> cnt(26);
for (char c : s) cnt[c - 'a']++;
string ans;
for (char c : order) {
ans.append(cnt[c - 'a'], c);
cnt[c - 'a'] = 0;
}
for (char c : s) if (cnt[c - 'a']) {
ans.append(cnt[c - 'a'], c);
cnt[c - 'a'] = 0;
}
return ans;
}
};Java
import java.util.*;
class Solution {
public String customSortString(String order, String s) {
int[] cnt = new int[26];
for (char c : s.toCharArray()) cnt[c - 'a']++;
StringBuilder ans = new StringBuilder();
for (char c : order.toCharArray()) {
while (cnt[c - 'a']-- > 0) ans.append(c);
}
for (char c : s.toCharArray()) {
while (cnt[c - 'a']-- > 0) ans.append(c);
}
return ans.toString();
}
}复杂度
时间
O(n + |order|)
n 是 s 长度,扫 s 计数 + 扫 order 输出 + 扫 s 补尾
空间
O(1)
计数表最多 26 个小写字母,常数级
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 自定义字符串排序 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
如果改用自定义比较器排序会怎样?+
可以:给 order 里每个字符一个优先级 rank,order 外字符给一个最大 rank,按 (rank, 原顺序) 排序。结果正确但时间是 O(n log n),比计数法慢。面试时先说计数法 O(n + |order|) 最优,再补一句比较器是更通用的写法。
这题考的核心是什么?+
识别「字符集很小(26 个)」这个信号:一旦字符集有限,计数表就能把「排序」降成线性扫描。很多字符串重排、频率统计题都能套这个计数思路。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄声线把 自定义字符串排序 一步步讲透(全站 0 节语音精讲,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。