每日一题——Python实现PAT甲级1015 Reversible Primes(举一反三+思想解读+逐步优化)


一个认为一切根源都是“自己不够强”的INTJ

个人主页:用哲学编程-CSDN博客
专栏:每日一题——举一反三
Python编程学习
Python内置函数

Python-3.12.0文档解读

目录

我的写法

is_prime函数分析:

decimal_to_base函数分析:

主循环分析:

我要更强

is_prime函数优化

decimal_to_base函数优化

完整代码及注释

优化解释

总结

哲学和编程思想

举一反三


题目链接

我的写法

# 定义函数is_prime用来检测一个数n是否为质数
def is_prime(n):
    # 如果n小于等于1,则它不是质数
    if n <= 1:
        return False
    # 如果n小于等于3,则它是质数
    if n <= 3:
        return True
    # 如果n能被2或3整除,则它不是质数
    if n % 2 == 0 or n % 3 == 0:
        return False
    # 初始化变量i为5
    i = 5
    # 循环,检测5及以上的数是否能整除n
    while i * i <= n:
        # 如果n能被i或i+2整除,则它不是质数
        if n % i == 0 or n % (i + 2) == 0:
            return False
        # 将i增加6后继续循环(6k±1的优化)
        i += 6
    # 如果上述条件都不满足,则n是质数
    return True

# 定义函数decimal_to_base,将十进制数转换为给定的base进制
def decimal_to_base(decimal_num, base):
    # 如果base不在2到36之间,则返回错误信息
    if base < 2 or base > 36:
        return "Base must be between 2 and 36."
    # 定义一个字符串包含了可能在进制转换中用到的所有字符
    digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    # 如果decimal_num小于base,直接返回对应的字符
    if decimal_num < base:
        return digits[decimal_num]
    # 否则进行递归调用,计算decimal_num除以base的商,并将余数对应的字符附加在后面
    else:
        return decimal_to_base(decimal_num // base, base) + digits[decimal_num % base]

# 主循环
while True:
    # 通过输入接收一行,然后分割字符串
    tmp=input().split()
    # 如果输入的长度小于2(即没有输入两个值),则退出循环
    if len(tmp)<2:
        break
    # 将输入的两个值转换为整数
    N,base=map(int,tmp)
    # 检查N是否为质数,以及N转换为指定进制后,翻转字符串并转换回十进制是否也为质数
    if is_prime(N) and is_prime(int(decimal_to_base(N,base)[::-1],base)):
        # 如果都是质数,则输出"Yes"
        print("Yes")
    else:
        # 否则输出"No"
        print("No")

这段代码包含两个主要的功能:一个是判断给定的整数是否为质数(is_prime函数),另一个是将十进制数转换为给定基数的表达形式(decimal_to_base函数)。最后,这两个功能在一个主循环中结合起来,以判断一个数及其在给定基数下的反转是否都是质数。

is_prime函数分析:

  • 正确性:
    • 此函数正确地实现了质数的判断,首先排除了不可能是质数的情况:小于等于1以及能够被2或3整除的数。其次利用了一个数学事实,即如果一个数n不是质数,那么它必定有一个因数小于或等于sqrt(n)。所以函数只检查到i*i <= n即可。
    • 6k±1规则被用来减少需要检查的可能因子,因为所有质数(除了2和3)都能表示成6k±1的形式。
  • 时间复杂度:
    • 在最坏的情况下(n是一个质数或者只能被另一个质数整除),这个算法的时间复杂度是O(sqrt(n)),因为它最多需要检查sqrt(n)个数字。
  • 空间复杂度:
  • 该函数使用了常数空间,除了输入值n和循环变量i之外,没有使用额外的存储空间。

decimal_to_base函数分析:

  • 正确性:
    • 函数正确地实现了基数转换,使用了递归方法。它也考虑了边界情况,即基数需要在2到36之间。
  • 时间复杂度:
    • 这个函数的时间复杂度是O(log_base(n)),因为每次递归调用都将数字n除以基数base。
  • 空间复杂度:
  • 由于使用了递归,空间复杂度也是O(log_base(n))。每次递归调用都会在调用栈中添加一层,直到达到基准情况。

主循环分析:

  • 主循环不断从用户那里接收输入,直到接收到的输入长度小于2。对于每一对输入,它会判断N是否为质数,以及N转换为指定进制后翻转字符串再转换回十进制是否也为质数。
  • 时间复杂度:
    • 主循环的时间复杂度依赖于输入的数N和基数base。质数检测有O(sqrt(N))的时间复杂度,而基数转换和反转字符串有O(log_base(N))的时间复杂度。
    • 质数检测会被执行两次,一次对原始数N,一次对翻转后的数。如果这两个数的大小相近,总的时间复杂度大约是O(sqrt(N) + log_base(N))。
  • 空间复杂度:
  • 主循环使用的空间主要取决于is_prime和decimal_to_base函数的空间复杂度,分别是O(1)和O(log_base(N))。因此,主循环的总空间复杂度大约是O(log_base(N))。

总的来说,该代码是高效和有效的,特别是在判断质数和基数转换方面使用了一些优化的方法。不过,递归可能在非常大的数字和/或基数非常高时导致栈溢出,所以在这种情况下可能需要迭代的方法来代替递归实现基数转换。


我要更强

针对前述代码,我们可以考虑一些优化策略,以提高算法的效率和减少内存使用。优化主要集中在两个方面:is_prime函数和decimal_to_base函数的改进。

is_prime函数优化

对于is_prime函数,我们已经使用了6k±1规则来减少必要的检查次数,这是一个很好的优化。进一步的优化可能不会显著减少时间复杂度,因为它本质上已经是O(sqrt(n)),这是检查质数的较快方法之一。

decimal_to_base函数优化

对于decimal_to_base函数,我们可以避免递归以减少空间复杂度。通过使用循环而不是递归,我们可以将空间复杂度降低到O(1)。

完整代码及注释

def is_prime(n):
    if n <= 1:
        return False
    if n <= 3:
        return True
    if n % 2 == 0 or n % 3 == 0:
        return False
    i = 5
    while i * i <= n:
        if n % i == 0 or n % (i + 2) == 0:
            return False
        i += 6
    return True

def decimal_to_base(decimal_num, base):
    if base < 2 or base > 36:
        return "Base must be between 2 and 36."
    digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    result = ""
    while decimal_num > 0:
        result = digits[decimal_num % base] + result
        decimal_num //= base
    return result or "0"

while True:
    tmp = input().split()
    if len(tmp) < 2:
        break
    N, base = map(int, tmp)
    if is_prime(N) and is_prime(int(decimal_to_base(N, base)[::-1], base)):
        print("Yes")
    else:
        print("No")

优化解释

  • is_prime函数保持不变,因为其时间复杂度已经是针对其操作相对较优的了。
  • decimal_to_base函数已改为使用循环而非递归。这种方法相比递归,减少了额外的函数调用开销和调用栈空间使用,从而将空间复杂度降低到O(1)。循环简单地将数字按基数转换,并每次迭代将余数添加到结果字符串的前面,达到了逆序的效果。

总结

这些优化帮助减少了内存使用(尤其是在decimal_to_base函数中避免了递归),而is_prime函数的优化主要集中在减少不必要的迭代上。然而,值得注意的是,在某些情况下,优化可能带来的性能提升是有限的,尤其是当输入值非常大时。此外,代码的可读性和维护性也是在进行优化时需要考虑的因素之一。


哲学和编程思想

在优化上述代码的过程中,我们实际上应用了几个编程和软件设计的哲学和思想:

  1. KISS原则(Keep It Simple, Stupid): 此原则主张尽可能保持简单,避免不必要的复杂性。在优化decimal_to_base函数时,我们采用了一个简单的循环代替了递归,这使得函数实现更为直观且易于理解。
  2. DRY原则(Don't Repeat Yourself): 这个原则鼓励减少重复,无论是数据还是逻辑。虽然在这里的优化中没有直接体现,但保持函数的单一职责(如is_prime只检查质数)和避免在代码中重复相同的逻辑是这个原则的体现。
  3. 分治思想: 分治方法是一种将问题分解为更小部分来解决,然后合并结果以获得最终解决方案的方法。在原始代码中,decimal_to_base通过递归将大问题分解为小问题。优化后,虽然不再使用递归,但仍然通过循环逐步构建最终结果,可以看作是分治思想的一种变形应用。
  4. 时间和空间权衡: 在计算机科学中,经常需要在时间复杂度和空间复杂度之间做出权衡。例如,在递归和迭代之间选择,就需要权衡它们在时间和空间上的消耗。在这里,我们优化decimal_to_base函数,将空间复杂度降低到O(1),而保持时间复杂度不变,体现了对资源利用的权衡。
  5. 最小惊讶原则: 这个原则表明代码应该如何预期般行事,避免混淆使用者。优化后的代码更加直白和易于理解,符合这一原则。
  6. 可维护性和可读性: 优化代码的另一个考虑是确保代码的可维护性和可读性。在上述代码中,尽管我们进行了优化,但仍然保持了代码的清晰性和可读性,使得未来的维护更加容易。
  7. 渐进增强: 这是一种逐步改进代码的方法,首先实现一个简单的版本,然后逐渐增加优化和改进。例如,我们首先定义了is_prime和decimal_to_base的基本版本,然后在后续版本中对其进行了优化。
  8. 代码重构: 代码重构是改进现有代码结构而不改变其外部行为的过程。在优化decimal_to_base函数中,我们进行了重构,通过替换递归为迭代,来优化性能而不改变函数的功能。

这些哲学和思想都是编程实践中常常被采用的,目的是为了写出高效、易于维护和理解的代码。实现这些通常需要在代码的性能和其他因素(如可读性、可维护性)之间进行仔细的权衡。


举一反三

采用这些哲学和编程思想,以下是一些通用的编程技巧和最佳实践,你可以将它们应用于各种编程任务中:

  1. 追求简洁:
    • 在设计算法和编写代码时,始终问自己是否可以进一步简化。避免过度工程化,尽量不要增加不必要的复杂度。
  2. 避免重复:
    • 注意代码中的重复模式。使用函数、类或模块来封装重复的代码,以提高复用性。
  3. 逐步开发:
    • 避免一开始就尝试编写完整的复杂系统。从一个基本的、可以工作的版本开始,然后逐渐添加特性和改进。
  4. 注重可读性:
    • 书写清晰的代码,这不仅有助于别人理解,也有助于未来的你回顾和维护代码。使用有意义的变量名、保持一致的代码风格、添加注释和文档。
  5. 重构:
    • 定期审视和重构你的代码。随着项目的进行和需求的变化,始终保持代码整洁和组织良好。
  6. 性能和资源的权衡:
    • 根据应用场景,确定何时应优先考虑时间效率,何时应优先考虑空间效率。有时快速的代码需要消耗更多内存,有时节省内存的代码可能不那么快。
  7. 模块化和解耦:
    • 设计代码时,使各个部分之间的耦合最小化。每个模块或函数应负责一组明确的任务,这样可以单独测试和优化。
  8. 代码的可测试性:
    • 编写代码时考虑测试。如果代码难以测试,它可能也难以维护。使用单元测试来验证每个部分的功能。
  9. 持续学习:
    • 不断学习新的技术和方法。编程是一个快速发展的领域,而且不同的问题可能需要不同的解决策略。
  10. 错误的预期和处理:
  • 设计代码时要考虑到错误的可能性。使用异常处理、断言和合适的错误处理来提高代码的健壮性。

感谢阅读。

  • 20
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

用哲学编程

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

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

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

打赏作者

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

抵扣说明:

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

余额充值