2026-08-09:按频率对元音排序。用go语言,给定一个全部由小写英文字母构成的字符串,你需要对其中的元音字母(即 a、e、i、o、u)进行重新排列,而所有非元音字母保持原来的位置和顺序不变。排列的规则是:首先统计每个元音字母在整个字符串中出现的次数,次数越多的元音字母在结果中越靠前;如果两个不同的元音字母出现次数相同,则比较它们各自在原字符串中第一次出现的位置,谁的位置更靠前,谁就在排列结果中排在前面。最后输出经过这样处理的完整字符串。

1 <= s.length <= 100000。

s 由小写英文字母组成。

输入: s = "leetcode"。

输出: "leetcedo"。

解释:

字符串中的元音字母为 ['e', 'e', 'o', 'e'],其出现频率为:e = 3,o = 1。

按出现频率非递增排序后,再放回原来的元音位置,得到 "leetcedo"。

题目来自力扣3913。

解题过程详述

  1. 1.建立元音识别机制
    构造一个长度为 123(包含字符 'z' 的 ASCII 码)的整型数组mp,将其所有元素初始化为 0。然后将字符'a''e''i''o''u'对应的数组位置分别标记为 1、2、3、4、5。这样,后续在遍历字符串时,只需将当前字符的mp值减去 1,便能得到它在计数数组中的索引(0~4)。如果该字符不是元音(mp值为 0),则计算出的索引为 -1,可据此快速过滤非元音字符。

  2. 2.统计元音频率并记录首次出现顺序

    扫描结束后,cnt中存储了每个元音的出现频率,vowels切片中按首次出现位置保存了所有出现过的元音(长度至多为 5)。

  • • 准备一个长度为 5 的整型数组cnt,用于记录 a、e、i、o、u 各自出现的总次数,初始均为 0。

  • • 准备一个空的字节切片vowels,用于按元音在字符串中第一次出现的顺序存放这些元音字母。

  • • 从头到尾扫描原字符串s中的每一个字符ch

    • • 通过mp[ch] - 1获取该字符的索引x;如果x < 0,说明它不是元音,直接跳过。

    • • 检查cnt[x]是否为 0。若为 0,表示这是该元音字母在字符串中第一次出现,因此将ch追加到vowels切片尾部。

    • • 将cnt[x]的值增加 1,完成对该元音的一次计数。

3.对元音按规则排序
使用稳定排序算法对vowels切片进行排序,排序的比较规则为:取出两元音对应的出现次数,次数大的排在前面。由于排序是稳定的,当两个元音出现次数相同时,它们在vowels切片中的原有相对顺序会被保留。而vowels切片的构建过程保证了它就是按各元音在原字符串中第一次出现的位置顺序排列的,因此稳定排序后,频率相同的元音会天然保持“首次出现位置越靠前越排在前面”的顺序。

4.将排序结果重新放回字符串

  • • 将原字符串s转换为可修改的字节切片t,作为输出结果的骨架。

  • • 初始化一个索引变量j = 0,指向vowels切片中当前正在使用的元音。

  • • 再次从头遍历t的每一个字符位置i

    • • 如果当前字符ch = t[i]对应的mp[ch] == 0,说明该位置是非元音字母,直接保留原字符不变,继续下一个位置。

    • • 如果mp[ch] != 0,说明该位置原本是元音,需要进行替换。将t[i]改为vowels[j],也就是当前应使用的元音。

    • • 找出被填入元音在计数数组中的索引x = mp[t[i]] - 1,然后将该元音的剩余次数cnt[x]减 1。

    • • 如果cnt[x]减少后变为 0,意味着这种元音已经被全部消耗完,于是将j增加 1,切换到vowels中的下一个元音,供后续的元音位置使用。

5.生成最终字符串
将修改完毕的字节切片t转换为字符串并返回。此时,所有非元音位置保持原样,所有元音位置则按照要求(频率非递增,频率相同则按首次出现位置排序)被重新排列后的元音填充。

复杂度分析

  • 时间复杂度
    整个过程主要包含两次对字符串的完整遍历(统计和替换),每次遍历都只进行常数级别的操作(数组访问、比较、赋值等),因此时间复杂度为O(n),其中 n 为字符串的长度。对vowels切片进行排序的操作,因其长度不超过 5,可视为常数时间 O(1)。总时间复杂度为O(n)

  • 额外空间复杂度
    算法中使用了长度为 5 的cnt数组、长度至多为 5 的vowels切片以及一个长度与原字符串相同的字节切片t。前三者的空间占用为 O(1);而t是为了构建修改后的字符串而从输入复制的一份副本,其长度等于 n,需要O(n)的额外空间。因此,总的额外空间复杂度为O(n)

Go完整代码如下:

package main

import (
"fmt"
"slices"
)

var mp = ['z' + 1]int{'a': 1, 'e': 2, 'i': 3, 'o': 4, 'u': 5}

func sortVowels(s string) string {
cnt := [5]int{}
vowels := []byte{} // 长度至多为 5
for _, ch := range s {
x := mp[ch] - 1
if x < 0 {
continue
}
if cnt[x] == 0 {
vowels = append(vowels, byte(ch))
}
cnt[x]++
}

// 把 aeiou 按照出现次数从大到小排序
slices.SortStableFunc(vowels, func(a, b byte) int { return cnt[mp[b]-1] - cnt[mp[a]-1] })

t := []byte(s)
j := 0
for i, ch := range t {
if mp[ch] == 0 {
continue
}
t[i] = vowels[j]
x := mp[t[i]] - 1
cnt[x]--
if cnt[x] == 0 {
j++ // 消耗完了,切换到下一种元音
}
}
return string(t)
}

func main() {
s := "leetcode"
result := sortVowels(s)
fmt.Println(result)
}
打开网易新闻 查看精彩图片

Python完整代码如下:

# -*-coding:utf-8-*-

def sort_vowels(s: str) -> str:
# 元音到索引的映射 (a->0, e->1, i->2, o->3, u->4)
vowel_to_idx = {'a': 0, 'e': 1, 'i': 2, 'o': 3, 'u': 4}
cnt = [0] * 5 # 各元音出现次数
vowels = [] # 出现过的元音,按首次出现顺序

# 统计元音频率,记录首次出现的元音顺序
for ch in s:
if ch in vowel_to_idx:
idx = vowel_to_idx[ch]
if cnt[idx] == 0:
vowels.append(ch)
cnt[idx] += 1

# 稳定排序:频率从大到小,频率相同保持首次出现顺序
vowels.sort(key=lambda c: -cnt[vowel_to_idx[c]])

# 替换元音位置
res = list(s)
j = 0
for i, ch in enumerate(res):
if ch not in vowel_to_idx:
continue
# 用当前排序中的元音替换
res[i] = vowels[j]
idx = vowel_to_idx[res[i]]
cnt[idx] -= 1
if cnt[idx] == 0:
j += 1

return ''.join(res)

if __name__ == '__main__':
test_str = "leetcode"
print(sort_vowels(test_str))
打开网易新闻 查看精彩图片

C++完整代码如下:

  




std::string sortVowels(const std::string& s) {
// 元音字符到索引的映射,索引 0-4 分别对应 a, e, i, o, u
std::array vowelIndex{};
vowelIndex.fill(-1);
vowelIndex['a' - 'a'] = 0;
vowelIndex['e' - 'a'] = 1;
vowelIndex['i' - 'a'] = 2;
vowelIndex['o' - 'a'] = 3;
vowelIndex['u' - 'a'] = 4;

std::array cnt{}; // 各元音的出现次数
std::vector vowels; // 出现过的元音,按首次出现顺序

for (char ch : s) {
int idx = (ch >= 'a' && ch <= 'z') ? vowelIndex[ch - 'a'] : -1;
if (idx < 0) continue; // 非元音则跳过
if (cnt[idx] == 0) {
vowels.push_back(ch);
}
cnt[idx]++;
}

// 稳定排序:频率从大到小,频率相同时保持首次出现的顺序
std::stable_sort(vowels.begin(), vowels.end(),
[&](char a, char b) {
return cnt[vowelIndex[a - 'a']] > cnt[vowelIndex[b - 'a']];
});

std::string t = s;
size_t j = 0;
for (char& ch : t) {
int idx = (ch >= 'a' && ch <= 'z') ? vowelIndex[ch - 'a'] : -1;
if (idx < 0) continue; // 非元音位置不变

ch = vowels[j];
int newIdx = vowelIndex[ch - 'a'];
cnt[newIdx]--;
if (cnt[newIdx] == 0) {
j++; // 当前元音消耗完毕,切换下一个
}
}
return t;
}

int main() {
std::string s = "leetcode";
std::string result = sortVowels(s);
std::cout << result << std::endl;
return 0;
}
打开网易新闻 查看精彩图片

我们相信人工智能为普通人提供了一种“增强工具”,并致力于分享全方位的AI知识。在这里,您可以找到最新的AI科普文章、工具评测、提升效率的秘籍以及行业洞察。 欢迎关注“福大大架构师每日一题”,发消息可获得面试资料,让AI助力您的未来发展。