题目描述

✅ 1647. 字符频次唯一的最小删除次数

image-20260929090817677

image-20260929090817760

题意分析

删除尽可能少的字符,使剩余各字母的正频次互不相同。每个字母只能降低频次,删到零后就不再参与比较。原字符总数固定,因此最少删除等价于让最终保留的频次总和最大。

解法:频次集合去重

核心思路

[!blue]
用集合 used 记录已经选定的正频次。处理原频次为 f 的字母时,从 f 向下寻找最大的未占用频次;每减一就删除一个字符。若所有可用正频次都已占用,就降到零,把这个字母删光。

这个选择可以按任意字母顺序进行。设已经处理的字母固定不动,当前最大可用频次为 g。某个最优方案给当前字母的频次 a 不可能大于 g;若 a < g,分两种情况:g 没有被后续字母使用,就把当前频次提高到 g;g 已被后续字母使用,就交换它与当前字母的最终频次。

交换始终合法:当前字母原频次足以保留 g,后续字母既然能保留 g,也能保留更小的 a;正频次仍不重复,保留总数不变。若 a = 0,后续字母直接删光即可。因此总能让一个最优方案采用当前贪心选择,逐个固定后仍然最优。

频次为零时不加入集合,因为零代表该字母已不存在,可以由多个字母同时取得。原本未出现的字母也直接跳过,不产生删除次数。

解题步骤

  1. 统计各字母次数。
  2. 逐个处理频次,正数且已被占用时递减并累计删除数。
  3. 剩余频次为正才登记到集合。
  4. 返回总删除数。

代码实现

class Solution {
    public int minDeletions(String s) {
        // 字符集固定为 26,用定长数组统计比哈希表更省常数。
        int[] freq = new int[26];

        for (int i = 0; i < s.length(); i++) {
            freq[s.charAt(i) - 'a']++;
        }

        Set<Integer> used = new HashSet<>();
        int answer = 0;

        for (int f : freq) {
            // 向下找第一个空坑位,每降一级就意味着删掉一个字符。
            while (f > 0 && used.contains(f)) {
                f--;
                answer++;
            }

            // 只登记正数坑位,0 是可以被多个字母共享的。
            if (f > 0) {
                used.add(f);
            }
        }

        return answer;
    }
}
func minDeletions(s string) int {
    // 字符集固定为 26,用定长切片统计比哈希表更省常数。
    freq := make([]int, 26)
    for i := 0; i < len(s); i++ {
        freq[int(s[i]-'a')]++
    }

    used := make(map[int]struct{})
    answer := 0
    for _, f := range freq {
        // 向下找第一个空坑位,每降一级就意味着删掉一个字符。
        for f > 0 {
            if _, ok := used[f]; !ok {
                break
            }
            f--
            answer++
        }
        // 只登记正数坑位,0 是可以被多个字母共享的。
        if f > 0 {
            used[f] = struct{}{}
        }
    }
    return answer
}

复杂度分析

  • 时间复杂度:期望 $O(n)$。统计扫描全部字符,每次递减都对应删除一个字符,总递减次数不超过 $n$;集合查询与插入期望为 $O(1)$。
  • 空间复杂度:$O(1)$,频次数组和集合最多各保存 $26$ 项。

关键点总结

[!green]

  • 集合保存调整后的频次,不是原频次。
  • 零表示字母被删光,不要求唯一。
  • 交换论证支持任意字母处理顺序。

易错点总结

[!yellow]

  • 降到零仍继续减:会制造不存在的负频次。
  • 调整后登记原频次:没有占住真正使用的新频次。
  • 同频字母除一个外全部删光:减少到不同正数往往更省。
  • 把未出现字母的零频次也要求互异:为不存在的字符额外计算删除。

相似题目

题目 难度 关联与区别
1207. 独一无二的出现次数 简单 原题只判断频次是否互异,本题还通过删除字符使频次互异并最小化删除数。
945. 使数组唯一的最小增量 中等 原题通过增加值消除重复,本题通过降低频次消除重复,频次降到0表示整类删除。
转载与许可
作者
链接 https://hgnulb.github.io/blog/2021/39382928
许可 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!