将字符串中的元音字母排序 图解题解
这道题到底在问什么
- 输入
- s = "lEetcOde"
- 输出
- "lEOtcede"
- 输入
- s = "lYmpH"
- 输出
- "lYmpH"(没有元音,原样返回)
最优解:一步一步想明白
- 3记牢这套三步法:收集元音、单独排序、按序回填。辅音自始至终待在原地,我们只搬动元音。下面从收集元音开始,一格一格扫过去。
- 4先认一下画面。上面这排就是字符串 lEetcOde,一共 8 个字符。我用一个指针从左往右扫,紫色就是它当前站的位置。扫的时候把元音格子点亮成绿色、收进右边的元音篮子,辅音格子压成灰色留在原地。现在篮子还是空的,咱们从下标 0 开始。
- 5指针走到下标 0,这里是 l。它是辅音,不参与排序,直接把这一格压成灰色,记住它就钉在下标 0 这个位置,后面谁都不许挪它。指针继续往右。
- 6指针走到下标 1,这里是 E。它是元音,把它点亮成绿色,并且丢进元音篮子。这是收到的第 1 个元音,它的 ASCII 值是 69。别管它现在什么大小写,先原封不动收进来,排序留到第二步。
- 7指针走到下标 2,这里是 e。它是元音,把它点亮成绿色,并且丢进元音篮子。这是收到的第 2 个元音,它的 ASCII 值是 101。别管它现在什么大小写,先原封不动收进来,排序留到第二步。
- 8指针走到下标 3,这里是 t。它是辅音,不参与排序,直接把这一格压成灰色,记住它就钉在下标 3 这个位置,后面谁都不许挪它。指针继续往右。
- 9指针走到下标 4,这里是 c。它是辅音,不参与排序,直接把这一格压成灰色,记住它就钉在下标 4 这个位置,后面谁都不许挪它。指针继续往右。
- 10指针走到下标 5,这里是 O。它是元音,把它点亮成绿色,并且丢进元音篮子。这是收到的第 3 个元音,它的 ASCII 值是 79。别管它现在什么大小写,先原封不动收进来,排序留到第二步。
- 11指针走到下标 6,这里是 d。它是辅音,不参与排序,直接把这一格压成灰色,记住它就钉在下标 6 这个位置,后面谁都不许挪它。指针继续往右。
- 12指针走到下标 7,这里是 e。它是元音,把它点亮成绿色,并且丢进元音篮子。这是收到的第 4 个元音,它的 ASCII 值是 101。别管它现在什么大小写,先原封不动收进来,排序留到第二步。
- 13一整趟扫完了。字符串里 4 个元音全进了篮子,按遇到的先后是 E e O e,ASCII 分别是 69、101、79、101。辅音 l t c d 已经乖乖钉在灰色位置上。接下来只对篮子里这四个元音动手,把它们按 ASCII 从小到大排好。
- 14排序前先想清一件事:比的是 ASCII 值,不是字母表本身。大写字母 A 到 Z 的 ASCII 是 65 到 90,小写 a 到 z 是 97 到 122,所以任何大写字母的 ASCII 都小于任何小写字母。这里 E 是 69、O 是 79,都比小写 e 的 101 小。按 ASCII 从小到大,顺序就是 E、O、e、e,两个大写排在前面,两个小写 e 排在后面。
- 15排序完成。篮子里的元音现在按 ASCII 非递减排成了 E、O、e、e。你看,原来收集顺序是 E e O e,现在两个大写 E 和 O 被提到了前面,两个小写 e 落到后面。这就是第二步的成果,接下来第三步把它们按这个顺序依次填回元音位置。
- 16现在第三步,从下标 0 往右扫。规则很简单:碰到辅音,原样不动;碰到元音位置,就从排好序的篮子里从头开始,依次取出一个填进去。我用一个取用指针 j 指着篮子,现在 j 指着第 1 个元音 E。开始扫。
- 17指针到下标 0,这里是辅音 l。辅音不参与排序,原样保留,篮子的取用指针 j 一动不动。指针继续往右。
- 18指针到下标 1,这是个元音位。从排好序的篮子里取出第 1 个元音 E,填到这一格。原来这里是 E,现在换成 E。取用指针 j 往后挪一格。
- 19指针到下标 2,这是个元音位。从排好序的篮子里取出第 2 个元音 O,填到这一格。原来这里是 e,现在换成 O。取用指针 j 往后挪一格。
- 20指针到下标 3,这里是辅音 t。辅音不参与排序,原样保留,篮子的取用指针 j 一动不动。指针继续往右。
- 21指针到下标 4,这里是辅音 c。辅音不参与排序,原样保留,篮子的取用指针 j 一动不动。指针继续往右。
- 22指针到下标 5,这是个元音位。从排好序的篮子里取出第 3 个元音 e,填到这一格。原来这里是 O,现在换成 e。取用指针 j 往后挪一格。
- 23指针到下标 6,这里是辅音 d。辅音不参与排序,原样保留,篮子的取用指针 j 一动不动。指针继续往右。
- 24指针到下标 7,这是个元音位。从排好序的篮子里取出第 4 个元音 e,填到这一格。原来这里是 e,现在换成 e。取用指针 j 往后挪一格。
- 25扫完了。四个元音位从左到右依次填进了 E、O、e、e,辅音 l、t、c、d 始终待在原地没挪窝。最终字符串是 lEOtcede,和一开始说的对上了。回头看,大写 E 和 O 因为 ASCII 小,排到了前面的元音位,小写 e 落到了后面,辅音框架纹丝不动。
⚠️ 容易写错的地方
✗ 错:判元音时只写小写 a e i o u,漏了大写
✓ 对:统一转小写再判,或把大小写十个元音都列上
题目明说大写元音也算,像 E、O、A 这些漏判就会被当辅音钉住不排,答案直接错
✗ 错:把大写小写当同一个字母排,按字母表比
✓ 对:严格按 ASCII 值比,大写整体小于小写
ASCII 里大写 65 到 90、小写 97 到 122,大写永远排在小写前面。按字母表把 E 和 e 视作相等会排错顺序
✗ 错:排序时连辅音一起排,或挪动了辅音位置
✓ 对:只把元音抠出来排,辅音下标全程不变
题目要求辅音留在原位,只有元音重排。把辅音也卷进排序会彻底打乱原串结构
✗ 错:回填时按从大到小或收集顺序填
✓ 对:按 ASCII 从小到大,用一个递增指针依次取
要求是非递减排列,必须升序回填;拿收集顺序或降序填都不满足元音有序
完整代码(Python / C++ / Java)
Python
from __future__ import annotations
from typing import *
from collections import *
from functools import *
from itertools import *
from math import *
from heapq import *
from bisect import *
class Solution:
def sortVowels(self, s: str) -> str:
vs = [c for c in s if c.lower() in "aeiou"]
vs.sort()
cs = list(s)
j = 0
for i, c in enumerate(cs):
if c.lower() in "aeiou":
cs[i] = vs[j]
j += 1
return "".join(cs)C++
#include <algorithm>
#include <array>
#include <cctype>
#include <climits>
#include <cmath>
#include <deque>
#include <functional>
#include <iostream>
#include <limits>
#include <map>
#include <numeric>
#include <queue>
#include <set>
#include <sstream>
#include <stack>
#include <string>
#include <tuple>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>
using namespace std;
class Solution {
public:
string sortVowels(string s) {
string vs;
for (auto c : s) {
char d = tolower(c);
if (d == 'a' || d == 'e' || d == 'i' || d == 'o' || d == 'u') {
vs.push_back(c);
}
}
sort(vs.begin(), vs.end());
for (int i = 0, j = 0; i < s.size(); ++i) {
char d = tolower(s[i]);
if (d == 'a' || d == 'e' || d == 'i' || d == 'o' || d == 'u') {
s[i] = vs[j++];
}
}
return s;
}
};Java
import java.util.*;
class Solution {
public String sortVowels(String s) {
List<Character> vs = new ArrayList<>();
char[] cs = s.toCharArray();
for (char c : cs) {
char d = Character.toLowerCase(c);
if (d == 'a' || d == 'e' || d == 'i' || d == 'o' || d == 'u') {
vs.add(c);
}
}
Collections.sort(vs);
for (int i = 0, j = 0; i < cs.length; ++i) {
char d = Character.toLowerCase(cs[i]);
if (d == 'a' || d == 'e' || d == 'i' || d == 'o' || d == 'u') {
cs[i] = vs.get(j++);
}
}
return String.valueOf(cs);
}
}复杂度
时间
O(n log n)
设字符串长度为 n,元音个数 k 不超过 n。两趟线性扫描各 O(n);中间对 k 个元音排序是 O(k log k),最坏 k 等于 n 就是 O(n log n)。排序这一步压过线性扫描,所以总时间 O(n log n)
空间
O(n)
按峰值算。元音篮子最多装 n 个字符,是 O(n);结果串也要 O(n)。排序本身的额外开销分语言:C plus plus 和 Java 通常是 O(log n) 的递归栈,Python 的 Timsort 最坏 O(n)。合起来空间是 O(n)
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 将字符串中的元音字母排序 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
这题的核心思路一句话怎么说?+
辅音位置固定不用管,只把元音抠出来按 ASCII 排好,再按顺序填回原来的元音位置。实现上就是收集、排序、回填三步,两趟线性扫描加一次排序。
大小写元音怎么统一处理?+
判断是不是元音时,把字符转成小写再看在不在 a e i o u 里,这样大写元音也能识别。但排序时用字符本身的 ASCII 值比,不转大小写,于是大写字母会自然排在小写前面,正好符合按 ASCII 非递减的要求。
复杂度是多少,能不能不排序?+
时间 O(n log n),瓶颈是给元音排序;空间 O(n) 存篮子和结果串。因为元音只有 a e i o u 十种(含大小写),其实可以用计数排序:统计每种元音出现几次,回填时按 ASCII 从小到大依次吐出,时间能降到 O(n),这是进阶优化,但通用排序写起来更直接。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄声线把 将字符串中的元音字母排序 一步步讲透(全站 0 节语音精讲,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。