题目描述

✅ 451. 根据字符出现频率排序

image-20260928221034249

image-20260928221034250

题意分析

将字符串中的字符重新排列,使出现次数较多的字符块排在前面,同一种字符的所有出现必须放在一起。每种字符的数量与输入保持一致,不能去重或补入其他字符。

输入包含大小写英文字母和数字,大小写需要分别统计。同频字符的先后顺序任意,因此答案可能不唯一;本题要求按频次排序,不要求字典序。

解法:频次统计后排序不同字符

核心思路

[!blue]

最终结果由若干同字符块组成,每个块的长度就是该字符的频次。因此先统计 count[ch],再只对出现过的不同字符排序,就能决定这些块的输出顺序,无需对原字符串的每一个位置排序。

比较两个字符时,频次大的应排在前面。Java 比较器交换计数的比较方向,Go 使用 count[chars[i]] > count[chars[j]]。两者频次相同无需额外规则,任一先后关系都满足题意。

排序完成后,对每种字符连续输出 count[ch] 次。这样保证同字符集中、各字符数量不变,而且整个结果的字符块频次非递增,正好满足全部要求。输入字符都在 ASCII 范围内,可以用长度为 128 的数组直接计数。

解题步骤

  1. 遍历字符串,将每个字符对应的计数加一。
  2. 收集计数大于零的字符,得到不同字符列表。
  3. 按计数从大到小排序该列表,同频顺序任意。
  4. 依次取出列表中的字符,每次连续追加它的全部出现次数。
  5. 返回构造出的字符串。

代码实现

class Solution {
    public String frequencySort(String s) {
        int[] count = new int[128];

        for (int i = 0; i < s.length(); i++) {
            count[s.charAt(i)]++;
        }

        List<Character> chars = new ArrayList<>();

        for (char ch = 0; ch < count.length; ch++) {
            if (count[ch] > 0) {
                chars.add(ch);
            }
        }

        // 只排序不同字符,频次高的字符块优先输出。
        chars.sort((first, second) -> Integer.compare(count[second], count[first]));

        StringBuilder result = new StringBuilder(s.length());

        for (char ch : chars) {
            // 完整写出每次出现,不能只输出去重后的字符。
            result.append(String.valueOf(ch).repeat(count[ch]));
        }

        return result.toString();
    }
}
import "sort"

func frequencySort(s string) string {
    count := [128]int{}
    for i := 0; i < len(s); i++ {
        count[s[i]]++
    }

    chars := make([]byte, 0, 62)
    for ch, frequency := range count {
        if frequency > 0 {
            chars = append(chars, byte(ch))
        }
    }
    // 只排序不同字符,频次高的字符块优先输出。
    sort.Slice(chars, func(i int, j int) bool {
        return count[chars[i]] > count[chars[j]]
    })

    result := make([]byte, 0, len(s))
    for _, ch := range chars {
        // 完整写出每次出现,不能只输出去重后的字符。
        for times := count[ch]; times > 0; times-- {
            result = append(result, ch)
        }
    }
    return string(result)
}

复杂度分析

  • 时间复杂度:$O(n+k\log k)$,其中 $n$ 是字符串长度,$k$ 是不同字符数。统计和输出各为 $O(n)$,只对 $k$ 种字符排序;本题 $k\le62$。
  • 空间复杂度:$O(n+k)$,包含构造结果的缓冲区和不同字符列表,固定计数数组为常数空间。

关键点总结

[!green]

  • 先统计再按字符块排序,把排序规模从出现次数降为不同字符数。
  • 输出时按完整频次展开,确保结果长度和每种字符的数量不变。
  • 同频不要求唯一顺序,比较器无需额外按字典序排序。

易错点总结

[!yellow]

  • 比较器方向写反会得到频次升序,与题目要求相反。
  • 只输出不同字符而没有按频次重复,会错误地把输入去重。
  • 只统计 26 个小写字母或合并大小写,会改变输入字符的构成。
  • Go 的字符列表应创建为长度零的切片再追加;预设非零长度会把零值字节也当成待排序字符。

相似题目

题目 难度 关联与区别
347. 前 K 个高频元素 中等 同样先按值统计频次,原题只选前k种,本题按频次输出全部字符并保留重数。
692. 前K个高频单词 中等 同样按频次排序,原题单词同频时有字典序规则,本题同频字符通常可任意排序。
转载与许可
作者
链接 https://hgnulb.github.io/blog/2026/00083753
许可 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!