2026-08-09:按频率对元音排序。用go语言,给定一个全部由小写英文字母构成的字符串,你需要对其中的元音字母(即 a、e、i、o、u)进行重新排列,而所有

45 阅读7分钟

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

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

    • 准备一个长度为 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,完成对该元音的一次计数。

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

  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++完整代码如下:

#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <array>

std::string sortVowels(const std::string& s) {
    // 元音字符到索引的映射,索引 0-4 分别对应 a, e, i, o, u
    std::array<int, 26> 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<int, 5> cnt{};          // 各元音的出现次数
    std::vector<char> 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;
}

在这里插入图片描述