文章目录
PTA刷题地址:https://pintia.cn
Gitee项目地址:https://gitee.com/springmorningcxx/pta-solution-javascript
特别鸣谢:感谢智能助手小深的技术指导与情绪支持
前言 - 为什么选择Node.js刷PTA?
- 快速验证思路:熟悉的JS语法加速实现
- 现代化语法:ES6+特性简化代码逻辑
- 挑战精神:PTA时空限制是基于
C/C++
,使用NodeJS部分题目需极限优化,在C++主场杀出一条血路! (其实都没关系,主要是我C++写算法写腻了,想换NodeJS玩玩)划掉
刷题指南
本博文基于PTA程序设计类实验辅助平台中的PAT考试题目集,如使用其它平台,请确保你使用的OJ平台支持NodeJS环境
环境配置
建议使用VSCode编译器
PTA中NodeJS版本为12.22.12
,需要安装对应版本的@types/node
开启VSCode代码提示
npm仓库 中没有这个版本,我们装一个最接近的12.20.55
版本即可
npm i @types/node@12.20.55
注意:类型定义安装是可选的,仅用于获得更好的代码提示,不影响实际解题
基础步骤
- 创建js文件:在本地创建
solution.js
文件 - 粘贴模板:根据需求复制同步/异步模板到文件中(推荐同步模板)
- 编写逻辑:在题目解答区域编写解题代码
- 测试运行:
node solution.js
重要!不同于C++标准输入的自动结束,本地调试时输入结束方式如下:
- Windows: Ctrl+Z → Enter
- Linux/Mac: Ctrl+D
如果你不喜欢需要手动输入结束符的控制台输入,也可采用文件输入
node solution.js < input.txt
输入输出要点
操作类型 | 推荐方法 | 示例 |
---|---|---|
单数字输入 | number() | let n = number() |
多数字输入 | numbers()+解构赋值 | let [a, b] = numbers(); |
数字数组输入 | numbers() | const arr = numbers() |
格式化输出 | 模板字符串 | console.log(${a} ${b} ) |
数组输出 | 内置join()函数 | console.log(arr.join(" ")) |
高性能输出 | 数组收集+单次process.stdout.write() | 关键优化!见下方优化示例❗ |
解题模板
模板提供了三个输入函数
input()
用于读取一行字符串number()
用于读取一行中的单个数字numbers()
用于读取一行中,按空格分割后转为数字数组
同步输入模板(推荐)⭐️
同步输入模板性能更高,推荐所有PTA题目使用。
注:如果题目中空格是字符串的合法输入,需要将第二行的trimEnd()删除
"use strict";
const data = require("fs").readFileSync(0, "utf-8").trimEnd().split("\n");
let line = 0;
const input = () => data[line++] || "";
const number = () => +input();
const numbers = () => input().split(" ").map(Number);
// ================ 题目解答区域 ================
// 在此处编写解题代码
// 示例:
// const n = number();
// const arr = numbers();
// console.log(arr.reduce((a, b) => a + b, 0));
// Windows下用Ctrl+Z结束输入,Linux下用Ctrl+D
// ============================================
异步输入模板
异步输入模板只在输入数据量极大(>100MB)时使用,但针对PTA场景,不存在这么大的数据,推荐无脑使用同步模板。提供异步模板主要应用于非PTA场景,例如无法使用require("fs")
的OJ。
"use strict";
(async () => {
const chunks = [];
for await (const chunk of process.stdin) chunks.push(chunk);
const data = Buffer.concat(chunks).toString().trimEnd().split("\n");
let line = 0;
const input = () => data[line++] || "";
const number = () => +input();
const numbers = () => input().split(" ").map(Number);
// ================ 题目解答区域 ================
// 在此处编写解题代码
// 示例:
// const n = number();
// const arr = numbers();
// console.log(arr.reduce((a, b) => a + b, 0));
// Windows下用Ctrl+Z结束输入,Linux下用Ctrl+D
// ============================================
})().catch(console.error);
优化教学
PTA平台使用NodeJS刷题在算法优化到极限仍超时的情况客观存在,由以下两点原因导致
- PTA并未针对不同语言做出不同的时空限制,时空限制是基于C/C++的,存在算法优化到极限仍超时的现象
- 此外,PTA的评测环境NodeJS版本为
12.22.12
,版本较低,优化可能不足。
👇 先不要气馁,建议尝试以下优化:
- 将同步模板的
trimEnd()
去掉(只有少数题目输入样例尾随空白字符需要trimEnd()
) - 将函数内联(特别是循环内的函数,注意别忘了同步模板的函数也能改为内联)
- 减少不必要的字符串操作
- 使用
"use strict";
开启严格模式,提升V8优化 - 减少高级特性的使用(如箭头函数、解构赋值、
for...of
循环等),这些特性在低版本Node中可能性能不足 - 使用
new Array(n)
代替[]
预分配内存 - 标准输入优化,变多次输出为单次输出,使用数组收集结果后单次输出(1045快速排序采用此项优化后直接从TLE超时变为99msAC)
- 使用
process.stdout.write
代替console.log
优化实例
PTA1025反转链表
以 PTA1025反转链表 为例,我在算法已经达到最优解依然超时的情况下做出如下优化,在用时378 / 400 ms
的情况下成功压线AC
以下是完整代码,优化过程已写至注释中
// 0. 使用"use strict"提升V8优化
"use strict";
// 1. 使用同步读取提升输入性能,并删除同步模板中无意义的trimEnd()
const data = require("fs").readFileSync(0, "utf-8").split("\n");
let line = 0;
// 2. 使用Map代替大数组
const nodeMap = new Map();
// 3. 预格式化函数 (减少padStart调用)
const formatAddr = (addr) => {
if (addr === -1) return "-1";
return addr.toString().padStart(5, "0");
};
// 读取首行,这里和下面的输入函数都改成了内联实现
const [head, totalNodes, k] = data[line++].split(" ").map(Number);
// 读取所有节点
for (let i = 0; i < totalNodes; i++) {
const [addr, val, next] = data[line++].split(" ").map(Number);
nodeMap.set(addr, { val, next });
}
// 4. 构建链表 (精确分配大小,避免动态扩容开销)
const list = new Array(totalNodes);
let idx = 0;
let current = head;
while (current !== -1) {
list[idx++] = current;
const node = nodeMap.get(current);
if (!node) break;
current = node.next;
}
// 5. 反转链表,核心算法从函数调用改成了内联实现,大大提速
for (let i = 0; i + k <= idx; i += k) {
// reverseRange(list, i, i + k - 1);
let start = i;
let end = i + k - 1;
while (start < end) {
const temp = list[start];
list[start] = list[end];
list[end] = temp;
start++;
end--;
}
}
// 6. 优化输出 (减少字符串操作)
const result = [];
for (let i = 0; i < idx; i++) {
const addr = list[i];
const node = nodeMap.get(addr);
const nextAddr = i < idx - 1 ? list[i + 1] : -1;
result.push(`${formatAddr(addr)} ${node.val} ${formatAddr(nextAddr)}`);
}
// 一次性输出 (比多次console.log快3-5倍)
process.stdout.write(result.join("\n"));
PTA1045快速排序
这题比较特殊,时间限制只有200 ms
,只对Java的放宽限制到800ms
,很明显对NodeJS这种动态语言不公平,原先我的算法已经是最优却依然超时,但我对标准输出进行优化后,直接获得了由TLE到用时99 / 200 ms
成功AC的巨幅提升
我原先的标准输出如下
console.log(cnt);
for (let i = 0; i < cnt; i++) {
if(i != 0) process.stdout.write(" ");
process.stdout.write(v[i].toString());
}
console.log();
我把在循环中多次调用输出函数改为先将结果收集到result数组中统一输出,并统一用process.stdout.write
,成功AC(从OJ反馈结果看至少有两倍以上的性能提升)
process.stdout.write(`${cnt}\n`);
const result = []
for (let i = 0; i < cnt; i++) {
result.push(v[i].toString());
}
process.stdout.write(`${result.join(" ")}\n`);
完整实现
"use strict";
const data = require("fs").readFileSync(0, "utf-8").split("\n");
let line = 0;
const input = () => data[line++] || "";
const number = () => +input();
const numbers = () => input().split(" ").map(Number);
// ================ 题目解答区域 ================
let n = Number(data[line++]);
let a = data[line++].split(" ").map(Number);
let b = [...a]; // 复制数组a到b
// 对数组a进行排序
a.sort((x, y) => x - y);
// 初始化变量
let max = 0;
let cnt = 0;
let v = [];
// 遍历数组并填充v数组
for (let i = 0; i < n; i++) {
if (a[i] === b[i] && b[i] > max) {
v[cnt++] = b[i];
}
if (b[i] > max) {
max = b[i];
}
}
// 输出结果
process.stdout.write(`${cnt}\n`);
const result = []
for (let i = 0; i < cnt; i++) {
result.push(v[i].toString());
}
process.stdout.write(`${result.join(" ")}\n`);
PTA常见问题 确认自己的方法没错却总有测试点报错?
对于非C/C++/Java写题目的同学,这个问题客观存在,我在1010一元多项式求导、1055集体照、1066 图像过滤均发现了这种情况
问题原因❗❗❗
测试用例给错了,多了空白符(因为题目都是基于C/C++测试的,C++的cin>>x
会自动过滤空白符,C语言、Java也类似,所以PTA平台只保证C/C++不会出问题)
- 有的测试用例分隔符会有多个空格或为其他空白符,尝试将
split(" ")
改为split(/\s+/)
,使用正则分割处理连续空格 - 整个输入会有前导和尾随空格,把模板中
trimEnd()
改成trim()
- 每行输入都可能会有前导和尾随空格,需要在
input()
后面加上trim()
你可以参考我下面的题解自行修正模板,我个人认为这种情况比较少,没有在模板中加入
1066 图像过滤
"use strict";
// 修复 改为.trim()
const data = require("fs").readFileSync(0, "utf-8").trim().split("\n");
let line = 0;
// 修复 加入.trim()
const input = () => data[line++].trim() || "";
const number = () => +input();
// 修复 改为.split(/\s+/)
const numbers = () => input().split(/\s+/).map(Number);
// ================ 题目解答区域 ================
let [n, m, a, b, g] = numbers();
for (let i = 0; i < n; i++) {
let parts = numbers().map((v) => {
return (v >= a && v <= b ? g : v).toString().padStart(3, "0");
});
console.log(parts.join(" "));
}
确实无解(仅一题有这种情况)
1052 卖个萌
编码格式的问题,输入输出需要用GBK编码
我尝试过实现轻量级的编码器,但是PTA平台的NodeJS环境的TextDecode()
API不支持GBK编码
而在PTA平台代码长度16KB的限制下自己实现TextDecode()
完全不现实(至少得上百K)
参考博客:
https://www.acwing.com/solution/content/171720/
https://www.cnblogs.com/index-html/p/js-str-to-gbk-ultra-lite.html
结语
在高性能的C++统治的算法竞赛世界,用JavaScript这种动态语言刷PTA就像拿着水果刀上战场。但当你用这套框架连续AC十道题时——那种逆版本而行的快感,是任何语言都给不了的!(๑•̀ㅂ•́)و✧
GitHub传送门:https://gitee.com/springmorningcxx/pta-solution-javascript
如果帮到你,欢迎点亮⭐️支持!