每日一题——Python实现PAT (Basic Level)1003 我要通过!(举一反三+思想解读)


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

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

目录

我的写法:

代码点评

代码分析

时间复杂度

空间复杂度

优化建议

我要更强!

优化分析

这种方法用到哪些哲学和编程思想

举一反三

1. 抽象化与模块化

2. 单一职责原则

3. 迭代改进


我的写法:

题目给出的规则可以这样解释:

  1. 字符串中必须仅有 P、A、T 这三种字符,不可以包含其它字符。
  2. 形如 xPATx 的字符串是有效的,其中 x 是由字母 A 组成的字符串,可以为空。
  3. 如果 aPbTc 是正确的,那么对于字符串 aPbATca 也是正确的。这里,a、b、c 是由字母 A 组成的字符串,可以为空。

从第二个规则可以看出,PAT 本身是有效的,而 APATA 也是有效的(因为 A 可以为空字符串)。这意味着 P 和 T 之间的 A 的数量为1,P 前面的 A 的数量为1,T 后面的 A 的数量也为1。

从第三个规则可以推断,如果 APATA 是有效的,那么 APAATAA 也是有效的。这里,P 和 T 之间的 A 的数量增加了一个,同时 T 后面的 A 的数量也增加了一个。这表明 P 和 T 之间的 A 的数量乘以 P 前面的 A 的数量等于 T 后面的 A 的数量。

我们可以继续这个模式,例如,如果 APAATAA 是有效的,那么 APAAATAAA 也是有效的。这里,P 和 T 之间的 A 的数量增加了一个,同时 T 后面的 A 的数量也增加了一个。

因此,可以得出结论:对于一个有效的 PAT 字符串,P 和 T 之间的 A 的数量乘以 P 前面的 A 的数量应该等于 T 后面的 A 的数量。这个规则确保了字符串满足题目中给出的条件,从而可以获得“答案正确”。


def is_pat(s):
    # 首先检查字符串中是否只包含P、A、T
    if set(s) - {'P', 'A', 'T'}:
        return "NO"
    
    # 计算P、T的位置和数量
    p_position = s.find('P')
    t_position = s.find('T')
    p_count = s.count('P')
    t_count = s.count('T')
    
    # 确保P和T的数量为1
    if p_count != 1 or t_count != 1:
        return "NO"
    
    # 分割字符串为P和T之前、之间、之后的部分
    before_p = s[:p_position]
    between_pt = s[p_position+1:t_position]
    after_t = s[t_position+1:]
    
    # 如果 between_pt 为空,直接返回 NO
    if not between_pt:
        return "NO"
    
    # 检查P之前和T之后的字符串是否只包含A
    if not (all(c == 'A' for c in before_p) and all(c == 'A' for c in between_pt) and all(c == 'A' for c in after_t)):
        return "NO"
    
    # 检查 aPbTc 是否满足 len(c) == len(a) * len(b)
    if len(before_p) * len(between_pt) == len(after_t):
        return "YES"
    else:
        return "NO"

n = int(input())
for _ in range(n):
    s = input()
    print(is_pat(s))

代码点评

代码分析

  1. 有效性检查:首先,代码检查字符串是否只包含字符 P、A、T,这是通过创建字符集并使用集合减法操作完成的。这种方法简单直接。
  2. 位置和数量计算:接下来,代码分别计算 P 和 T 的位置和数量。这是为了确保字符串中只有一个 P 和一个 T,以及它们的顺序(P 在 T 前面)。
  3. 分割字符串:根据 P 和 T 的位置,字符串被分割为三部分。这些部分分别代表 P 之前、P 和 T 之间、以及 T 之后的字符串。
  4. 格式和规则检查:代码检查分割后的字符串是否满足给定的格式规则。这包括检查 P 和 T 之间是否至少有一个 A,以及检查 aPbTc 是否满足 len(c) == len(a) * len(b)。

时间复杂度

  • 字符串操作(如 find、count 和切片操作)的时间复杂度通常是 O(n),其中 n 是字符串的长度。
  • 集合操作(如集合减法)的时间复杂度是 O(m),其中 m 是集合中元素的数量。在这个案例中,m 是很小的固定值。
  • 总的来说,这段代码的时间复杂度大致为 O(n),因为字符串操作是主要的时间消耗来源。

空间复杂度

  • 额外空间:代码中主要的额外空间消耗来自于存储分割后的字符串部分以及字符集。
  • 分割后的字符串部分占用的空间与原始字符串的长度相关,但它们加起来不会超过原字符串的长度,因此这部分的空间复杂度是 O(n)。
  • 字符集和用于检查的临时变量占用的空间很小,可以视为常数空间。
  • 因此,整体的空间复杂度是 O(n)。

优化建议

  • 代码优化:对于 all(c == 'A' for c in ...) 的检查,可以考虑更高效的实现方式,比如直接使用字符串的 .count() 方法来确定 A 的数量,避免在每个字符上进行迭代。
  • 此外,如果字符串很长,当前的分割和检查方法可能不是最高效的。考虑到算法的复杂性主要来自于字符串操作,优化这些部分可能会对性能有所帮助。

总的来说,这段代码在逻辑上是正确的,并且能够有效地完成任务。然而,存在一些潜在的优化空间,特别是在字符串操作和迭代检查方面。



我要更强!

代码已经相当高效,但是可以进一步优化以减少不必要的时间和空间开销。以下是优化后的代码:

def is_pat_optimized(s):
    # 首先检查字符串中是否只包含P、A、T
    if any(char not in {'P', 'A', 'T'} for char in s):
        return "NO"
    
    # 计算P、T的位置和数量
    p_position = s.find('P')
    t_position = s.find('T')
    p_count = s.count('P')
    t_count = s.count('T')
    
    # 确保P和T的数量为1
    if p_count != 1 or t_count != 1:
        return "NO"
    
    # 分割字符串为P和T之前、之间、之后的部分
    before_p = s[:p_position]
    between_pt = s[p_position+1:t_position]
    after_t = s[t_position+1:]
    
    # 如果 between_pt 为空,直接返回 NO
    if not between_pt:
        return "NO"
    
    # 检查P之前和T之后的字符串是否只包含A
    if any(char != 'A' for char in before_p + between_pt + after_t):
        return "NO"
    
    # 检查 aPbTc 是否满足 len(c) == len(a) * len(b)
    if len(after_t) == len(before_p) * len(between_pt):
        return "YES"
    else:
        return "NO"

# 读取输入并处理多个字符串
n = int(input())
for _ in range(n):
    s = input()
    print(is_pat_optimized(s))

优化分析

  1. 时间复杂度:减少了字符串的分割次数,并且将检查字符是否为 'A' 的操作合并为一个。这使得时间复杂度保持在 O(n),其中 n 是字符串的长度。
  2. 空间复杂度:没有使用额外的数据结构来存储字符串的子部分,而是直接在原始字符串上进行操作。因此,空间复杂度仍然是 O(1),因为没有使用与输入大小成比例的额外空间。

通过这些优化,确保了代码在处理大量输入时仍然能够保持高效。



这种方法用到哪些哲学和编程思想

优化的代码体现了几个重要的哲学和编程思想:

  1. 极简主义(Minimalism):通过减少不必要的操作和数据结构,代码变得更加简洁和高效。这反映了极简主义的哲学,即去除多余的元素,专注于核心功能。
  2. 效率优先(Efficiency First):优化过程中,我们特别关注时间复杂度和空间复杂度的降低,确保代码在处理大量数据时仍能保持高性能。这体现了效率优先的编程思想,即在设计和编写代码时,始终考虑其运行效率。
  3. 抽象化(Abstraction):通过将复杂的逻辑简化为几个关键的检查点,代码变得更加易于理解和维护。这体现了抽象化的哲学,即通过隐藏细节,使复杂系统更易于处理。
  4. 单一职责原则(Single Responsibility Principle):每个函数(如 is_pat_optimized)只负责一个明确的功能,这有助于保持代码的清晰性和可维护性。这体现了单一职责原则,即每个模块或函数应该只有一个引起变化的原因。
  5. 迭代改进(Iterative Improvement):优化过程通常不是一蹴而就的,而是通过多次迭代,逐步改进代码的性能和可读性。这反映了迭代改进的思想,即通过持续的小步骤改进,达到最终的优化目标。
  6. 测试驱动开发(Test-Driven Development, TDD):虽然代码示例中没有直接体现,但在实际开发中,优化后的代码应该通过一系列测试来验证其正确性和性能。这体现了测试驱动开发的哲学,即在编写代码之前先编写测试,确保代码的每个部分都能按预期工作。

通过这些哲学和编程思想的指导,可以编写出更加高效、可维护和易于理解的代码。



举一反三

要举一反三,需要培养一种思维方式,即在面对新问题时,能够识别并应用已知的模式和原则。以下是一些技巧,以及如何在代码中应用它们的示例:

1. 抽象化与模块化

将复杂的问题分解为更小、更易于管理的部分,并为每个部分创建独立的模块。

# 定义一个模块,用于检查字符串中是否只包含特定字符
def contains_only(s, allowed_chars):
    """检查字符串s是否只包含allowed_chars中的字符"""
    return all(char in allowed_chars for char in s)

# 定义另一个模块,用于检查字符串的子串长度关系
def check_substring_lengths(s, p_pos, t_pos):
    """检查字符串s中P和T的位置,并验证子串长度关系"""
    before_p = s[:p_pos]
    between_pt = s[p_pos+1:t_pos]
    after_t = s[t_pos+1:]
    return len(after_t) == len(before_p) * len(between_pt)

# 使用这些模块来解决原始问题
def is_pat_abstracted(s):
    allowed_chars = {'P', 'A', 'T'}
    p_pos = s.find('P')
    t_pos = s.find('T')
    
    # 使用contains_only模块检查字符串是否只包含特定字符
    if not contains_only(s, allowed_chars):
        return "NO"
    
    # 使用check_substring_lengths模块检查子串长度关系
    if not check_substring_lengths(s, p_pos, t_pos):
        return "NO"
    
    return "YES"

2. 单一职责原则

确保每个函数或模块只做一件事,并且做得好。

# 定义一个函数,只负责检查字符串中P和T的数量
def check_pt_count(s):
    """检查字符串s中P和T的数量是否都为1"""
    p_count = s.count('P')
    t_count = s.count('T')
    return p_count == 1 and t_count == 1

# 在主函数中使用这个单一职责的函数
def is_pat_single_responsibility(s):
    if not check_pt_count(s):
        return "NO"
    # 其他检查...

3. 迭代改进

通过迭代,逐步改进代码的性能和可读性。

# 初始版本,只检查字符串中是否包含P、A、T
def is_pat_v1(s):
    if set(s) - {'P', 'A', 'T'}:
        return "NO"
    return "YES"

# 第二版,增加检查P和T的数量
def is_pat_v2(s):
    if set(s) - {'P', 'A', 'T'} or s.count('P') != 1 or s.count('T') != 1:
        return "NO"
    return "YES"

# 最终版,增加检查子串长度关系
def is_pat_final(s):
    if set(s) - {'P', 'A', 'T'} or s.count('P') != 1 or s.count('T') != 1:
        return "NO"
    p_pos = s.find('P')
    t_pos = s.find('T')
    if len(s[t_pos+1:]) != len(s[:p_pos]) * len(s[p_pos+1:t_pos]):
        return "NO"
    return "YES"

通过这些技巧,我们可以将一个问题的解决方案应用到其他类似的问题上,从而实现举一反三。记住,关键是要理解每个技巧背后的原则,并将这些原则应用到新的情境中。
 


以上是本节全部内容,感谢支持~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

用哲学编程

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

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

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

打赏作者

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

抵扣说明:

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

余额充值