一个认为一切根源都是“自己不够强”的INTJ
个人主页:用哲学编程-CSDN博客
专栏:每日一题——举一反三
Python编程学习
Python内置函数
Python-3.12.0文档解读
目录
我的写法
代码逻辑概述
时间复杂度分析
空间复杂度分析
总结
我要更强
代码优化点
时间复杂度分析
空间复杂度分析
哲学和编程思想
抽象与封装:
记忆化(Memoization):
集合操作优化:
函数式编程思想:
迭代与递归:
算法优化:
举一反三
题目链接:https://pintia.cn/problem-sets/994805260223102976/exam/problems/type/7?problemSetProblemId=994805320306507776&page=0
我的写法
#对任何一个正整数 n,如果它是偶数,那么把它砍掉一半;
#n//=2
#如果它是奇数,那么把 (3n+1) 砍掉一半。这样一直反复砍下去,最后一定在某一步得到 n=1。
#n=(3*n+1)//2K=int(input())nums=set(list(map(int,input().split())))
init=nums.copy()
def Callatz(num):if num%2==0:return num//2else:return (3*num+1)//2for num in init:tmp=Callatz(num)while tmp!=1:nums.discard(tmp)tmp=Callatz(tmp)print(*sorted(list(nums),reverse=True))
这段代码的核心逻辑是基于Collatz猜想,对输入的一组正整数进行迭代处理,直到所有数都变为1,并输出那些在迭代过程中没有被其他数“覆盖”的数。
代码逻辑概述
- 输入处理:代码首先读取一个整数K和一个整数列表,并将列表转换为集合nums,同时复制一份初始集合init。
- Collatz迭代:定义了一个函数Callatz,用于根据Collatz猜想的规则对数进行变换。主循环对初始集合中的每个数进行迭代,直到该数变为1。在迭代过程中,如果某个数在集合中出现,则将其移除。
- 输出结果:最后,代码输出剩余的数,并按降序排序。
时间复杂度分析
- Collatz迭代:对于每个输入的数,迭代次数取决于该数的值。虽然Collatz猜想没有被证明,但实践中大多数数会在有限步骤内变为1。对于一个数n,最坏情况下的迭代次数是O(log n),因为每次迭代要么将数减半,要么将其变为3n+1并减半。
- 集合操作:集合的discard操作时间复杂度为O(1)。
综合考虑,假设输入的数个数为K,每个数的平均值为N,则总的时间复杂度大致为O(K * log N)。
空间复杂度分析
- 集合存储:集合nums和init存储了输入的数,空间复杂度为O(K)。
- 其他变量:临时变量tmp和其他局部变量占用常数空间。
因此,总的空间复杂度为O(K)。
总结
这段代码实现了一个经典的数学问题,逻辑清晰,时间复杂度为O(K * log N),空间复杂度为O(K)。代码通过集合操作有效地处理了数之间的覆盖关系,并最终输出结果。
我要更强
优化时间复杂度和空间复杂度的方法主要集中在减少不必要的计算和存储。以下是几种可能的优化策略:
- 使用记忆化技术:将已经计算过的数及其变换记录下来,避免重复计算。
- 减少集合操作:在进行Collatz变换时,直接判断和移除不需要的数,避免重复操作。
下面是优化后的代码,包括详细的注释:
K = int(input())
nums = set(map(int, input().split()))
init = nums.copy()
memo = {}def Callatz(num):if num in memo:return memo[num]if num % 2 == 0:result = num // 2else:result = (3 * num + 1) // 2memo[num] = resultreturn resultfor num in init:tmp = Callatz(num)while tmp != 1:if tmp in nums:nums.discard(tmp)tmp = Callatz(tmp)print(*sorted(nums, reverse=True))
代码优化点
- 使用记忆化技术:
- 新增了一个字典memo来存储已经计算过的数及其变换结果,从而避免重复计算。
- 在Callatz函数中,首先检查数是否已经在memo中,如果在则直接返回变换结果,否则进行计算并存储在memo中。
- 减少集合操作:
- 在每次进行Collatz变换时,直接判断变换后的数是否在集合nums中,如果在则移除,从而减少不必要的集合操作。
时间复杂度分析
由于使用了记忆化技术,每个数的变换结果只计算一次,因此:
- Collatz迭代:使用记忆化技术后,每个数的最坏情况下的迭代次数仍然为O(log n),但由于避免了重复计算,整体计算次数减少。
- 集合操作:集合的discard操作时间复杂度为O(1)。
综合考虑,优化后的时间复杂度大致为O(K * log N),和原始实现相同,但由于避免了重复计算,实际运行时间会有所减少。
空间复杂度分析
- 集合存储:集合nums和init仍然存储了输入的数,空间复杂度为O(K)。
- 记忆化存储:新增的字典memo在最坏情况下会存储所有经过的数,空间复杂度为O(K * log N)。
虽然引入了额外的存储空间,但总体上由于避免了重复计算,实际运行效率会更高。
哲学和编程思想
这些优化方法涉及了多个哲学和编程思想,具体包括:
-
抽象与封装:
- 封装:将Collatz猜想的变换逻辑封装在Callatz函数中,使得主逻辑更加清晰和简洁。这种做法体现了面向对象编程中的封装思想,即将数据和操作数据的方法封装在一起,提高代码的可维护性和可读性。
-
记忆化(Memoization):
- 动态规划:记忆化是动态规划的一种实现方式,通过存储已解决子问题的结果来避免重复计算。这种思想体现了“不要重复自己”(Don't Repeat Yourself, DRY)的原则,通过减少重复计算来提高效率。
- 实用主义:记忆化技术体现了实用主义哲学,即在编程中注重实际效果和效率,而不是纯粹的理论或形式。
-
集合操作优化:
- 最小化操作:在每次进行Collatz变换时,直接判断变换后的数是否在集合nums中,如果在则移除,从而减少不必要的集合操作。这种做法体现了“最小化操作”的思想,即在编程中尽量减少不必要的操作,以提高效率。
- 简洁性:代码中尽量保持简洁,避免冗余操作,体现了简洁性原则,即在编程中追求简单、直接和高效的解决方案。
-
函数式编程思想:
- 纯函数:Callatz函数是一个纯函数,即给定相同的输入,总是返回相同的输出,且没有副作用。这种做法符合函数式编程中的纯函数思想,提高了代码的可测试性和可维护性。
-
迭代与递归:
- 迭代:代码中使用迭代(循环)来处理Collatz变换,而不是递归。迭代通常比递归更高效,因为它避免了递归调用栈的开销。这种做法体现了迭代优于递归的思想,特别是在处理大规模数据时。
-
算法优化:
- 预计算:通过预先计算和存储结果,可以显著减少后续计算的时间。这种做法体现了预计算和缓存的思想,即在编程中通过预先计算和存储结果来提高效率。
总结来说,这些优化方法体现了多个编程和哲学思想,包括封装、记忆化、最小化操作、简洁性、函数式编程、迭代优于递归以及预计算和缓存等。这些思想在编程中广泛应用,旨在提高代码的效率、可维护性和可读性。
举一反三
根据上述哲学和编程思想,以下是一些通用的编程技巧,可以帮助在面对类似问题时举一反三:
- 抽象与封装:
- 定义函数:将复杂或重复的逻辑封装成函数,提高代码的可读性和可维护性。
- 模块化:将代码分解成独立的模块或类,每个模块负责一个特定的功能,便于管理和复用。
- 记忆化(Memoization):
- 缓存结果:对于计算密集型或递归问题,使用字典或数组来缓存已计算的结果,避免重复计算。
- 动态规划:对于涉及重叠子问题的问题,考虑使用动态规划来优化解决方案。
- 集合操作优化:
- 减少冗余操作:在处理集合或列表时,尽量减少不必要的操作,例如使用集合的discard或remove方法来直接移除元素。
- 使用合适的数据结构:根据问题的特点选择合适的数据结构,例如使用集合来快速查找和去重。
- 函数式编程思想:
- 纯函数:尽量编写纯函数,即没有副作用且输入输出一致的函数,提高代码的可测试性和可维护性。
- 不可变数据:尽量使用不可变数据,避免数据被意外修改,减少潜在的bug。
- 迭代与递归:
- 优先迭代:在处理大规模数据时,优先考虑使用迭代而不是递归,避免栈溢出等问题。
- 尾递归优化:如果必须使用递归,考虑使用尾递归优化,减少栈空间的使用。
- 算法优化:
- 预计算:对于一些固定或可预见的结果,提前计算并存储,减少运行时的计算量。
- 空间换时间:在时间和空间复杂度之间权衡,有时可以通过增加空间使用来减少时间复杂度。
- 代码简洁性:
- 避免冗余:保持代码简洁,避免不必要的变量和操作。
- 使用内置函数:利用Python等语言的内置函数和库,例如map、filter、sorted等,提高代码的简洁性和效率。
- 测试与调试:
- 单元测试:为关键函数编写单元测试,确保其正确性。
- 调试技巧:使用断点、日志输出等调试技巧,快速定位和修复问题。
通过掌握这些技巧,可以在面对不同问题时灵活运用,提高编程效率和代码质量。记住,编程是一个不断学习和实践的过程,不断积累经验并应用这些思想和技巧,将能够更好地解决各种编程难题。