一个认为一切根源都是“自己不够强”的INTJ
个人主页:用哲学编程-CSDN博客
专栏:每日一题——举一反三
Python编程学习
Python内置函数
Python-3.12.0文档解读
目录
我的写法
代码分析
时间复杂度分析
空间复杂度分析
总结
我要更强
方法一:使用 collections.Counter 优化字符计数
优化后的代码:
方法二:使用 heapq 优化排序过程
优化后的代码:
方法三:手动维护最大值优化
优化后的代码:
复杂度分析
方法一:使用 collections.Counter
方法二:使用 heapq
方法三:手动维护最大值
总结
哲学和编程思想
方法一:使用 collections.Counter
哲学和编程思想
方法二:使用 heapq
哲学和编程思想
方法三:手动维护最大值
哲学和编程思想
总结
举一反三
题目链接:https://www.lanqiao.cn/problems/504/learning/?page=1&first_category_id=1&difficulty=20&sort=students_count&asc=0
我的写法
import os
import sys
word=input()
char_counts={}
for char in word:
if char not in char_counts:
char_counts[char]=1
else:
char_counts[char]+=1
print(*sorted(char_counts.items(),key=lambda x:(-x[1],x[0]))[0],sep='\n',end='')
代码分析
- 输入处理:
- 字符计数:
- 排序和输出:
这部分代码首先将字典 char_counts 的键值对转换为列表,然后使用 sorted 函数按指定的规则排序。排序规则是先按出现次数的负值(即从高到低)排序,如果出现次数相同则按字符的字典序排序。最后,输出排序后的第一个元素(即出现次数最多的字符及其出现次数)。
时间复杂度分析
- 字符计数部分:遍历输入字符串的时间复杂度是 O(n),其中 n 是输入字符串的长度。
- 排序部分:排序的时间复杂度是 O(m log m),其中 m 是不同字符的数量。在最坏情况下,m 可能等于 n,但通常 m 远小于 n。
综合来看,整个代码的时间复杂度主要由排序部分决定,因此总体时间复杂度为 O(n + m log m)。
空间复杂度分析
- 字符计数部分:使用了一个字典 char_counts 来存储字符及其出现次数,因此空间复杂度是 O(m),其中 m 是不同字符的数量。
- 排序部分:排序过程中需要一个临时列表来存储字典的键值对,因此空间复杂度也是 O(m)。
综合来看,整个代码的空间复杂度为 O(m)。
总结
- 时间复杂度:O(n + m log m)
- 空间复杂度:O(m)
这段代码在处理大量数据时可能会受到排序操作的影响,但总体来说,它在时间和空间效率上都是合理的。
我要更强
当然,可以对这段代码进行优化。以下是几种可能的优化方式:
方法一:使用 collections.Counter 优化字符计数
collections.Counter 可以快速计算字符出现的次数,并且它的构造器是经过优化的,性能优于手动构造字典。
优化后的代码:
import collections
word = input()
char_counts = collections.Counter(word)
# 按照出现次数从高到低排序,出现次数相同时按字符字典序排序
most_common_char = sorted(char_counts.items(), key=lambda x: (-x[1], x[0]))[0]
# 输出结果
print(*most_common_char, sep='\n', end='')
方法二:使用 heapq 优化排序过程
对于寻找出现次数最多的字符,可以使用堆数据结构,这样可以优化排序操作。
优化后的代码:
import collections
import heapq
word = input()
char_counts = collections.Counter(word)
# 使用堆来获取出现次数最多的字符
most_common_char = heapq.nlargest(1, char_counts.items(), key=lambda x: (x[1], -ord(x[0])))[0]
# 输出结果
print(*most_common_char, sep='\n', end='')
方法三:手动维护最大值优化
在遍历字符串的过程中,手动维护一个最大值,这样可以避免对整个字典进行排序。
优化后的代码:
word = input()
char_counts = {}
max_char = ''
max_count = 0
for char in word:
if char not in char_counts:
char_counts[char] = 1
else:
char_counts[char] += 1
# 更新最大值
if (char_counts[char] > max_count) or (char_counts[char] == max_count and char < max_char):
max_count = char_counts[char]
max_char = char
# 输出结果
print(max_char, max_count, sep='\n', end='')
复杂度分析
方法一:使用 collections.Counter
- 时间复杂度:
- 建立 Counter 的时间复杂度是 O(n)。
- 排序的时间复杂度是 O(m log m)。
- 空间复杂度:O(m)
方法二:使用 heapq
- 时间复杂度:
- 建立 Counter 的时间复杂度是 O(n)。
- 使用堆获取最大值的时间复杂度是 O(m log k),这里 k = 1,因此简化为 O(m)。
- 空间复杂度:O(m)
方法三:手动维护最大值
- 时间复杂度:整个过程只需要一次遍历字符串,时间复杂度是 O(n)。
- 空间复杂度:O(m)
总结
- 方法一和方法二在构建字符计数时都使用了 Counter,在处理大数据时,性能优于手动字典构建。
- 方法二通过堆优化了排序过程,适用于需要获取前 k 个最大值的情况。
- 方法三直接在遍历过程中维护最大值,在时间和空间复杂度上都是最优的。
可以根据具体需求选择适合的优化方案。
哲学和编程思想
在代码优化过程中,可以应用一些哲学和编程思想来设计更高效、更可读的解决方案。以下是对上述方法中使用的哲学和编程思想的详细说明:
方法一:使用 collections.Counter
哲学和编程思想
- 开源工具复用:
- 哲学:不要重复发明轮子(DRY - Don't Repeat Yourself)。
- 编程思想:利用现有的高效库和工具可以节省时间和精力,同时提高代码的性能和可靠性。collections.Counter 是一个专门用于计数的高效工具。
- 抽象化:
- 哲学:将复杂的问题抽象为更简单的子问题。
- 编程思想:通过使用高层次的抽象,如 Counter,我们可以避免手动管理计数逻辑,从而简化代码。
方法二:使用 heapq
哲学和编程思想
- 数据结构选择:
- 哲学:选择最合适的数据结构解决问题。
- 编程思想:堆是一种适合处理优先级队列的高效数据结构,能够在 O(log n) 时间内完成插入和删除操作。heapq 用于高效地找到前 k 个最大/最小元素。
- 分而治之:
- 哲学:将问题分解为更小的子问题,逐一解决。
- 编程思想:通过使用堆来处理排序问题,可以将问题分解为插入和删除操作,避免全局排序。
方法三:手动维护最大值
哲学和编程思想
- 原地计算(In-place Calculation):
- 哲学:尽量减少空间占用,直接在现有数据上进行计算。
- 编程思想:通过在遍历过程中直接维护最大值,避免了额外的空间开销和排序操作。
- 贪心算法:
- 哲学:在每一步选择中做出局部最优解,从而达到全局最优解。
- 编程思想:在每次字符计数更新时,立即检查并更新最大值,确保总是能够得到当前的全局最优解。
总结
这些优化方法利用了多种哲学和编程思想,如开源工具复用、抽象化、数据结构选择、分而治之、原地计算和贪心算法。这些思想不仅帮助我们编写高效的代码,还提高了代码的可读性和可维护性。通过理解和应用这些思想,我们可以设计出更加优雅和高效的解决方案。
举一反三
根据上述的哲学和编程思想,以下是一些具体的技巧,可以帮助将这些原则应用到其他问题中:
- 理解并利用现有的库和工具:在 Python 中,有很多预建的库和工具可供使用,如 collections、heapq 等。了解这些工具可以提供的功能,可以帮助你更高效地解决问题,而不必从头开始编写代码。
- 选择合适的数据结构:解决问题时,选择合适的数据结构是至关重要的。例如,如果你需要频繁地查找、添加或删除元素,那么可能应该选择如集合(set)或字典(dictionary)。如果需要排列或排序数据,考虑使用列表(list)或堆(heap)。
- 使用抽象化简化问题:试图将复杂问题抽象化,或者分解成更小的、更易于处理的部分。例如,你可以创建辅助函数来处理某些重复的任务,或者使用类(class)和对象(object)来模拟现实世界的情况。
- 贪心算法:在处理优化问题时,贪心算法是一种有效的策略。每次做出当前看起来最好的选择,最终可能得到全局最优解。但要注意,贪心算法并不总是能得到最优解,需要根据具体问题来分析。
- 原地计算:如果可能,尽量在原地进行计算,以减少额外的空间需求。例如,使用列表的索引进行操作,而不是创建新的列表。
- 预处理优化:在开始主要计算之前,通过预处理输入数据来简化问题或提高效率。例如,使用 Counter 进行预计数,或者对数据进行排序或分类。
- 复杂度分析:理解算法的时间复杂度和空间复杂度是很重要的,它可以帮助你预测代码的性能,以及是否有优化的可能性。
记住,这些只是工具和方法,应用哪种取决于具体的问题和场景。在实际编程中,需要灵活运用,甚至需要综合利用多种技巧和思想来解决问题。