一个认为一切根源都是“自己不够强”的INTJ
个人主页:用哲学编程-CSDN博客
专栏:每日一题——举一反三
Python编程学习
Python内置函数
Python-3.12.0文档解读
目录
我的写法
时间复杂度分析
空间复杂度分析
总体空间复杂度:O(N)
总结
我要更强
时间复杂度分析
空间复杂度分析
总结
哲学和编程思想
举一反三
题目链接
我的写法
# 接收一个整数N,表示输入的数字个数
N = int(input())
# 从输入中读取N个整数,并将它们转换为集合以去除重复项
nums = set(map(int, input().split()))
# 找出集合中的最大值
max_num = max(nums)
# 如果集合中的最大值小于等于0,那么返回1
if max_num <= 0:
print('1')
raise SystemExit(0) # 提前终止程序,因为我们已经找到了答案
# 从1到最大值之间查找缺失的正整数
for i in range(1, max_num):
if i not in nums: # 如果当前数i不在集合中,输出这个数
print(i)
raise SystemExit(0) # 提前终止程序,因为我们已经找到了答案
# 如果1到最大值之间的所有正整数都在集合中,返回最大值+1
print(max_num + 1)
这段代码实现了查找从1开始的最小正整数,该整数不在给定的整数集合中。下面是对这段代码的详细点评和分析:
时间复杂度分析
- 输入处理:O(N)
- 将N个数字读取并转换为集合需要O(N)时间。
- 查找最大值:O(N)
- 查找集合中的最大值需要遍历所有元素,时间复杂度为O(N)。
- 检查缺失的正整数:O(max_num)
- 在最坏情况下,需要从1检查到 max_num,每次检查集合中是否存在某个整数的时间为O(1),因此这一部分的时间复杂度为O(max_num)。
- 总体时间复杂度:O(N + max_num)
- 由于 max_num 最多不会超过N,因此时间复杂度可以表示为O(N)(假设输入的N个数字都是正数且没有重复)。
空间复杂度分析
- 输入处理:O(N)
- 输入N个数字并存储到集合中,需要O(N)的空间。
- 其他部分:
- 使用了几个常数级别的额外变量如 max_num 和 i,这些变量的空间复杂度为O(1)。
总体空间复杂度:O(N)
总结
这段代码高效地查找了最小的缺失正整数,逻辑清晰,处理了多种可能的输入情况。时间复杂度为O(N),空间复杂度也是O(N),对于一般的输入规模是可以接受的。此外,使用集合来存储输入数据,可以快速地进行查找操作,进一步优化了性能。唯一需要注意的是,在极端情况下,如果输入的数字范围非常大,可能会导致较高的空间开销。
我要更强
为了优化时间复杂度和空间复杂度,我们可以考虑以下几个策略:
- 使用数组代替集合:如果输入的数字范围有限,可以使用数组来标记数字是否出现,这样可以减少空间使用。
- 只关注正整数:我们只关心正整数的缺失,因此可以忽略非正整数,这样可以减少不必要的检查。
- 原地修改数组:使用输入的数组本身来标记数字是否出现,这样可以避免额外的空间使用。
下面是使用这些策略优化后的代码:
# 读取输入的数字个数
N = int(input())
# 读取输入的数字,并转换为列表
nums = list(map(int, input().split()))
# 使用一个数组来标记1到N之间的数字是否出现
# 初始化所有位置为False,表示数字未出现
present = [False] * N
# 遍历输入的数字,如果数字在1到N之间,则标记为True
for num in nums:
if 1 <= num <= N:
present[num - 1] = True
# 遍历标记数组,找到第一个未被标记的位置,即缺失的最小正整数
for i in range(N):
if not present[i]:
print(i + 1)
raise SystemExit(0)
# 如果所有位置都被标记,则缺失的最小正整数是N+1
print(N + 1)
时间复杂度分析
- 输入处理:O(N)
- 标记数字:O(N)
- 查找缺失的正整数:O(N)
- 总体时间复杂度:O(N)
空间复杂度分析
- 标记数组:O(N)
- 总体空间复杂度:O(N)
总结
这段代码通过使用一个额外的数组来标记数字是否出现,优化了空间复杂度。同时,由于只关注1到N之间的数字,因此可以忽略输入中的非正整数,减少了不必要的检查。这种方法在输入数字的范围有限时非常有效,特别是在输入的数字都是正整数且范围不大于N时,可以达到O(N)的时间复杂度和空间复杂度。
哲学和编程思想
这些优化方法体现了以下哲学和编程思想:
- KISS原则(Keep It Simple, Stupid):
- 代码简洁明了,避免不必要的复杂性。例如,使用数组来标记数字是否出现,而不是使用更复杂的数据结构。
- YAGNI原则(You Ain't Gonna Need It):
- 只实现当前需要的功能,不预先实现未来可能需要的功能。在代码中,我们只处理了当前输入的数字,没有预先考虑更大范围的数字。
- 空间与时间的权衡(Space-Time Tradeoff):
- 在空间和时间之间做出权衡。使用额外的数组来标记数字是否出现,虽然增加了空间复杂度,但减少了时间复杂度,因为可以直接在O(1)时间内检查数字是否出现。
- 局部性原理(Principle of Locality):
- 利用数据的局部性,假设输入的数字集中在一定的范围内。通过限制标记数组的大小为N,我们假设缺失的正整数不会超过N,这在很多实际情况下是合理的。
- 抽象与具体化:
- 将问题抽象为更具体的形式。例如,将查找缺失的最小正整数问题抽象为在数组中查找第一个未被标记的位置。
- 优化与效率:
- 追求代码的效率。通过原地修改数组和使用数组标记,我们减少了不必要的内存分配和数据复制,提高了程序的执行效率。
- 问题分解:
- 将复杂问题分解为更小的、可管理的部分。在代码中,我们将问题分解为读取输入、标记数字和查找缺失数字三个部分。
- 算法优化:
- 选择合适的算法来解决问题。在这个例子中,我们选择了基于数组的标记算法,这是一种简单而高效的方法。
- 数据驱动:
- 根据数据的特性来设计解决方案。由于我们假设输入的数字都是正整数且范围不大于N,因此我们可以设计一个基于这个假设的优化方案。
通过这些哲学和编程思想的应用,能够设计出更加高效、简洁且易于理解的代码。这些思想不仅适用于这个特定的例子,也适用于更广泛的编程和软件开发领域。
举一反三
基于上述哲学和编程思想,以下是一些技巧和策略,可以帮助在编程和问题解决中举一反三:
- 简化问题:
- 在开始编码之前,尝试简化问题。去掉非必要的部分,专注于核心需求。
- 使用简单的数据结构和算法,避免过早优化。
- 避免过度设计:
- 不要预先实现不需要的功能。根据实际需求逐步构建系统。
- 保持代码的灵活性和可扩展性,但只在必要时进行扩展。
- 权衡时间和空间:
- 在设计算法时,考虑时间和空间的平衡。有时候,牺牲一点空间可以显著减少时间复杂度。
- 分析问题的特性,选择最合适的数据结构和算法。
- 利用局部性原理:
- 如果数据具有局部性,考虑使用缓存或预取技术来提高性能。
- 在处理大量数据时,考虑数据的分块处理,以减少内存使用和提高处理速度。
- 抽象问题:
- 将复杂问题分解为更小的、可管理的部分。每个部分应该有一个清晰的目标和接口。
- 使用抽象数据类型(ADTs)来隐藏实现细节,提高代码的可读性和可维护性。
- 追求效率:
- 在编码时,始终考虑性能。避免不必要的循环和递归,减少函数调用的开销。
- 使用合适的数据结构来存储和操作数据,例如使用哈希表进行快速查找。
- 问题分解:
- 将大问题分解为小问题,逐步解决。每个小问题解决后,再整合起来解决整个问题。
- 使用模块化和分层设计,将代码组织成易于管理和测试的单元。
- 算法优化:
- 学习和掌握常见的算法和数据结构,了解它们的时间和空间复杂度。
- 在解决问题时,尝试找到最优的算法,或者对现有算法进行改进。
- 数据驱动:
- 分析数据的特性,根据数据的特点选择或设计合适的算法。
- 在处理数据密集型任务时,考虑数据预处理和后处理步骤,以提高算法的效率。
通过应用这些技巧和策略,可以在编程和问题解决中更加灵活和高效。记住,编程不仅仅是写代码,更是一种思考和解决问题的艺术。