news 2026/8/9 19:50:56

PAT乙级1040题解析:高效统计字符串子序列

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
PAT乙级1040题解析:高效统计字符串子序列

1. PAT乙级1040题目解析与实现

作为一名参加过多次PAT考试的程序员,我想分享一下我对乙级1040这道字符串处理题目的解题思路和代码实现。这道题在PAT乙级考试中属于中等难度,主要考察对字符串操作的熟练程度和算法优化能力。

1.1 题目要求分析

题目给定一个只包含P、A、T三种字母的字符串,要求统计其中能够组成"PAT"这个单词的子序列数量。这里的子序列指的是保持原有字符顺序的组合,比如字符串"PPATTT"中,有效的子序列有:

  • P(1)A(3)T(4)
  • P(1)A(3)T(5)
  • P(1)A(3)T(6)
  • P(2)A(3)T(4)
  • P(2)A(3)T(5)
  • P(2)A(3)T(6)

1.2 核心算法思路

最直观的暴力解法是三重循环枚举所有可能的P、A、T组合,但这种方法时间复杂度为O(n³),对于长字符串会超时。我们需要更高效的算法:

  1. 预处理阶段:

    • 统计每个字符左侧P的数量
    • 统计每个字符右侧T的数量
  2. 计算阶段:

    • 遍历字符串,当遇到字符A时
    • 将左侧P的数量 × 右侧T的数量累加到结果中

这种方法将时间复杂度降到了O(n),能够处理最大长度的输入。

1.3 代码实现细节

#include <iostream> #include <string> using namespace std; const int MOD = 1000000007; int main() { string s; cin >> s; int len = s.length(); // 预处理左侧P的数量 int leftP[len] = {0}; for(int i = 0; i < len; i++) { if(i > 0) leftP[i] = leftP[i-1]; if(s[i] == 'P') leftP[i]++; } // 预处理右侧T的数量 int rightT[len] = {0}; for(int i = len-1; i >= 0; i--) { if(i < len-1) rightT[i] = rightT[i+1]; if(s[i] == 'T') rightT[i]++; } // 计算结果 long long ans = 0; for(int i = 0; i < len; i++) { if(s[i] == 'A') { ans = (ans + leftP[i] * rightT[i]) % MOD; } } cout << ans << endl; return 0; }

1.4 关键点说明

  1. 预处理数组:

    • leftP[i]表示s[0..i]中P的个数
    • rightT[i]表示s[i..n-1]中T的个数
  2. 模运算处理:

    • 题目要求结果对1000000007取模
    • 需要在累加时就进行模运算,防止溢出
  3. 数据类型选择:

    • 使用long long存储结果,避免int溢出

2. 算法优化与边界情况

2.1 空间复杂度优化

上述实现使用了两个辅助数组,空间复杂度为O(n)。可以进一步优化:

int countPAT(string s) { int p = 0, a = 0, t = 0; for(char c : s) { if(c == 'P') p++; else if(c == 'A') a = (a + p) % MOD; else if(c == 'T') t = (t + a) % MOD; } return t; }

这种方法只需要常数空间,更加高效。

2.2 边界情况处理

需要特别注意以下几种特殊情况:

  1. 空字符串:应该返回0
  2. 没有A的字符串:结果必然为0
  3. 超长字符串(10^5个字符):确保算法时间复杂度为O(n)
  4. 全是P或全是T的字符串:结果应该为0

3. 测试用例设计

为了验证代码的正确性,应该设计以下几类测试用例:

  1. 常规情况:

    • 输入:"PPATTT"
    • 输出:6
  2. 无A情况:

    • 输入:"PPPTTT"
    • 输出:0
  3. 边界情况:

    • 输入:"PAT"
    • 输出:1
  4. 最大长度测试:

    • 输入:10^5个字符的随机PAT字符串
    • 输出:验证不超时

4. 常见错误与调试技巧

4.1 常见错误类型

  1. 数组越界:

    • 预处理数组时没有正确处理首尾边界
    • 解决方法:检查循环的起始和终止条件
  2. 整数溢出:

    • 没有及时取模导致中间结果溢出
    • 解决方法:在每次累加后立即取模
  3. 逻辑错误:

    • 混淆字符顺序,如把T放在A前面
    • 解决方法:仔细检查字符判断条件

4.2 调试技巧

  1. 打印中间变量:

    • 输出预处理数组的值
    • 验证每个A位置的计算结果
  2. 小规模测试:

    • 先用短字符串验证基本逻辑
    • 逐步增加字符串长度
  3. 对比暴力解法:

    • 对于小输入,用暴力解法验证优化解法的正确性

5. 性能分析与优化

5.1 时间复杂度分析

  1. 预处理阶段:

    • 两次线性扫描:O(n) + O(n) = O(n)
  2. 计算阶段:

    • 一次线性扫描:O(n)

总体时间复杂度为O(n),可以处理最大规模输入。

5.2 实际运行测试

在实际测试中,对于长度为10^5的字符串:

  • 优化解法:约50ms
  • 暴力解法:超时(>1000ms)

6. 类似题目拓展

掌握这道题后,可以尝试解决以下类似题目:

  1. LeetCode 828:统计唯一字符的子字符串
  2. LeetCode 1525:字符串的好分割数目
  3. PAT甲级1093:类似的子序列统计问题

这些题目都使用了类似的预处理和组合数学思想。

7. 个人解题心得

在实际编程中,我总结了以下几点经验:

  1. 先理解题意:

    • 明确子序列的定义
    • 确认输入输出要求
  2. 从暴力解法入手:

    • 先想清楚最直观的解法
    • 再考虑如何优化
  3. 画图辅助:

    • 绘制字符位置关系图
    • 标记预处理数组的含义
  4. 注意模运算:

    • 大数问题一定要及时取模
    • 防止中间结果溢出

这道题很好地训练了字符串处理能力和算法优化思维,建议编程初学者多练习此类题目,培养计算思维。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/9 19:47:12

快要毕业了,求一份毕业论文全套 AI 辅助工具推荐?

又到一年一度毕业冲刺阶段&#xff0c;相信不少应届毕业生正深陷毕业论文的难题当中&#xff1a;苦苦思索合适选题、翻看海量文献却整理不出文献综述、搭建不好论文逻辑大纲、写完初稿重复率超标、被学校 AIGC‑AI 痕迹检测困扰、繁杂的院校格式排版反复返工&#xff0c;开题报…

作者头像 李华
网站建设 2026/8/9 19:41:13

UE5实时3D高斯泼溅渲染:从原理到工程实现全解析

1. 项目概述&#xff1a;当UE5遇见高斯泼溅最近在图形学社区和游戏开发圈里&#xff0c;一个词的热度居高不下&#xff1a;3D Gaussian Splatting&#xff0c;简称3DGS。如果你关注过NeRF&#xff08;神经辐射场&#xff09;这类技术&#xff0c;那你对3DGS一定不会陌生。简单来…

作者头像 李华
网站建设 2026/8/9 19:39:29

SwarmForge实战教程:使用two-pack快速完成后端开发

SwarmForge实战教程&#xff1a;使用two-pack快速完成后端开发 【免费下载链接】swarm-forge A simple tool for coordinating several AI agents. 项目地址: https://gitcode.com/GitHub_Trending/sw/swarm-forge SwarmForge是一款基于tmux的AI代理协调工具&#xff0c…

作者头像 李华
网站建设 2026/8/9 19:38:35

未来理财规划:从零开始构建财务自由之路

1. 为什么我们需要"未来的你理财"&#xff1f;上周五晚上11点&#xff0c;我盯着手机银行里那个触目惊心的数字发呆——工作5年&#xff0c;存款还不到3万。那一刻我突然意识到&#xff1a;如果继续这样稀里糊涂地花钱&#xff0c;10年后的我可能还在为房租发愁。这就…

作者头像 李华