LeetCode 1647. Minimum Deletions to Make Character Frequencies Unique【贪心,排序】1509

本文属于「征服LeetCode」系列文章之一,这一系列正式开始于2021/08/12。由于LeetCode上部分题目有锁,本系列将至少持续到刷完所有无锁题之日为止;由于LeetCode还在不断地创建新题,本系列的终止日期可能是永远。在这一系列刷题文章中,我不仅会讲解多种解题思路及其优化,还会用多种编程语言实现题解,涉及到通用解法时更将归纳总结出相应的算法模板。

为了方便在PC上运行调试、分享代码文件,我还建立了相关的仓库。在这一仓库中,你不仅可以看到LeetCode原题链接、题解代码、题解文章链接、同类题目归纳、通用解法总结等,还可以看到原题出现频率和相关企业等重要信息。如果有其他优选题解,还可以一同分享给他人。

由于本系列文章的内容随时可能发生更新变动,欢迎关注和收藏征服LeetCode系列文章目录一文以作备忘。

A string s is called good if there are no two different characters in s that have the same frequency.

Given a string s, return the minimum number of characters you need to delete to make s good.

The frequency of a character in a string is the number of times it appears in the string. For example, in the string "aab", the frequency of 'a' is 2, while the frequency of 'b' is 1.

Example 1:

Input: s = "aab"
Output: 0
Explanation: `s` is already good.

Example 2:

Input: s = "aaabbbcc"
Output: 2
Explanation: You can delete two 'b's resulting in the good string "aaabcc".
Another way it to delete one 'b' and one 'c' resulting in the good string "aaabbc".

Example 3:

Input: s = "ceabaacb"
Output: 2
Explanation: You can delete both 'c's resulting in the good string "eabaab".
Note that we only care about characters that are still in the string at the end (i.e. frequency of 0 is ignored).

Constraints:

  • 1 <= s.length <= 10^5
  • s contains only lowercase English letters.

解法 贪心+排序

因为只能通过去掉字符来减小出现次数,因此出现次数较多的字符应当「尽可能保留」,以免和后面「撞车」,这就是本题的贪心思路。

首先得到每个字符出现的次数,并从大到小排序。然后从前到后,遍历这个频数数组。设当前频数为 c n t [ i ]   ( c n t [ i ] > 0 ) cnt[i]\ (cnt[i]>0) cnt[i] (cnt[i]>0) ,并维护此前出现的「最小频数」(设为 p r e v prev prev )如果:

  • p r e v > c n t [ i ] prev > cnt[i] prev>cnt[i] ,则无需去除该字符
  • 否则,如果 p r e v ≤ c n t [ i ] prev \le cnt[i] prevcnt[i] ,说明当前频数「过多」,需要去除一定数量的该字符。因为要尽可能少去除,因此需要保留 ⁡ max ⁡ { p r e v − 1 , 0 } \max\{prev-1,0\} max{prev1,0} 个字符。
class Solution {
public:
    int minDeletions(string s) {
        vector<int> cnt(26, 0);
        for (auto ch: s) {
            cnt[ch - 'a']++;
        }
        sort(cnt.begin(), cnt.end(), greater<int>());
        
        int ret = 0;
        int prev = cnt[0];
        for (int i = 1; i < 26 && cnt[i] > 0; i++) {
            if (prev <= cnt[i]) {
                prev = max(prev - 1, 0); // 要保留的字符数
                ret += (cnt[i] - prev);
            } else {
                prev = cnt[i];
            }
        }
        return ret;
    }
};

当然,还可使用 Set 进行去重。HashSet 中保存不同的数目,如果加进来的数目已经存在,就自减,减到 HashSet 中没有的数目。为什么不用排序?例如添加顺序为 4   4   3   2   1 4\ 4\ 3\ 2\ 1 4 4 3 2 1 3   2   1   4   4 3\ 2\ 1\ 4\ 4 3 2 1 4 4 , 第一种是把 4   3   2   1 4\ 3\ 2\ 1 4 3 2 1 每个数都减 1 1 1 ,答案为 4 4 4 。 第二种是直接把最后一个 4 4 4 减到 0 0 0 ,答案也是 4 4 4 。所以答案只需要在意去重自减时减少的个数,而不用在意顺序

class Solution {
    public int minDeletions(String s) {
        int[] a = new int[26];
        char[] cs = s.toCharArray();
        for (char c : cs) a[c - 'a'] ++;// 统计字母个数

        Set<Integer> h = new HashSet<Integer>();
        int res = 0;
        for (int i : a) {
            if (i != 0) {               // 有数目才进行判断
                while (h.contains(i)) { // set已经包含就自减
                    i --;
                    res ++;
                }
                if (i != 0) h.add(i);   // 自减到0时,表示完全删除了某个字母,不能加入set中
            }
        }
        return res;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

memcpy0

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值