刷题笔记:使字符出现次数唯一的最小删除数
问题描述
给定一个字符串 s,你需要通过删除一些字符,使得每个字符在字符串中出现的次数均不相同。目标是找出最少需要删除的字符数量以达到这个目标。
示例
-
示例1
- 输入:
s = "aab" - 输出:
0 - 解释: 字符
'a'出现2次,字符'b'出现1次,这些出现次数已经不同,无需删除。
- 输入:
-
示例2
- 输入:
s = "aaabbbcc" - 输出:
2 - 解释: 可以删除两个
'b',使得'a'出现3次,'b'出现1次,'c'出现2次,频率均不同。
- 输入:
-
示例3
- 输入:
s = "abcdef" - 输出:
5 - 解释: 所有字符出现次数均为1,只有一个字符可以保留,其他5个字符需要删除。
- 输入:
解题思路
为了确保每个字符的出现次数都是唯一的,我们需要:
-
统计字符频率:首先统计每个字符在字符串中出现的次数。
-
确保频率唯一:
- 使用一个集合来记录已经使用过的频率。
- 遍历每个字符的频率,如果当前频率已经被使用过,则减少该频率(相当于删除一个字符),直到找到一个未被使用的频率或频率降为0。
- 记录每次减少频率的操作次数,这些操作次数就是需要删除的字符数量。
-
返回删除总数:所有操作完成后,返回删除的字符总数。
执行过程示例
让我们通过示例2 "aaabbbcc" 来演示代码的执行过程。
示例输入
s = "aaabbbcc"
执行步骤详解
-
统计字符频率
freq := make(map[rune]int) for _, char := range s { freq[char]++ }结果:
freq = { 'a': 3, 'b': 3, 'c': 2 } -
初始化辅助数据结构
usedFreqs := make(map[int]bool) // 存储已使用的频率 deletions := 0 // 删除的字符总数 -
遍历字符频率并确保唯一性
for _, count := range freq { for count > 0 && usedFreqs[count] { count-- deletions++ } if count > 0 { usedFreqs[count] = true } }详细过程:
-
处理字符
'a',频率3:- 检查
3是否已在usedFreqs中:usedFreqs[3]:false(尚未使用)
- 将
3添加到usedFreqs:usedFreqs = {3: true} - 删除次数
deletions:0
- 检查
-
处理字符
'b',频率3:- 检查
3是否已在usedFreqs中:usedFreqs[3]:true(已使用)
- 将频率减少到
2,并增加删除计数:count = 2 deletions = 1 - 检查
2是否已在usedFreqs中:usedFreqs[2]:false(尚未使用)
- 将
2添加到usedFreqs:usedFreqs = {3: true, 2: true} - 删除次数
deletions:1
- 检查
-
处理字符
'c',频率2:- 检查
2是否已在usedFreqs中:usedFreqs[2]:true(已使用)
- 将频率减少到
1,并增加删除计数:count = 1 deletions = 2 - 检查
1是否已在usedFreqs中:usedFreqs[1]:false(尚未使用)
- 将
1添加到usedFreqs:usedFreqs = {3: true, 2: true, 1: true} - 删除次数
deletions:2
- 检查
-
-
最终结果
return deletions // 返回 2删除总数:
2
Python 实现
from collections import Counter
def solution(s: str) -> int:
# 统计每个字符的出现频率
freq = Counter(s)
used_freqs = set() # 用于存储已使用的频率
deletions = 0 # 删除的字符总数
for char, count in freq.items():
# 当当前频率已经被使用,且大于0时,减少频率并增加删除计数
while count > 0 and count in used_freqs:
count -= 1
deletions += 1
if count > 0:
used_freqs.add(count) # 将新的唯一频率加入已使用集合
return deletions
if __name__ == '__main__':
print(solution("aab") == 0) # 预期输出: True
print(solution("aaabbbcc") == 2) # 预期输出: True
print(solution("abcdef") == 5) # 预期输出: True
代码解释
-
统计字符频率:
- 使用
Counter(s)统计字符串中每个字符出现的次数,结果存储在freq中。
- 使用
-
确保频率唯一:
- 初始化一个空集合
used_freqs用于记录已使用的频率。 - 遍历
freq中每个字符及其频率:- 如果当前频率已经在
used_freqs中存在,则减少该频率(相当于删除一个字符),并增加删除计数deletions。 - 重复上述步骤,直到找到一个未被使用的频率或频率降为0。
- 如果调整后的频率大于0,将其添加到
used_freqs中。
- 如果当前频率已经在
- 初始化一个空集合
-
返回结果:
- 最终返回
deletions,即为使所有字符频率唯一所需的最少删除次数。
- 最终返回
Go 语言实现
package main
import (
"fmt"
)
// solution 函数用于计算需要删除的最少字符数,使得每个字符的出现次数都唯一
func solution(s string) int {
// 统计每个字符的出现频率
freq := make(map[rune]int)
for _, char := range s {
freq[char]++
}
usedFreqs := make(map[int]bool) // 用于存储已使用的频率
deletions := 0 // 删除的字符总数
for _, count := range freq {
// 当当前频率已经被使用,且大于0时,减少频率并增加删除计数
for count > 0 && usedFreqs[count] {
count--
deletions++
}
if count > 0 {
usedFreqs[count] = true // 将新的唯一频率加入已使用集合
}
}
return deletions
}
func main() {
// 测试用例
fmt.Println(solution("aab") == 0) // 预期输出: true
fmt.Println(solution("aaabbbcc") == 2) // 预期输出: true
fmt.Println(solution("abcdef") == 5) // 预期输出: true
}
代码解释
-
统计字符频率:
- 使用
map[rune]int来存储每个字符的出现次数。 - 遍历字符串
s,统计每个字符的频率。
- 使用
-
确保频率唯一:
- 使用
map[int]bool来记录已经使用过的频率。 - 遍历
freq中每个字符的频率:- 如果当前频率已经在
usedFreqs中存在,则减少该频率,并增加删除计数deletions。 - 重复上述步骤,直到找到一个未被使用的频率或频率降为0。
- 如果调整后的频率大于0,将其标记为已使用。
- 如果当前频率已经在
- 使用
-
返回结果:
- 最终返回
deletions,即为使所有字符频率唯一所需的最少删除次数。
- 最终返回
总结
通过上述步骤,我们成功地将字符串 "aaabbbcc" 转换为每个字符出现次数唯一的形式,所需的最少删除次数为 2。
复杂度分析
-
时间复杂度:
O(n),其中n是字符串的长度。- 统计字符频率需要遍历整个字符串,时间复杂度为
O(n)。 - 遍历字符频率并调整频率的过程,最坏情况下也需要
O(n)的时间。
- 统计字符频率需要遍历整个字符串,时间复杂度为
-
空间复杂度:
O(k),其中k是字符串中不同字符的数量。- 需要额外的空间来存储字符频率和已使用的频率。
关键点总结
-
频率统计:使用高效的数据结构(如
Counter在 Python 中,map在 Go 中)来统计字符频率。 -
确保唯一性:
- 使用集合或映射来记录已使用的频率。
- 通过减少频率来避免冲突,记录每次减少的操作次数。
-
优化思路:
- 处理频率冲突时,尽量减少删除次数,以达到最优解。
- 可以从高频率开始处理,以减少整体删除次数。
-
实现语言的选择:
- Python 提供了便捷的
Counter类,简化频率统计。 - Go 语言则通过
map来实现类似的功能,代码逻辑清晰。
- Python 提供了便捷的