使用Node.js(JavaScript)高效刷PTA等OJ!(附高性能解题框架与优化教学)


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

注意:类型定义安装是可选的,仅用于获得更好的代码提示,不影响实际解题

基础步骤

  1. 创建js文件:在本地创建solution.js文件
  2. 粘贴模板:根据需求复制同步/异步模板到文件中(推荐同步模板)
  3. 编写逻辑:在题目解答区域编写解题代码
  4. 测试运行: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,版本较低,优化可能不足。

👇 先不要气馁,建议尝试以下优化:

  1. 将同步模板的trimEnd()去掉(只有少数题目输入样例尾随空白字符需要trimEnd()
  2. 将函数内联(特别是循环内的函数,注意别忘了同步模板的函数也能改为内联)
  3. 减少不必要的字符串操作
  4. 使用"use strict";开启严格模式,提升V8优化
  5. 减少高级特性的使用(如箭头函数、解构赋值、for...of循环等),这些特性在低版本Node中可能性能不足
  6. 使用new Array(n)代替[]预分配内存
  7. 标准输入优化,变多次输出为单次输出,使用数组收集结果后单次输出(1045快速排序采用此项优化后直接从TLE超时变为99msAC)
  8. 使用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++不会出问题)

  1. 有的测试用例分隔符会有多个空格或为其他空白符,尝试将split(" ")改为split(/\s+/),使用正则分割处理连续空格
  2. 整个输入会有前导和尾随空格,把模板中trimEnd()改成trim()
  3. 每行输入都可能会有前导和尾随空格,需要在input()后面加上trim()

你可以参考我下面的题解自行修正模板,我个人认为这种情况比较少,没有在模板中加入

1066 图像过滤

https://pintia.cn/problem-sets/994805260223102976/exam/problems/type/7?problemSetProblemId=994805266514558976&page=0

"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
如果帮到你,欢迎点亮⭐️支持!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值