目录
-
-
- 1.二分查找(简单)
- 2.HJ12字符串反转/HJ11数字颠倒/HJ106 字符逆序
- 3.HJ54 表达式求值
- 4.HJ76 尼科彻斯定理
- 5.HJ75 公共子串计算
- 6.求最大连续bit数(位运算)
- 7.HJ85 最长回文子串(字符串,穷举)
- 8.HJ100 等差数列
- 9.HJ87 密码强度等级
- 10. HJ10 字符个数统计(开始中等题,虽然这是一道简单题)
- 11.HJ46 截取字符串
- 12.HJ60 查找组成一个偶数最接近的两个素数
- 13.HJ40 统计字符
- 14.Hj14 字符串排序
- 15.HJ5 进制转换
- 16. HJ59 找出字符串中第一个只出现一次的字符
- 17.HJ81 字符串字符匹配
- 18 力扣简单 破冰游戏
- 19 力扣中等 无重复字符的最长子串
- 20.最长公共前缀
- 21 反转字符串中的单词
- 22. 字符串中的单词数
- 23.581. 最短无序连续子数组
- 24.1071. 字符串的最大公因子
- 25有效括号的嵌套深度
- 26. 二分查找
- 27 移除元素
- 28 根据字符出现频率排序
- 29 斐波那契数
- 30. 爬楼梯
- 31 使用最小花费爬楼梯
- 32 不同路径
- 33.不同路径 II
命令
网络
- ping – send ICMP ECHO_REQUEST packets to network hosts
The ping utility uses the ICMP protocol’s mandatory ECHO_REQUEST datagram to elicit an ICMP ECHO_RESPONSE from a host or gateway. ECHO_REQUEST datagrams (“pings”) have an IP and ICMP header, followed by a “struct timeval” and then an arbitrary number of “pad” bytes used to fill out the packet.
ICMP ICMP(Internet Control Message Protocol)是一种网络协议,用于在IP层发送控制消息。这些消息用于检查网络连接的可靠性、维护网络配置等。ICMP协议的主要用途是检测网络设备的状态和运行情况。
以下是一些常见的ICMP协议消息类型:
PING(回显请求):用于检查目标主机是否正常运行。 TCP Port Unreachable:表示目标主机上的特定TCP端口无法访问。 TTL Expired:表示数据包在传输过程中超过了指定的生存时间(TTL)。 ICMPv6:用于IPv6网络中的ICMP消息。
框架
vue
react
Refactoring to input with State
- 1.Identify your component’s different visual states
通过props控制虚拟dom
form.tsx
export default function Form({
status = 'empty'
}) {
if (status === 'success') {
return <h1>That's right!</h1>
}
return (
<>
<h2>City quiz</h2>
<p>
In which city is there a billboard that turns air into drinkable water?
</p>
<form>
<textarea disabled={
status === 'submitting'
} />
<br />
<button disabled={
status === 'empty' ||
status === 'submitting'
}>
Submit
</button>
{status === 'error' &&
<p className="Error">
Good guess but a wrong answer. Try again!
</p>
}
</form>
</>
);
}
import Form from './Form.js';
let statuses = [
'empty',
'typing',
'submitting',
'success',
'error',
];
export default function App() {
return (
<>
{statuses.map(status => (
<section key={status}>
<h4>Form ({status}):</h4>
<Form status={status} />
</section>
))}
</>
);
}
- 2.Determine what triggers those state changes
- 3.Represent the state in memory with useState
const [answer, setAnswer] = useState('');
const [error, setError] = useState(null);
- 4.Remove any non-essential state variables
- 5 .Connect the event handlers to set state
App.ts
import { useState } from 'react';
export default function Form() {
const [answer, setAnswer] = useState('');
const [error, setError] = useState(null);
const [status, setStatus] = useState('typing');
if (status === 'success') {
return <h1>That's right!</h1>
}
async function handleSubmit(e) {
e.preventDefault();
setStatus('submitting');
try {
await submitForm(answer);
setStatus('success');
} catch (err) {
setStatus('typing');
setError(err);
}
}
function handleTextareaChange(e) {
setAnswer(e.target.value);
}
return (
<>
<h2>City quiz</h2>
<p>
In which city is there a billboard that turns air into drinkable water?
</p>
<form onSubmit={handleSubmit}>
<textarea
value={answer}
onChange={handleTextareaChange}
disabled={status === 'submitting'}
/>
<br />
<button disabled={
answer.length === 0 ||
status === 'submitting'
}>
Submit
</button>
{error !== null &&
<p className="Error">
{error.message}
</p>
}
</form>
</>
);
}
function submitForm(answer) {
// Pretend it's hitting the network.
return new Promise((resolve, reject) => {
setTimeout(() => {
let shouldError = answer.toLowerCase() !== 'lima'
if (shouldError) {
reject(new Error('Good guess but a wrong answer. Try again!'));
} else {
resolve();
}
}, 1500);
});
}
useReducer
canvas
绘制三角形或多边形
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
// 绘制三角形
ctx.beginPath();
ctx.moveTo(10, 10);
ctx.lineTo(50, 100);
ctx.lineTo(10, 150);
ctx.closePath();
ctx.fillStyle = 'blue';
ctx.fill();
// 绘制多边形
ctx.beginPath();
ctx.moveTo(150, 10);
ctx.lineTo(200, 50);
ctx.lineTo(150, 100);
ctx.lineTo(100, 50);
ctx.closePath();
ctx.fillStyle = 'red';
ctx.fill();
基础
防抖
n 秒内函数只会执行一次
function debounce(fn, timing) {
let timer;
return function() {
clearTimeout(timer);
timer = setTimeout(() => {
fn();
}, timing);
}
}
常用于限制函数的执行频率,从而提高性能。例如,在滚动事件处理程序中,可能会触发一个函数来重新获取页面内容,但这个函数可能会被频繁触发,导致性能下降。使用 throttle 函数可以解决这个问题,确保在滚动事件触发后的 timing 时间间隔内只执行一次函数。
const debounce = (fn, delay) => {
let timer;
return ()=>{
clearTimeout(timer);
timer = setTimeout(()=>{
fn();
}, delay);
}
}
常用于避免事件处理程序或函数被频繁触发,从而提高性能。例如,在搜索输入框中,当用户输入时,可能会触发一个搜索函数,但这个函数可能会被频繁触发,导致性能下降。使用 debounce 函数可以解决这个问题,确保搜索函数只在用户停止输入后的 delay 毫秒后执行。
进程与线程
进程和线程是操作系统中实现多任务的重要概念。
进程是应用程序在操作系统中运行的基本单位。一个进程可以包含一个或多个线程,线程是进程的实际执行者,负责执行具体的任务。进程之间的内存空间是相互独立的,而线程共享进程的内存空间。
线程是进程内执行任务的更小的单位。线程在进程启动时创建,数量一般远小于进程的数量。线程之间可以并发执行,因此可以提高程序的执行效率。但是,线程之间的同步和互斥操作比较复杂,需要进行特殊的处理。
在Web前端开发中,进程和线程的概念并不是非常常用。但是,在某些情况下,如使用多进程的Node.js服务器,或者使用JavaScript的异步编程时,进程和线程的概念仍然具有重要的意义。
在现代浏览器中,JavaScript的执行环境是单线程的,这意味着在同一时刻只能执行一个任务。但是,浏览器提供了一些异步编程的API,如Promise、setTimeout、setInterval等,可以实现非阻塞的异步操作,从而提高程序的执行效率。
在某些情况下,可以使用多进程的Node.js服务器来提高Web前端的性能。Node.js使用了一个名为Child Process的模块,可以创建和管理子进程,从而实现多进程。使用多进程可以充分利用计算机的多核资源,提高程序的执行效率。
总之,进程和线程是操作系统实现多任务的重要概念,在Web前端开发中,虽然进程和线程的概念并不是非常常用,但是了解这些概念对于理解程序的执行原理和实现高性能程序仍然具有重要的意义。
节流
function throttle(fn, timing) {
let trigger;
return function() {
if (trigger) return;
trigger = true;
fn();
setTimeout(() => {
trigger = false;
}, timing);
}
}
Async/Await 如何通过同步的方式实现异步
generate
var fetch = require("node-fetch");
function *gen() { // 这里的 * 可以看成 async
var url = "https://api.github.com/users/github";
var result = yield fetch(url); // 这里的 yield 可以看成 await
console.log(result.bio);
}
var g = gen();
var result = g.next();
result.value.then(data => data.json()).then(data => g.next(data));
实现(5).add(3).minus(2)功能
Number.prototype.add = function(num) {
return this+num
}
Number.prototype.minus = function(num) {
return this-num
}
Number.prototype.multiply = function(num) {
return this*num
}
Number.prototype.divide = function(num) {
return this/num
}
1==‘1’
在JavaScript中,当你将一个字符串(如 “1”)与一个整数(如 1)进行比较时,JavaScript会自动将字符串转换为数字类型。这种隐式类型转换通常被称为类型转换。
在这个例子中,1 == ‘1’ 的结果为 true,因为JavaScript将字符串 “1” 转换为数字 1,然后比较它们是否相等。
这种隐式类型转换可能会导致一些问题,因为它可能会导致意外的错误。例如,如果你将一个字符串(如 “1”)传递给一个接受整数参数的函数,但该函数在处理该参数时没有考虑到可能的数据类型,那么可能会导致错误。
为了避免这种情况,你可以显式地将字符串转换为数字类型,例如:
const str = "1";
const num = parseInt(str, 10); // 将字符串转换为数字,第二个参数指定数字的进制(10表示十进制)
if (num == 1) {
console.log("true");
}
或者,如果你要将数字类型转换为字符串类型,可以使用 toString() 方法:
const num = 1;
const str = num.toString(); // 将数字转换为字符串
if (str == "1") {
console.log("true");
}
总之,为了避免隐式类型转换可能带来的问题,最好是在比较和转换数据时显式地使用适当的类型转换方法。
变量提升,块级作用域
for(var i =0;i<10;++i){
setTimeout(()=>{console.log(i)})
}
//10*10
for(let i =0;i<10;++i){
setTimeout(()=>{console.log(i)})
}
//0-9
或者直接使用立即执行函数 IIFEFE
for(var i =0;i<10;++i){
setTimeout(()=>{console.log(i)})
}
//10*10
没有宏任务了?
微任务队列(micro task queue)是一种用于处理异步操作的技术,它允许在浏览器执行JavaScript代码时能够同时处理多个任务。这些任务可以是任何需要长时间运行或需要等待的数据库操作、AJAX请求或其他异步操作。
在现代浏览器中,有一个内置的微任务队列,称为Promise。当使用async/await或async函数时,浏览器会自动使用Promise来处理异步操作。Promise是一种状态机,它可以记住之前完成的任务,并在新任务完成时更新其状态。
除了Promise,还有其他一些库和框架,如setTimeout、setInterval、requestAnimationFrame等,也可以用于创建微任务队列。这些库提供了一些更高级的功能,如错误处理、任务取消等。
总之,微任务队列是处理异步操作的一种强大技术,它允许浏览器在执行JavaScript代码时同时处理多个任务,从而提高应用程序的性能和响应能力。
数组的reduce方法
闭包:闭包是指一个函数可以访问其词法环境中的变量,即使函数执行完毕,这些变量仍然占用内存
JavaScript闭包是指一个函数可以访问其词法环境中的变量,即使函数执行完毕,这些变量仍然占用内存。闭包可以实现以下功能:
- 私有变量和函数:通过闭包,可以将函数内部的变量和函数封装为私有,防止外部访问。
- 保持变量状态:闭包可以保存函数外部变量的一个副本,即使函数外部变量被重新赋值,闭包中的变量仍然保持之前的状态。
- 实现柯里化(Currying):柯里化是指将多参数函数转换为一系列单参数函数的过程,闭包可以帮助实现柯里化。
- 实现事件处理程序和回调函数:闭包可以实现事件处理程序和回调函数,这些函数可以访问事件处理程序的上下文对象和事件对象。
- 实现模块化:通过闭包,可以将一组相关的函数和数据封装到一个对象中,从而实现模块化。
总之,闭包是JavaScript中一种强大的编程技术,可以帮助实现私有变量和函数、保持变量状态、实现柯里化、事件处理程序和回调函数、模块化等功能。
todo
gc
浏览器 garbage collection(GC)机制是指浏览器在运行时自动清理内存的机制。它是一种优化技术,旨在释放不再需要的内存,从而提高应用程序的性能和响应能力。
浏览器GC机制的主要目的是:
清除不再使用的对象和内存。 释放内存,以便为新的对象分配更多的空间。 减少内存碎片。 浏览器GC机制的实现方式因浏览器而异,但通常包括以下几个步骤:
标记阶段:标记所有活动对象,并将它们划分为可回收和不可回收两部分。 引用阶段:清除所有不可回收对象的引用,使它们变得可回收。 清理阶段:回收所有可回收对象。 需要注意的是,浏览器GC机制不是实时的,而是在一定时间间隔内自动执行。浏览器GC机制的执行时间通常取决于浏览器和操作系统的性能,以及应用程序的内存使用情况。
总之,浏览器GC机制是指浏览器在运行时自动清理内存的机制,旨在释放不再需要的内存,从而提高应用程序的性能和响应能力。
内存泄漏是指在应用程序中,由于某些原因,导致内存分配不正确,导致内存占用无法被垃圾回收器回收。这可能会导致应用程序崩溃、性能下降或内存不足等问题。
内存泄漏通常是由于以下原因之一:
- 全局变量:在函数外部声明的变量成为全局变量,即使函数执行完毕,全局变量仍然占用内存。
- 闭包:闭包是指一个函数可以访问其词法环境中的变量,即使函数执行完毕,这些变量仍然占用内存。
- 循环引用:两个对象互相引用,导致内存占用无法被垃圾回收器回收。
- 定时器:定时器没有正确地清除,导致内存占用无法被垃圾回收器回收。
- 未清除的DOM元素:未正确清除的DOM元素占用内存,导致内存占用无法被垃圾回收器回收。
- 未清除的CSS样式:未正确清除的CSS样式占用内存,导致内存占用无法被垃圾回收器回收。
v-for 的key
在 Vue 中,使用 v-for 指令遍历数组或其他可迭代对象时,需要提供一个唯一的标识符(key)来标识每个元素。这个 key 用于跟踪每个元素的状态,从而在数据发生变化时能够正确地更新视图。
key 的作用类似于 CSS 选择器,它可以帮助 Vue 更准确地找到需要更新的元素,从而提高性能。当数据发生变化时,Vue 可以通过比较新旧数据中的 key 值来判断哪些元素需要更新,哪些可以复用。
在 Vue 2 中,key 默认使用数组的索引值作为 key。在 Vue 3 中,key 默认为数组的第一个属性或对象的唯一属性。
301与302
状态码 302 和 301 是 HTTP 协议中用来表示重定向的两种状态码。
状态码 302 Found(找到)表示请求的资源已经被临时移动到另一个位置,并且将来可能会将返回这个资源。新的资源位置会在 HTTP 头部中的 “Location” 字段中给出。客户端应该在收到 302 响应时,重新发起请求到指定的新位置。
状态码 301 Moved Permanently(永久移动)表示请求的资源已经被永久移动到另一个位置。客户端应该在收到 301 响应时,将旧资源的位置记录下来,并将新的位置作为 future requests 的默认位置。
HTTP 头部中的 “Location” 字段用于告诉客户端重定向的目标 URL。客户端收到 302 响应时,应该在下次请求时使用这个 URL。客户端收到 301 响应时,应该在下次请求时使用这个 URL,并将旧资源的位置记录下来。
需要注意的是,状态码 302 和 301 的区别在于,当客户端收到 302 响应时,它需要重新发起请求;而当客户端收到 301 响应时,它不需要重新发起请求,因为资源已经被永久移动。因此,在实际应用中,可以根据具体需求选择使用 302 或 301 来进行重定向。
js用reduce反转字符串
在 JavaScript 中,可以使用数组的 reduce() 方法来反转字符串。reduce() 方法接受一个回调函数作为参数,回调函数有四个参数:累加器(accumulator),当前值(currentValue),当前索引(currentIndex),源数组(array)。回调函数的返回值将作为累加器的新值,最后 reduce() 方法返回累加器的结果。
下面是一个使用 reduce() 方法反转字符串的示例:
const str = 'Hello, world!';
const reversedStr = str.split('').reduce((accumulator, currentValue) => {
return currentValue + accumulator;
}, '');
console.log(reversedStr); // 输出:!dlrow ,olleH
在这个示例中,我们首先将字符串分割成数组,然后使用 reduce() 方法将数组中的每个元素从后向前拼接成一个新的字符串。回调函数的第一个参数 accumulator 表示累加器,它初始值为空字符串。每次迭代时,回调函数将当前值 currentValue 添加到累加器的末尾,然后将累加器的新值赋值给 accumulator。最后,reduce() 方法返回累加器的结果,即反转后的字符串。
需要注意的是,reduce() 方法只能接受一个累加器参数,因此不能直接将字符串作为第一个参数传递给 reduce() 方法。我们需要先将字符串分割成数组,然后将数组作为参数传递给 reduce() 方法。
场景
控制多人同时编辑的技术
- 实时数据库(Real-time Database):使用实时数据库,如 Firebase 或 RealtimeDB,可以将数据实时同步到多个客户端。这使得用户可以在多个设备上实时查看和编辑相同的数据。
- 实时通信服务(Real-time Communication Services):使用实时通信服务,如 Google Hangouts、Microsoft Teams 或 Slack,可以在多个设备上实时沟通和协作。这使得用户可以在多个浏览器窗口中同时编辑和讨论同一个文档。
- 实时应用程序(Real-time Applications):使用实时应用程序,如 Google Docs、Microsoft Word 或 Google Sheets,可以在多个设备上实时编辑和查看同一个文档。这使得用户可以在多个浏览器窗口中同时编辑和查看同一个文档。
- 分布式计算(Distributed Computing):使用分布式计算,如 Apache Spark、Hadoop 或 Flink,可以将数据实时同步到多个节点,以便在多台计算机上实时处理和分析数据。
- 分布式文件系统(Distributed File System):使用分布式文件系统,如 Amazon S3、Google Cloud Storage 或 Azure Blob Storage,可以将文件实时同步到多个节点,以便在多台计算机上实时访问和编辑文件。
这些技术都可以实现多人同时编辑,具体选择哪种技术取决于具体应用的需求和场景。
项目如何防止xss攻击,分前后端角度
防止XSS攻击(跨站脚本攻击)的方法有很多,下面是前后端分别可以采取的措施:
前端防止XSS攻击:
- 使用浏览器的内置过滤器:浏览器会自动过滤掉大部分XSS攻击,如 script 等特殊字符。
- 使用编码:将用户输入的数据进行编码,如 escape() 或 encodeURIComponent(),使其不能被解释为恶意脚本。
- 使用标签属性:将用户输入的数据作为属性值,而不是作为标签内容,这样可以减少被解释为恶意脚本的可能。
- 使用自定义模板引擎:自定义模板引擎可以对用户输入进行更严格的过滤,防止XSS攻击。
- 使用输入验证:对用户输入进行验证,确保输入的数据符合预期的格式和范围。
- 使用输出过滤器:对输出数据进行过滤,确保输出数据中不包含任何恶意脚本。
后端防止XSS攻击:
- 使用编码库:使用编码库,如PHP的htmlspecialchars(),可以自动过滤掉恶意脚本。
- 使用参数化查询:使用参数化查询,可以防止SQL注入攻击,同时防止XSS攻击。
- 使用输出过滤器:对输出数据进行过滤,确保输出数据中不包含任何恶意脚本。
- 使用过滤库:使用过滤库,如PHP的filter_var(),可以对用户输入进行更严格的过滤,防止XSS攻击。
- 使用输入验证:对用户输入进行验证,确保输入的数据符合预期的格式和范围。
- 使用输出编码:对输出数据进行编码,可以防止被解释为恶意脚本。
总之,前后端都要采取一系列措施来防止XSS攻击,以确保用户输入的安全性。
浏览器渲染原理
浏览器的渲染原理可以简单地描述为以下几个步骤:
解析 HTML
浏览器将 HTML 文档解析成 DOM(文档对象模型)树,以便浏览器可以理解和操作 HTML 元素。
浏览器解析 HTML 时使用的方案主要有两种:SAX 和 DOM。
SAX(Simple API for XML)解析器:SAX 解析器是一种基于事件驱动的解析方式,它会将 HTML 文件分割成一系列的事件,然后逐个处理这些事件。SAX 解析器不会将整个 HTML 文档加载到内存中,因此非常适合处理大型的 HTML 文档。SAX 解析器的主要缺点是,它不支持在解析过程中修改 DOM(Document Object Model,文档对象模型)。 DOM(Document Object Model)解析器:DOM 解析器会将 HTML 文件解析成 DOM 树,然后通过 DOM API 进行操作。DOM 解析器会将整个 HTML 文档加载到内存中,因此适用于需要对 HTML 文件进行复杂操作的场景。DOM 解析器的主要优点是支持在解析过程中修改 DOM,但可能会导致解析速度变慢。 在实际应用中,浏览器通常会根据 HTML 文档的大小和复杂程度选择合适的解析方式。对于较小的 HTML 文档,SAX 解析器更为高效;而对于较大的 HTML 文档,DOM 解析器更为高效。此外,浏览器可能会在解析过程中自动切换解析方式,以提高性能。
需要注意的是,尽管 DOM 解析器支持在解析过程中修改 DOM,但在解析完成后,浏览器可能会对 DOM 进行优化,例如清除不再需要的节点、压缩 DOM 等,以减小内存占用和提高性能。因此,在实际应用中,尽量使用 DOM 解析器,并在解析完成后进行优化。
解析 CSS
浏览器将 CSS 样式表解析成 CSSOM(样式对象模型)树,以便浏览器可以理解和应用 CSS 规则。
构建渲染树
浏览器将 DOM 和 CSSOM 合并成一个渲染树,以便浏览器可以绘制屏幕上的内容。
布局
浏览器使用布局引擎计算渲染树中每个节点的宽度和高度,以便将它们放置在屏幕上的适当位置。
绘制
浏览器使用 GPU(图形处理单元)绘制渲染树中的每个节点,以便将它们显示在屏幕上。
重排和重绘
如果渲染树中的某个节点发生了变化,例如,元素的属性或内容发生了变化,那么浏览器将重新计算渲染树,并重新绘制屏幕上的内容。这个过程被称为重排。如果只是修改了某个节点的属性,例如,修改了它的样式或大小,那么浏览器将只重新计算需要重新绘制的部分,这个过程被称为重绘。 浏览器渲染过程的复杂性在于,它需要同时处理 HTML、CSS 和布局。此外,浏览器需要处理多种设备类型和屏幕尺寸,以确保网页在不同设备和屏幕上都能正常显示。因此,浏览器的渲染原理可以被视为一种复杂的高级算法。
网络内容拦截
从网络的角度来看,拦截内容通常是通过路由器、代理服务器等网络设备实现的。这些设备可以对网络通信进行拦截和处理,从而实现内容的过滤、篡改、拦截等操作。
以路由器为例,路由器可以作为网络通信的入口和出口,可以将网络通信中的数据包进行拦截和处理。当数据包从一台计算机发出,经过路由器,到达目标计算机时,路由器可以对数据包进行查看、修改或丢弃等操作。如果路由器对数据包进行了拦截和处理,数据包将不会到达目标计算机,从而实现内容的过滤、篡改、拦截等操作。
除了路由器之外,代理服务器也可以实现内容的拦截和处理。代理服务器可以作为网络通信的转发器,可以将网络通信中的数据包进行拦截和处理,然后转发到目标计算机。代理服务器可以实现对内容的过滤、篡改、拦截等操作,从而实现对网络通信的掌控。
总之,拦截内容的实现可以通过路由器、代理服务器等网络设备来实现,这些设备可以对网络通信中的数据包进行查看、修改或丢弃等操作,从而实现内容的过滤、篡改、拦截等操作。
算法
js写算法需要提前掌握
以下是一些在编写JavaScript代码时需要提前掌握的方法、函数和技巧:
变量声明和方法
在JavaScript中,变量声明非常重要。可以使用var、let和const关键字声明变量。其中,let关键字声明的变量具有块级作用域,而const关键字声明的变量不可更改。
此外,方法(函数)在JavaScript中是非常重要的。可以使用function关键字定义方法,并使用参数传递数据。方法可以返回值,也可以不返回值。
对象和数组
JavaScript中可以使用对象(Object)和数组(Array)来存储和操作数据。对象可以使用点(.)或方括号([])访问属性,数组可以使用索引访问元素。
条件语句和循环
条件语句(如if、else if、else)用于根据条件执行不同的代码块。循环语句(如for、while、do-while)用于重复执行一段代码。
函数和模块
JavaScript中可以使用函数来封装一段代码,使其更易于维护和 reuse。模块(module)是一种组织代码的方法,可以将相关功能分组在一起。
箭头函数和箭头操作符
箭头函数是一种简洁的函数定义方式,可以简化代码。箭头操作符(=>)用于将函数的返回值与参数绑定在一起。
Promise和async/await
Promise是一种处理异步操作的方法,可以确保异步操作按预期进行。async/await是ES2017中引入的新语法,用于简化异步编程。
数组方法
JavaScript提供了许多数组方法,如push、pop、shift、unshift、splice、sort、reverse等,这些方法可以方便地对数组进行操作。
字符串方法
JavaScript提供了许多字符串方法,如toUpperCase、toLowerCase、substring、slice、indexOf、lastIndexOf、trim等,这些方法可以方便地对字符串进行操作。
正则表达式
JavaScript提供了正则表达式来处理字符串。可以使用RegExp构造函数创建正则表达式对象,并使用正则表达式的方法来匹配和替换字符串。
全局对象和内置函数
JavaScript中有许多全局对象和内置函数,如window、document、console、Math、Date等。这些对象和函数可以方便地进行浏览器操作和数据处理。
exam
1.二分查找(简单)
输入不重复有序整数数组,使用空格隔开。查找特定整数,返回数组下标。不存在则返回-1。
function findIndex(arr, target) {
let left = 0;
let right = arr.length - 1;
while (left <= right) {
const mid = Math.floor((left + right) / 2);
if (arr[mid] === target) {
return mid;
} else if (arr[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return -1;
}
const arr = [1, 3, 5, 7, 9];
const target = 5;
console.log(findIndex(arr, target)); // 输出:2
2.HJ12字符串反转/HJ11数字颠倒/HJ106 字符逆序
const readline = require("readline");
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
rl.on("line", function (line) {
console.log(line.split('').reverse().join(''));
});
line.split('').reverse().join('')
3.HJ54 表达式求值
如果能使用eval的话会很简单
const rl = require("readline").createInterface({ input: process.stdin });
var iter = rl[Symbol.asyncIterator]();
const readline = async () => (await iter.next()).value;
void async function () {
// Write your code here
while(line = await readline()){
let res = eval(line);
}
}()
4.HJ76 尼科彻斯定理
const rl = require("readline").createInterface({ input: process.stdin });
var iter = rl[Symbol.asyncIterator]();
const readline = async () => (await iter.next()).value;
void async function () {
// Write your code here
while(line = await readline()){
let num =Number(line);
let res = []
let mid = Math.pow(num,3)/num;
for(let i= 0;i<num;i++){
res.push(mid+1-(num/2-i)*2);
}
console.log(res.join('+'));
}
}()
5.HJ75 公共子串计算
给定两个只包含小写字母的字符串,计算两个字符串的最大公共子串的长度。 注:子串的定义指一个字符串删掉其部分前缀和后缀(也可以不删)后形成的字符串。
输入:asdfas werasdfaswer
输出:6
const rl = require("readline").createInterface({ input: process.stdin });
var iter = rl[Symbol.asyncIterator]();
const readline = async () => (await iter.next()).value;
void async function () {
// Write your code here
let str = []
while(line = await readline()){
str.push(line);
}
let str_1=str[0];
let str_2=str[1];
let short = str_1.length>str_2.length?str_2:str_1;
let long = str_1.length>str_2.length?str_1:str_2;
let max = 0;
let dp = new Array(short.length);
for(let i=0;i<short.length;i++){
dp[i] = new Array(long.length).fill(0);
for(let j=0;j<long.length;j++){
if(short[i]===long[j]){
if(i>0&&j>0){
dp[i][j] = dp[i-1][j-1]+1;
}
else{
dp[i][j]=1;
}
}else{
dp[i][j]=0;
}
if(max<dp[i][j]){
max=dp[i][j];
}
}
}
console.log(max);
}()
6.求最大连续bit数(位运算)
求一个int类型数字对应的二进制数字中1的最大连续数,例如3的二进制为00000011,最大连续2个1
输入: 200 输出: 2 说明: 200的二进制表示是11001000,最多有2个连续的1。
const rl = require("readline").createInterface({ input: process.stdin });
var iter = rl[Symbol.asyncIterator]();
const readline = async () => (await iter.next()).value;
void async function () {
// Write your code here
while(line = await readline()){
let num = Number(line);
let max =0;
let temp =0;
while(num){
console.log(num,num%2);
if(num%2===1){
temp++;
}
else{
temp =0;
}
if(temp>=max){
max = temp;
}
num = num >>1;
}
console.log(max);
}
}()
7.HJ85 最长回文子串(字符串,穷举)
给定一个仅包含小写字母的字符串,求它的最长回文子串的长度。 所谓回文串,指左右对称的字符串。 所谓子串,指一个字符串删掉其部分前缀和后缀(也可以不删)的字符串
const rl = require("readline").createInterface({ input: process.stdin });
var iter = rl[Symbol.asyncIterator]();
const readline = async () => (await iter.next()).value;
void async function () {
function isHui(s){
return s.split('').reverse().join('')===s;
}
// Write your code here
while(line = await readline()){
let tokens = line.split(' ');
if(line.length<3){
console.log(line.length)
}
let max =2;
for (let i=2;i<line.length;i++){
let left =i-1;
let right =i+1;
while(line[left]===line[i]&&left>=0){
left--;
}
while(line[right]===line[i]&&right<line.length){
right++;
}
let sum = right-left-1;
while(line[left]===line[right]&&left>=0&&right<line.length){
left--;
right++;
sum+=2;
}
if(sum>max){
max=sum
}
}
console.log(max)
}
}()
8.HJ100 等差数列
等差数列 2,5,8,11,14。。。。 (从 2 开始的 3 为公差的等差数列) 输出求等差数列前n项和
const rl = require("readline").createInterface({ input: process.stdin });
var iter = rl[Symbol.asyncIterator]();
const readline = async () => (await iter.next()).value;
void async function () {
// Write your code here
while(line = await readline()){
let n =Number(line);
let res = Math.pow(n,2)*3/2+n/2;
console.log(res);
}
}()
9.HJ87 密码强度等级
const rl = require("readline").createInterface({ input: process.stdin });
var iter = rl[Symbol.asyncIterator]();
const readline = async () => (await iter.next()).value;
void async function () {
// Write your code here
while(line = await readline()){
let count =0;
let len = line.length;
if(len<1){
console.log("VERY_WEAK");
return;
}
{
if(len<=4){
count+=5;
}
else if(len<=7){
count+=10;
}
else{
count+=25;
}
}
let hasLetter =false;
let hasAllLetter = false;
if(new RegExp("[a-z,A-Z]","g").test(line))
{
hasLetter =true;
if(line.toUpperCase()===line||line.toLowerCase()===line){
count+=10;
}
else{
hasAllLetter=true;
count+=20;
}
}
let hasNum =false;
if(new RegExp("[0-9]","g").test(line))
{
hasNum = true;
let match_num = line.matchAll(/[0-9]/g);
let flag = 0;
while(!match_num.next().done){
flag++
}
if(flag>1){
count+=20;
}
else{
count+=10;
}
}
let hasSig = false;
//符号
if(new RegExp(/[\x21-\x2F,\x3A-\x40,\x5B-\x60,\x7B-\x7E]/,"g").test(line))
{
hasSig =true;
let match_num = line.matchAll(/[\x21-\x2F,\x3A-\x40,\x5B-\x60,\x7B-\x7E]/g);
let flag = 0;
while(!match_num.next().done){
flag++
}
if(flag>1){
count+=25;
}
else{
count+=10;
}
}
if(hasLetter&&hasNum){
if(hasAllLetter&&hasSig){
count+=5;
}else if(hasSig){
count+=3
}else
count+=2
}
console.log(count>=90?"VERY_SECURE":count>=80?"SECURE":count>=70?"VERY_STRONG":count>=60?"STRONG":count>=50?"AVERAGE":count>=25?"WEAK":"VERY_WEAK");
}
}()
10. HJ10 字符个数统计(开始中等题,虽然这是一道简单题)
编写一个函数,计算字符串中含有的不同字符的个数。字符在 ASCII 码范围内( 0~127 ,包括 0 和 127 ),换行表示结束符,不算在字符里。不在范围内的不作统计。多个相同的字符只计算一次 例如,对于字符串 abaca 而言,有 a、b、c 三种不同的字符,因此输出 3 。
const rl = require("readline").createInterface({ input: process.stdin });
var iter = rl[Symbol.asyncIterator]();
const readline = async () => (await iter.next()).value;
void async function () {
// Write your code here
while(line = await readline()){
let tokens = line.split('');
let list = [...new Set(tokens)];
console.log(list.length);
}
}()
11.HJ46 截取字符串
const rl = require("readline").createInterface({ input: process.stdin });
var iter = rl[Symbol.asyncIterator]();
const readline = async () => (await iter.next()).value;
void async function () {
let res =[]
// Write your code here
while(line = await readline()){
res.push(line)
}
console.log(res[0].slice(0,parseInt(res[1])))
}()
12.HJ60 查找组成一个偶数最接近的两个素数
任意一个偶数(大于2)都可以由2个素数组成,组成偶数的2个素数有很多种情况,本题目要求输出组成指定偶数的两个素数差值最小的素数对。
要判断一个数是否为素数,可以使用以下方法:
遍历从 2 到该数本身的所有整数,判断它们是否能被该数整除。 如果该数能被其中任意一个整数整除,则该数不是素数。 如果该数不能被任何整数整除,则该数是素数。
const rl = require("readline").createInterface({ input: process.stdin });
var iter = rl[Symbol.asyncIterator]();
const readline = async () => (await iter.next()).value;
void (async function () {
// Write your code here
function isSu(n) {
if (n >= 2) {
for (let i = 2; i < Math.ceil(n / 2); i++) {
if (n % i == 0) {
return false;
}
}
return true;
} else {
return false;
}
}
while ((line = await readline())) {
let mid = Number(line) / 2;
let l = 0,
g = 0;
for (let i = 0; i < mid; i++) {
// console.log(isSu(mid-i),isSu(mid+i),mid,i)
if (isSu(mid - i) && isSu(mid + i)) {
l = mid - i;
g = mid + i;
console.log(l);
console.log(g);
return;
}
}
}
})();
13.HJ40 统计字符
输入一行字符,分别统计出包含英文字母、空格、数字和其它字符的个数
const rl = require("readline").createInterface({ input: process.stdin });
var iter = rl[Symbol.asyncIterator]();
const readline = async () => (await iter.next()).value;
void async function () {
// Write your code here
while(line = await readline()){
let sl = 0;
let sk = 0;
let sn = 0;
let match_l = line.toLowerCase().matchAll(/[a-z]/g);
while(!match_l.next().done){
sl++
};
let match_k = line.matchAll(" ");
while(!match_k.next().done){
sk++
};
let match_n = line.matchAll(/[0-9]/g);
while(!match_n.next().done){
sn++
};
console.log("%d\n%d\n%d\n%d",sl,sk,sn,line.length-sl-sk-sn)
}
}()
14.Hj14 字符串排序
const rl = require("readline").createInterface({ input: process.stdin });
var iter = rl[Symbol.asyncIterator]();
const readline = async () => (await iter.next()).value;
void async function () {
let lsit = [];
// Write your code here
while(line = await readline()){
lsit.push(line);
}
let n = lsit.shift();
lsit = lsit.sort()
for(let i =0;i<Number(n);i++){
console.log(lsit[i]);
}
}()
15.HJ5 进制转换
写出一个程序,接受一个十六进制的数,输出该数值的十进制表示。
const rl = require("readline").createInterface({ input: process.stdin });
var iter = rl[Symbol.asyncIterator]();
const readline = async () => (await iter.next()).value;
void async function () {
// Write your code here
while(line = await readline()){
console.log(parseInt(line,16))
}
}()
16. HJ59 找出字符串中第一个只出现一次的字符
找出字符串中第一个只出现一次的字符
输入: asdfasdfo
输出: o
const rl = require("readline").createInterface({ input: process.stdin });
var iter = rl[Symbol.asyncIterator]();
const readline = async () => (await iter.next()).value;
void async function () {
// Write your code here
while(line = await readline()){
let lines = line.split('');
let newStr = [...new Set(lines)];
for(let i = 0 ;i<newStr.length;i++){
let tag = 0;
while(lines.indexOf(newStr[i])!==-1){
lines.splice(lines.indexOf(newStr[i]),1);
tag++;
}
if(tag===1){
console.log(newStr[i])
return;
}
}
}
console.log(-1)
}()
使用match会更妙一点
while ((line = await readline())) {
try {
let unfind = true;
for (const i of line) {
if (line.match(new RegExp(i, "g")).length === 1) {
unfind = false;
console.log(i);
break;
}
}
if (unfind) {
console.log(-1);
}
} catch (e) {}
}
在 JavaScript 中,可以使用 break 语句来跳出 for…of 循环。当 for…of 循环执行到 break 语句时,循环会立即停止,不再执行循环内剩余的代码。
注意,break 语句只会跳出当前循环,不会跳出外层循环。如果需要在循环内跳出所有循环(包括外层循环),可以使用 return 语句。
17.HJ81 字符串字符匹配
判断短字符串S中的所有字符是否在长字符串T中全部出现。
const rl = require("readline").createInterface({ input: process.stdin });
var iter = rl[Symbol.asyncIterator]();
const readline = async () => (await iter.next()).value;
void async function () {
let lines = [];
// Write your code here
while(line = await readline()){
lines.push(line);
}
// console.log(lines[1].indexOf(lines[0])!==-1)
let res= true
lines[0].split('').forEach(item=>{
if(lines[1].indexOf(item)===-1){
res = false
}
})
console.log(res)
}()
18 力扣简单 破冰游戏
var iceBreakingGame = function(num, target) {
if(num===1){
return 0
}
let x = iceBreakingGame(num-1,target);
return (target+x)%num;
};
19 力扣中等 无重复字符的最长子串
/**
* @param {string} s
* @return {number}
*/
const isC = (s)=>{
return [...new Set(s.split(''))].join('')===s;
}
var lengthOfLongestSubstring = function(s) {
if(s.length<1){
return 0;
}
if(isC(s)){
return s.length;
}
let max =1;
for(let i=0;i<s.length-1;i++){
for(j=i;j<s.length;j++){
let str = s.slice(i,j+1);
if(!isC(str)){
if(max<j-i){
max=j-i;
}
break;
}
if(j===s.length-1&&isC(str)){
if(max<j-i+1){
max=j-i+1;
}
}
}
}
return max;
};
emmm 做复杂了
20.最长公共前缀
编写一个函数来查找字符串数组中的最长公共前缀。
如果不存在公共前缀,返回空字符串 ""。
/**
* @param {string[]} strs
* @return {string}
*/
const compareTowStr = (short,long)=>{
let str=""
for(let i=0;i<short.length;i++){
if(short[i]===long[i]){
str+=short[i];
}else{
break
}
}
return str;
}
var longestCommonPrefix = function(strs) {
if(strs.length===1){
return strs[0]
}
let min = strs[0].length;
let minStr=strs[0];
strs.map(str=>{
if(str.length<min){
minStr = str;
}
})
let minIndex = minStr.length;
for(let i=0;i<strs.length;i++){
let str = compareTowStr(minStr,strs[i]);
console.log(str);
if(minIndex>=str.length){
minIndex = str.length;
// res = str;
}
}
let res = minStr.slice(0,minIndex);
return res
};
21 反转字符串中的单词
给你一个字符串 s ,请你反转字符串中 单词 的顺序。
单词 是由非空格字符组成的字符串。s 中使用至少一个空格将字符串中的 单词 分隔开。
返回 单词 顺序颠倒且 单词 之间用单个空格连接的结果字符串。
注意:输入字符串 s中可能会存在前导空格、尾随空格或者单词间的多个空格。返回的结果字符串中,单词间应当仅用单个空格分隔,且不包含任何额外的空格。
/**
* @param {string} s
* @return {string}
*/
var reverseWords = function(s) {
let l = s.match(/[a-z,A-Z,0-9]+/g)
let res = l.reverse().join(' ').trim()
return res
};
22. 字符串中的单词数
/**
* @param {string} s
* @return {number}
*/
var countSegments = function(s) {
return (s.match(/\S+/g)||[]).length;
};
23.581. 最短无序连续子数组
/**
* @param {number[]} nums
* @return {number}
*/
var findUnsortedSubarray = function(nums) {
let numss = nums.join(',').split(',');
let l = numss.sort((a,b)=>a-b);
console.log(l,nums)
let i_1 = 0;
let i_2 = 0;
for(let i =0;i<nums.length;i++){
if(l[i]!=nums[i]){
i_1 = i;
break;
}
}
for(let i =nums.length-1;i>0;i--){
if(l[i]!=nums[i]){
i_2 = i;
break;
}
}
if(i_1===i_2) return 0;
return i_2-i_1+1;
};
24.1071. 字符串的最大公因子
/**
* @param {string} str1
* @param {string} str2
* @return {string}
*/
var gcdOfStrings = function(str1, str2) {
let short = str1.length<=str2.length?str1:str2;
let long = str1.length>str2.length?str1:str2;
if(short.length<2){
return short;
}
let res =""
for(let i=short.length-1;i>=0;i--){
res=short.slice(0,i+1);
if(
short.split(res).join("").trim().length===0&&
long.split(res).join("").trim().length===0){
break;
}
}
if(short.split(res).join("").trim().length>0||long.split(res).join("").trim().length>0){
return ""
}
return res
};
25有效括号的嵌套深度
这题目描述的。。。
var maxDepthAfterSplit = function(seq) {
let max = [];
let stack = 0;
for (let i = 0; i < seq.length; i++) {
if (seq[i] === '(') {
stack++;
max.push(stack%2)
} else{
let ans = stack % 2;
stack--;
max.push(ans)
}
}
return max
}
26. 二分查找
/**
* @param {number[]} nums
* @param {number} target
* @return {number}
*/
var search = function(nums, target) {
let left = 0;let right = nums.length - 1;
let middle = Math.floor((left+right)/2);
while(left<=right&&nums[middle]!==target){
middle = Math.floor((left+right)/2)
if(nums[middle]<target){
left = middle+1;
}
else if (nums[middle]>target){
right = middle-1;
}
}
return nums[middle]===target?middle:-1
};
27 移除元素
/**
* @param {number[]} nums
* @param {number} val
* @return {number}
*/
var removeElement = function(nums, val) {
while(nums.indexOf(val)!==-1){
nums.splice(nums.indexOf(val),1)
}
return nums.length
};
28 根据字符出现频率排序
/**
* @param {string} s
* @return {string}
*/
var frequencySort = function(s) {
let list = s.split('');
let map = new Map();
list.map(item=>{
map.set(item,map.get(item)==0?1:((map.get(item)||0)+1));
})
return list.sort((a,b)=>{
if(a===b) return 0
else if(map.get(a)===map.get(b)) return a.localeCompare(b)
else return map.get(b)-map.get(a)
}).join("")
};
29 斐波那契数
动态规划
/**
* @param {number} n
* @return {number}
*/
// var fib = function(n) {
// if(n===0) return 0;
// if(n<3) return 1;
// return fib(n-1)+fib(n-2)
// };
var fib = function(n) {
if(n===0) return 0;
else if(n<=2) return 1;
let dp = new Array(n).fill(0);
dp[0] =1n;
dp[1] =1n;
for(let i = 2;i<n;i++){
dp[i] = (dp[i-1]+dp[i-2])% 1000000007n;
}
return dp[n-1]
};
30. 爬楼梯
这道题其实就是斐波那契,问题在于理解题意以及关键点 dp[i] = dp[i-1]+ dp[i-2]
,
意思就是你爬到第i阶的方法数,等于你爬到第i-1阶的方法数加上你爬到第i-2阶的方法数
/**
* @param {number} n
* @return {number}
*/
var climbStairs = function(n) {
//达到i阶的方案
if(n<3) {
return n;
}
let dp = new Array(n).fill(0);
dp[0] = 1;
dp[1] = 2;
for(let i = 2;i<n;i++){
dp[i] = dp[i-1]+dp[i-2];
}
return dp[n-1]
};
31 使用最小花费爬楼梯
/**
* @param {number[]} cost
* @return {number}
*/
var minCostClimbingStairs = function(cost) {
let len = cost.length;
if(len===0) return 0;
if(len===1) return cost[0];
if(len===2) return cost[0]<cost[1]?cost[0]:cost[1];
let dp = new Array(len+1);
dp[0] = 0;
dp[1] = 0;
for(let i=2;i<=len;i++){
dp[i] = Math.min(dp[i-1]+cost[i-1],dp[i-2]+cost[i-2]);
}
return dp[len]
};
32 不同路径
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。
问总共有多少条不同的路径?
/**
* @param {number} m
* @param {number} n
* @return {number}
*/
var uniquePaths = function(m, n) {
let dp = new Array(m).fill((new Array(n)).fill(0).slice());
for(let i = 0;i<m;++i) {
for(let j= 0;j<n;j++){
if(i===0||j===0){
dp[i][j] = 1;
}
else{
dp[i][j] = dp[i-1][j]+dp[i][j-1];
}
}
}
return dp[m-1][n-1]
};
这道题用数论的方法,固定的路径长度,选向右或者向下的箭头,会比较简单
var uniquePaths = function (m, n) {
let res = 1;
let g = Math.min(m, n)
for (let y = 1; y < g; y++) {
res = res * (m + n - g + y - 1) / y;
}
return res;
};
33.不同路径 II
/**
* @param {number[][]} obstacleGrid
* @return {number}
*/
var uniquePathsWithObstacles = function(obstacleGrid) {
if(obstacleGrid[0][0]===1)return 0;
let m = obstacleGrid.length;
let n = obstacleGrid[0].length;
let dp = new Array(m);
for(let i = 0;i<m;i++){
dp[i] = new Array(n).fill(0);
}
for(let i =0;i<n&&obstacleGrid[0][i]!==1;i++){
dp[0][i] =1;
}
for(let i =0;i<m&&obstacleGrid[i][0]!==1;i++){
dp[i][0] =1;
}
for(let i=1;i<m;i++){
for(let j=1;j<n;j++){
console.log(dp[i][j],i-1,j)
if(obstacleGrid[i][j]===1) dp[i][j]=0;
else dp[i][j] = dp[i-1][j]+dp[i][j-1];
}
}
return dp[m-1][n-1];
};
真题
1.宜居星球改造计划🌟
2XXX 年,人类通过对火星的大气进行宜居改造分析,使得火星已在理论上具备人类宜居的条件;
由于技术原因,无法一次性将火星大气全部改造,只能通过局部处理形式;
假设将火星待改造的区域为 r o w ∗ c o l u m n row * columnrow∗column 的网格,每个网格有 3 个值,宜居区、可改造区、死亡区,使用 YES、NO、NA 代替:
YES 表示该网格已经完成大气改造; NO 表示该网格未进行改造,后期可进行改造; NA 表示死亡区,不作为判断是否改造完成的宜居,无法穿过; 初始化下,该区域可能存在多个宜居区,并且每个宜居区能同时在每个太阳日单位向上下左右四个方向的相邻格子进行扩散,自动将 4 个方向相邻的真空区改造成宜居区;
请计算这个待改造区域的网格中,可改造区是否能全部变成宜居区,如果可以,则返回改造的太阳日天数,不可以则返回-1。
const readline = require("readline");
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
let rows, cols, gridCopy, coordinates;
rl.on("line", (line) => {
if (!rows) {
rows = parseInt(line);
coordinates = [];
} else if (!cols) {
cols = line.split(" ").length;
gridCopy = new Array(rows).fill().map(() => new Array(cols));
for (let i = 0; i < rows; i++) {
const row = line.split(" ");
for (let j = 0; j < cols; j++) {
gridCopy[i][j] = row[j];
}
if (i < rows - 1) {
rl.prompt();
}
}
const noNums = countNoOccurrences();
let flag = true;
let day = 0;
while (noNums !== 0 && flag) {
for (const coordinate of findYesCoords()) {
updateAdjElems(coordinate[0], coordinate[1]);
}
if (coordinates.length !== 0) {
updateGridCopy();
noNums -= coordinates.length;
coordinates = [];
day++;
} else {
flag = false;
}
}
printDayOrMinusOne(noNums, day);
rl.close();
}
});
function countNoOccurrences() {
let count = 0;
for (const row of gridCopy) {
count += row.filter((s) => s === "NO").length;
}
return count;
}
function updateAdjElems(i, j) {
const directions = [
[-1, 0],
[1, 0],
[0, -1],
[0, 1],
];
for (const dir of directions) {
const newRow = i + dir[0];
const newCol = j + dir[1];
if (
newRow >= 0 &&
newRow < rows &&
newCol >= 0 &&
newCol < cols &&
gridCopy[newRow][newCol] === "NO"
) {
gridCopy[newRow][newCol] = "YES";
coordinates.push([newRow, newCol]);
}
}
}
function findYesCoords() {
const yesCoords = [];
for (let i = 0; i < rows; i++) {
for (let j = 0; j < cols; j++) {
if (gridCopy[i][j] === "YES") {
yesCoords.push([i, j]);
}
}
}
return yesCoords;
}
function updateGridCopy() {
for (const coordinate of coordinates) {
gridCopy[coordinate[0]][coordinate[1]] = "YES";
}
}
function printDayOrMinusOne(noNums, day) {
console.log(noNums === 0 ? day : -1);
}
自己跟写一遍
const rl = require("readline").createInterface({ input: process.stdin });
var iter = rl[Symbol.asyncIterator]();
const readline = async () => (await iter.next()).value;
/**
*
YES NO NO NO
NO NO NO NO
NO NO NO NO
NO NO NO NO
6
*
*/
void async function () {
let rows ,cols,coordinates=[];
let gridCopy = [];
// Write your code here
while(line = await readline()){
gridCopy.push(line.split(' '));
}
rows = gridCopy.length;
cols = gridCopy[0].length;
//找寻数组中“NO”的数量
let noNums = countNoOccurences();
let flag = true;
let day = 0;
while(noNums!==0&&flag){
for(const coordinate of FindeYesCoords()){
//遍历YES的坐标数组
//每轮循环更新YES的上下左右
updateAdjElems(coordinate[0],coordinate[1])
}
if(coordinates.length!==0){
for(const coordinate of coordinates){
gridCopy[coordinate[0]][coordinate[1]] = "YES"
}
noNums -= coordinates.length;
coordinates = [];
day++;
}else{
flag = false
}
}
console.log(noNums===0?day:-1);
//返回二维数组中YES的坐标即[i,j] 下标的数组
function FindeYesCoords(){
const list = [];
for(let i = 0;i<rows;i++){
for(let j = 0;j<cols;j++){
if(gridCopy[i][j]==="YES"){
list.push([i,j])
}
}
}
return list;
}
//每轮循环更新YES的上下左右 ,gridCopy[i][j]
function updateAdjElems(i,j){
const direction = [
[-1,0],
[1,0],
[0,-1],
[0,1]
]
for(const dir of direction){
let newI = i+dir[0];
let newJ = j + dir[1];
if(
newI>=0&&
newJ>=0&
newI<rows&&
newJ<cols&&
gridCopy[newI][newJ]==="NO"){
gridCopy[newI][newJ] = "YES";
coordinates.push([newI,newJ])
}
}
}
//找寻数组中“NO”的数量
function countNoOccurences(){
let count =0;
gridCopy.forEach(item=>{
count+= item.filter(s=>s==="NO").length;
})
return count
}
}()
2.跳格子(2)🌟
小明和朋友玩跳格子游戏,有 n 个连续格子组成的圆圈,每个格子有不同的分数,小朋友可以选择从任意格子起跳,但是不能跳连续的格子,不能回头跳,也不能超过一圈;给定一个代表每个格子得分的非负整数数组,计算能够得到的最高分数。
输入 2 3 2 输出 4
输入 1 2 3 1 输出 4 1+3
动态规划
动态转移方程为
function maxSumNonAdjacent(nums) {
const n = nums.length;
if (n === 0) {
return 0;
} else if (n === 1) {
return nums[0];
} else if (n === 2) {
return Math.max(nums[0], nums[1]);
}
let prevMax = nums[0];
let currMax = Math.max(nums[0], nums[1]);
for (let i = 2; i < n; i++) {
const temp = currMax;
currMax = Math.max(currMax, prevMax + nums[i]);
prevMax = temp;
}
return currMax;
}
const readline = require("readline");
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
rl.question("", (input) => {
const strings = input.split(" ");
const nums = strings.slice(0, strings.length - 1).map(Number);
const res = maxSumNonAdjacent(nums);
console.log(res);
rl.close();
});
但感觉他的解法有问题,不能排除首尾相连的情况,这里把dp数组改成对象数组试试,给几个例子试试,感觉没错了
const rl = require("readline").createInterface({ input: process.stdin });
var iter = rl[Symbol.asyncIterator]();
const readline = async () => (await iter.next()).value;
void async function () {
while(line = await readline()){
let scores = line.split(" ").map(item=>parseInt(item));
const n = scores.length;
if(n===1){
console.log(scores[0]);
return;
}
let dp = new Array(n).fill().map(()=>{return {}});
dp[0].value = scores[0];
dp[0].hasOne = true;
dp[1].value = Math.max(scores[0],scores[1]);
if(scores[0]>=scores[1]){
dp[1].hasOne = true;
}else{
dp[1].hasOne = false;
}
for(let i=2;i<n;i++){
dp[i].value = Math.max(dp[i-2].value+scores[i],dp[i-1].value);
// console.log(dp[i].value,dp[i-1])
if(dp[i-2].value+scores[i]>=dp[i-1].value&&dp[i-2].hasOne){
dp[i].hasOne = true;
}else{
dp[i].hasOne = false;
}
}
console.log(dp[n-1].hasOne?dp[n-2].value:dp[n-1].value);
}
}()
3.跳房子(2)✨
跳房子,也叫跳飞机,是一种世界性的儿童游戏。
游戏参与者需要分多个回合按顺序跳到第 1 格直到房子的最后一格,然后获得一次选房子的机会,直到所有房子被选完,房子最多的人获胜。
跳房子的过程中,如果有踩线等违规行为会结束当前回合,甚至可能倒退几步。
假设房子的总格数是 count,小红每回合可能连续跳的步数都放在数组 steps 中,请问数组中是否有一种步数的组合,可以让小红三个回合跳到最后一格?如果有,请输出索引和最小的步数组合(数据保证索引和最小的步数组合是唯一的)。
注意:数组中的步数可以重复,但数组中的元素不能重复使用
- 输入描述 第一行输入为房子总格数 count,它是 int 整数类型。
第二行输入为每回合可能连续跳的步数,它是 int 整数数组类型。
- 输出描述 返回索引和最小的满足要求的步数组合(顺序保持 steps 中原有顺序)
let count = 9;
let minIndexSum = Number.MAX_VALUE;
let resList;
function dfs(nums, remaining, combination, indices, index) {
if (remaining === 0) {
let total = 0;
let indexSumTemp = 0;
for (let i = 0; i < 3; i++) {
total += combination[i]; // 计算组合的元素总和
indexSumTemp += indices[i]; // 计算索引总和
}
if (total === count && indexSumTemp < minIndexSum) { // 满足条件时更新结果
minIndexSum = indexSumTemp;
console.log(combination)
resList = combination.slice();
}
} else {
for (let i = index; i < nums.length; i++) {
combination[3 - remaining] = nums[i]; // 添加元素到组合
indices[3 - remaining] = i; // 添加索引
dfs(nums, remaining - 1, combination, indices, i + 1); // 递归调用
}
}
}
function printResult() {
let output = "[" + resList.join(",") + "]";
console.log(output);
}
let nums = [1, 5, 2, 0, 2, 4];
let combination = Array(3);
let indices = Array(3);
dfs(nums, 3, combination, indices, 0);
printResult();
Array.prototype.slice() 返回一个新的数组对象,占用一段新的内存地址
三数之和
var threeSum = function(nums) {
// [-1,0,1,2,-1,-4]
// [-4,-1,-1,0,1,2] <-- sorted.
nums.sort((a,b) => a-b);
let res = [];
for (let i=0; i<=nums.length-3; i++) {
if (i>0 && nums[i] === nums[i-1]) continue; // avoid the same situation.
let l = i+1, r = nums.length-1;
let tar = -nums[i];
while (l < r) {
let s = nums[l] + nums[r];
if (s < tar) l += 1;
else if (s > tar) r -= 1;
else {
if(!(l > i+1 && nums[l] === nums[l-1])) res.push([nums[i],nums[l],nums[r]]);
l += 1;
r -= 1;
}
}
}
return res;
};
写的时候这个sort方法搞了我一下,例如
let nums = [-1,0,1,2,-1,-4,-2,-3,3,0,4]
nums.sort()
猜猜结果是什么
nums === [-1, -1, -2, -3, -4, 0, 0, 1, 2, 3, 4]
你敢信
Array.prototype.sort()默认排序是将元素转换为字符串,然后按照它们的 UTF-16 码元值升序排序。
用dfs炸裂,超时了,下面这段
res = res.map(item=>item.sort((a,b)=>a-b));
res = [...new Set(res.map(item=>`${item},`))].map(item=>item.split(','))
res.map(item=>item.pop());
/**
* @param {number[]} nums
* @return {number[][]}
*/
var threeSum = function (nums) {
let res= []
const df = (remaining,index,combination,indexList)=>{
if(remaining===0){
let total = 0;
for(let i=0;i<3;i++){
total += combination[i];
}
if(indexList.length===[...new Set(indexList)].length)
if(total===0){
res.push(combination.slice());
}
}else{
for(let i = 0;i<nums.length;i++){
combination[3-remaining] = nums[i];
indexList[3-remaining] = i;
df(remaining-1,index+1,combination,indexList);
}
}
}
df(3,0,[0,0,0],[0,0,0]);
res = res.map(item=>item.sort((a,b)=>a-b));
res = [...new Set(res.map(item=>`${item},`))].map(item=>item.split(','))
res.map(item=>item.pop());
return res
};
真有意思
4.荒岛求生
有一个荒岛,只有左右两个港口,只有一座桥连接这两个港口,现在有一群人需要从两个港口逃生,有的人往右逃生,有的往左逃生,如果两个人相遇,则 pk,体力值大的能够打赢体力值小的,体力值相同则同归于尽,赢的人才能继续往前逃生,并减少相应的体力
输入描述 系列非 0 整数,用空格隔开,正数代表向右逃生,负数代表向左逃生
输出描述 最终能够逃生的人数
输入 5 10 8 -8 -5 输出 2
8 与-8 相遇,同归于尽,10 遇到-5,打赢并减少五点体力,最终逃生的为[5,5],均从右侧港口逃生,输出 2
function main() {
const readline = require("readline");
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
rl.question("", (input) => {
const numsString = input.split(" ");
const nums = numsString.map((numStr) => parseInt(numStr));
const left = [];
const right = [];
nums.forEach((num) => {
if (num > 0) {
right.push(num);
} else {
left.push(Math.abs(num));
}
while (left.length > 0 && right.length > 0) {
if (left[left.length - 1] > right[right.length - 1]) {
left.push(left.pop() - right.pop());
} else if (left[left.length - 1] < right[right.length - 1]) {
right.push(right.pop() - left.pop());
} else {
left.pop();
right.pop();
}
}
});
console.log(left.length + right.length);
rl.close();
});
}
main();
自己跟写一遍
const rl = require("readline").createInterface({ input: process.stdin });
var iter = rl[Symbol.asyncIterator]();
const readline = async () => (await iter.next()).value;
void (async function () {
while ((line = await readline())) {
let list = line.split(" ").map((item) => Number(item));
let left = [];
let right = [];
list.map((item) => {
if (item > 0) {
left.push(item);
} else {
right.push(Math.abs(item));
}
while (left.length > 0 && right.length > 0) {
let l = left[left.length - 1];
let r = right[right.length - 1];
if (l > r) {
left.push(l - r);
} else if (r > l) {
right.push(r - l);
} else {
left.pop();
right.pop();
}
}
});
console.log(left.length + right.length);
}
})();
5.二维伞的雨滴效应
为了模拟伞状雨滴效应,用二叉树来模拟二维平面伞, 现在输入一串正整数数组序列(不含0,数组成员至少是1个) , 若此数组序列是二叉搜索树的前序遍历的结果,那么请输出一个返回值1,否则输出0.
普通的伞在二维平面世界中,左右两侧均有一条边,而两侧伞边最下面各有一个伞坠子,雨滴落到伞面,逐步流到伞坠处,会将伞坠的信息携带并落到地面, 随着日积月累,地面会呈现伞坠的信息。 输出如下三个值,以空格分隔: 是否二叉排序树,左侧地面呈现的伞坠数字值,右侧地面呈现的伞坠数字值.
6.统计监控
在一长方形停车场内,每个车位上方都有对应监控器,当且仅当在当前车位或者前后左右四个方向任意一个车位范围停车时,监控器才需要打开,给出某一时刻停车场的停车分布,请统计最少需要打开多少个监控器。
输入
第一行输入 m mm,n nn 表示长宽,满足 1 < m , n < = 20 1<m,n<=201<m,n<=20;后面输入 m mm 行,每行有 n nn 个 0 00 或 1 11 的整数,整数间使用一个空格隔开,
表示该行已停车情况,其中 0 00 表示空位,1 11 表示已停。
输出
最少需要打开监控器的数量。
例如
输入
3 3
1 0 0
0 1 0
0 0 0
输出
6
let rowCount = 3,
colCount = 3; // 行数和列数
let grid = new Array(25);
const DIRECTIONS = [
[0, -1],
[0, 1],
[-1, 0],
[1, 0],
[0, 0],
]; // 方向数组,用于遍历相邻格子
for (let i = 0; i < grid.length; i++) {
grid[i] = new Array(25).fill(0);
}
const inputGrid = [
[1, 0, 0],
[0, 1, 0],
[0, 0, 0],
];
for (let row = 1; row <= rowCount; row++) {
// 读入地图
for (let col = 1; col <= colCount; col++) {
grid[row][col] = inputGrid[row - 1][col - 1];
}
}
let result = 0;
for (let row = 1; row <= rowCount; row++) {
// 遍历每个格子
for (let col = 1; col <= colCount; col++) {
if (grid[row][col] == 1) {
// 如果当前格子是车,直接计入结果
result++;
continue;
}
// 否则,遍历相邻的格子
if (
(row > 1 && grid[row - 1][col] == 1) || // 上方格子
(row < rowCount && grid[row + 1][col] == 1) || // 下方格子
(col > 1 && grid[row][col - 1] == 1) || // 左侧格子
(col < colCount && grid[row][col + 1] == 1) || // 右侧格子
grid[row][col] == 1
) {
// 当前格子本身是车
result++;
}
}
}
console.log(result);
自己反向思考一下
const rl = require("readline").createInterface({ input: process.stdin });
var iter = rl[Symbol.asyncIterator]();
const readline = async () => (await iter.next()).value;
void (async function () {
let grid = []
while ((line = await readline())) {
grid.push(line.split(' ').map(item=>parseInt(item)));
}
let list = grid.shift()
let m = (list[0]);
let n = (list[1]);
let redirections = [
[0,-1],
[0,1],
[1,0],
[-1,0],
]
for(let i = 0;i<m;i++){
for(let j = 0;j<n;j++){
if(!(grid[i][j]===true))
redirections.forEach(redirection=>{
if(i+redirection[0]<m&&i+redirection[0]>=0&&j+redirection[1]>=0&&j+redirection[1]<n){
let target = grid[i+redirection[0]][j+redirection[1]];
if(target===1){
grid[i][j]="hh";
}
}
})
}
}
let res =0
for(let i = 0;i<m;i++){
for(let j = 0;j<n;j++){
if(grid[i][j]!==0){
res+=1
}
}
}
console.log(res)
return res;
})();
7.整数编码
实现一个整数编码方法 使得待编码的数字越小 编码后所占用的字节数越小 编码规则如下
编码时7位一组,每个字节的低 7 位用于存储待编码数字的补码 字节的最高位表示后续是否还有字节,置1表示后面还有更多的字节,置0表示当前字节为最后一个字节 采用小端序编码,低位和低字节放在低地址上 编码结果按16进制数的字符格式进行输出,小写字母需要转化为大写字母
输入
1000
输出
E807
1000 的二进制表示为 0011 1110 1000 至少需要两个字节进行编码 第一个字节最高位是 1 剩余 7 位存储数字 1000 的低 7 位(1101000) 所以第一个字节的二进制位(1110 1000)即 E8 第二个字节最高位置 0 剩余的 7 位存储数字 1000 的第二个低 7 位(0000111) 所以第一个字节的二进制为(0000 0111)即 07 采用小端序编码 所以低字节 E8 输出在前面 高字节 07 输出在后面
const readline = require("readline");
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
rl.on("line", (input) => {
solveMethod(parseInt(input));
});
function solveMethod(num) {
const binary = num.toString(2);
const len = binary.length;
let builder = "";
for (let i = len; i > 0; i -= 7) {
const start = Math.max(i - 7, 0);
let bin = binary.substring(start, i);
if (bin.length < 7) {
let head = "";
for (let j = 0; j < 7 - bin.length; j++) {
head += "0";
}
bin = head + bin;
}
bin = i - 7 <= 0 ? "0" + bin : "1" + bin;
const hex = parseInt(bin, 2).toString(16).toUpperCase().padStart(2, "0");
builder += hex;
}
console.log(builder);
}
parseInt(string, radix) 解析一个字符串并返回指定基数的十进制整数,radix 是 2-36 之间的整数,表示被解析字符串的基数。
padStart() 方法用另一个字符串填充当前字符串(如果需要会重复填充),直到达到给定的长度。填充是从当前字符串的开头开始的。
跟
const rl = require("readline").createInterface({ input: process.stdin });
var iter = rl[Symbol.asyncIterator]();
const readline = async () => (await iter.next()).value;
void (async function () {
while ((line = await readline())) {
let num = parseInt(line);
num = num.toString(2);
let len = num.length;
let res = ""
for(let i = len;i>0;i-=7){
let start = Math.max(i-7,0);
let sbs = num.substring(start,i);
sbs = sbs.padStart(7-sbs.length,"0");
sbs = i-7>0?'1'+sbs:'0'+sbs;
res+=parseInt(sbs,2).toString(16).toUpperCase().padStart(2,"0")
}
console.log(res)
}
})();
8.五子棋迷
张兵和王武是五子棋迷,工作之余经常切磋棋艺。 走了一会儿,轮到张兵了,他对着一条线思考起来了,这条线上的棋子分布如下: 用数组表示: -1 0 1 1 1 0 1 0 1 -1
棋子分布说明:
-1 代表白子,0 代表空位,1 代表黑子; 数组长度 L, 满足 1 < L < 40, 且 L 为奇数; 请帮他写一个程序,算出最有利的出子位置。
最有利定义:
找到一个空位 (0),用棋子 (1/-1) 填充该位置,可以使得当前子的最大连续长度变大; 如果存在多个位置,返回最靠近中间的较小的那个坐标; 如果不存在可行位置,直接返回 -1; 连续长度不能超过 5 个(五子棋约束);
输入描述
第一行: 当前出子颜色 第二行: 当前的棋局状态
输出描述
1 个整数,表示出子位置的数组下标
1.输入
1
-1 0 1 1 1 0 1 0 1 -1 1
输出
5
2.输入
-1
-1 0 1 1 1 0 1 0 1 -1 1
输出
1
代码示例
function countLeftMatch(nums, target, index, maxVal) {
let left = index - 1;
let leftCount = 0;
while (left >= 0 && nums[left] === target && leftCount < maxVal - 1) {
leftCount++;
left--;
}
return leftCount;
}
function countRightMatch(nums, target, index, maxVal) {
let right = index + 1;
let rightCount = 0;
while (
right < nums.length &&
nums[right] === target &&
rightCount < maxVal - 1
) {
rightCount++;
right++;
}
return rightCount;
}
let target = 1;
let nums = "-1 0 1 1 1 0 1 0 1 -1 1".split(" ").map((x) => parseInt(x));
let maxVal = -Infinity;
let midDist = 0;
let result = -1;
for (let i = 0; i < nums.length; i++) {
if (nums[i] !== 0) {
continue;
}
let leftCount = countLeftMatch(nums, target, i, maxVal);
if (leftCount > 4) {
continue;
}
let rightCount = countRightMatch(nums, target, i, maxVal);
let totalCount = leftCount + rightCount;
let distToMid = Math.abs(i - Math.floor(nums.length / 2));
if (totalCount > maxVal || (totalCount === maxVal && distToMid < midDist)) {
maxVal = totalCount;
result = i;
midDist = distToMid;
}
}
console.log(result);
这个代码有点问题,更改输入为-1时,最后的输出不对
const rl = require("readline").createInterface({ input: process.stdin });
var iter = rl[Symbol.asyncIterator]();
const readline = async () => (await iter.next()).value;
void (async function () {
let list = [];
while ((line = await readline())) {
list.push(line);
}
let target = Number(list[0]);
let nums = list[1].split(" ").map((x) => parseInt(x));
function countLeftMatch(nums, target, index, maxVal) {
let left = index - 1;
let leftCount = 0;
while (left >= 0 && nums[left] === target ) {
leftCount++;
left--;
}
return leftCount;
}
function countRightMatch(nums, target, index, maxVal) {
let right = index + 1;
let rightCount = 0;
while (
right < nums.length &&
nums[right] === target
) {
rightCount++;
right++;
}
return rightCount;
}
let maxVal = -Infinity;
let midDist = 0;
let result = -1;
for (let i = 0; i < nums.length; i++) {
if (nums[i] !== 0) {
continue;
}
let leftCount = countLeftMatch(nums, target, i, maxVal);
if (leftCount > 4) {
continue;
}
let rightCount = countRightMatch(nums, target, i, maxVal);
let totalCount = leftCount + rightCount;
let distToMid = Math.abs(i - Math.floor(nums.length / 2));
if (
totalCount > maxVal ||
(totalCount === maxVal && distToMid < midDist)
) {
maxVal = totalCount;
result = i;
midDist = distToMid;
}
}
console.log(result);
})();
改掉两个函数里面的约束条件后,看似正确了
(2023-10-18)todo:跟
跟
const rl = require("readline").createInterface({ input: process.stdin });
var iter = rl[Symbol.asyncIterator]();
const readline = async () => (await iter.next()).value;
void (async function () {
let list = [];
while ((line = await readline())) {
list.push(line);
}
let target = Number(list[0]);
let nums = list[1].split(" ").map((x) => parseInt(x));
let inde=-1;
let maxVal =0;
let midDist =nums.length ;
const readLeft = (index)=>{
let i = index-1;
let res =0;
while(nums[i]===target&&i>=0){
res++;
i--
}
return res
}
const readRight = (index)=>{
let i = index+1;
let res =0;
while(nums[i]===target&&i<nums.length){
res++;
i++;
}
return res
}
for(let i = 0 ;i<nums.length;i++){
if(nums[i]!==0) continue;
let left = readLeft(i);
if(left>4) continue;
let right = readRight(i);
if(right>4) continue;
let sum = left+right;
if(sum>5) continue;
if(sum>maxVal&& Math.abs(i-nums.length/2)<midDist){
maxVal = sum;
inde=i;
midDist = Math.abs(i-nums.length/2);
}
}
console.log(inde)
})();
9.选修课
题目描述
现有两门选修课,每门选修课都有一部分学生选修,每个学生都有选修课的成绩,需要你找出同时选修了两门选修课的学生, 先按照班级进行划分,班级编号小的先输出,每个班级按照两门选修课成绩和的降序排序,成绩相同时按照学生的学号升序排序。
输入
第一行为第一门选修课学生的成绩,第二行为第二门选修课学生的成绩, 每行数据中学生之间以英文分号分隔,每个学生的学号和成绩以英文逗号分隔,学生学号的格式为 8 位数字( 2 位院系编号+入学年份后 2 位+院系内部 1 位专业编号+所在班级 3 位学号), 学生成绩的取值范围为 [0,100] 之间的整数,两门选修课选修学生数的取值范围为 [1-2000] 之间的整数。
输出
同时选修了两门选修课的学生的学号,如果没有同时选修两门选修课的学生输出 NULL, 否则,先按照班级划分,班级编号小的先输出,每个班级先输出班级编号(学号前五位), 然后另起一行输出这个班级同时选修两门选修课的学生学号,学号按照要求排序(按照两门选修课成绩和的降序,成绩和相同时按照学号升序), 学生之间以英文分号分隔。
举例
输入
01202021,75;01201033,95;01202008,80;01203006,90;01203088,100
01202008,70;01203088,85;01202111,80;01202021,75;01201100,88
输出
01202
01202008;01202021
01203
01203088
class Student {
constructor(id, score) {
this.id = id;
this.score = score;
}
}
const one ="01202021,75;01201033,95;01202008,80;01203006,90;01203088,100".split(";");
const two = "01202008,70;01203088,85;01202111,80;01202021,75;01201100,88".split(";");
const tIds = new Map();
for (const t of two) {
const tStu = t.split(",");
const tId = tStu[0];
const tScore = parseInt(tStu[1]);
tIds.set(tId, tScore);
}
const stuComparator = (a, b) => {
if (a.score !== b.score) {
return b.score - a.score;
} else {
return a.id.localeCompare(b.id);
}
};
const map = new Map();
for (const s of one) {
const sStu = s.split(",");
const sId = sStu[0];
if (tIds.has(sId)) {
const sScore = parseInt(sStu[1]);
const tScore = tIds.get(sId);
const totalScore = sScore + tScore;
const cls = sId.substring(0, 5);
const student = new Student(sId, totalScore);
if (!map.has(cls)) {
map.set(cls, new Array());
}
map.get(cls).push(student);
}
}
if (map.size === 0) {
console.log("NULL");
} else {
const sortedMap = new Map([...map.entries()].sort());
for (const [key, value] of sortedMap) {
console.log(key);
const sortedValue = value.sort(stuComparator);
const res = sortedValue.map((stu) => stu.id).join(";");
console.log(res);
}
}
说实话,这种题我做不来,感觉就是数据结构的东西,什么类,Map之类的,确实比较薄弱,当然 我好像其他地方也很薄弱
10.找目标字符串
题目
给定一个字符串 和一个二维字符数组 如果该字符串存在于该数组当中 则按照字符串的字符顺序输出字符串每个字符所在单元格的位置下标字符串 如果找不到返回字符串”N”
需要按照字符串的字符组成顺序搜索, 且搜索到的位置必须是相邻单元格 其中”相邻单元格”是指那些水平相邻或垂直相邻的单元格 同一个单元格内的字母不允许被重复使用 假定在数组中最多只存在一个可能的匹配
输入
第一行为一个数字(N) 指示二维数组在后续输入所占的行数 第二行到第N+1行输出为二维大写字符串数组 每行字符用半角逗号分割 第N+2行为待查找的字符串 由大写字符组成 字符数组的大小为N*N, 0 < N <= 100 单词长度为K, 0 < K < 1000
输出
输出一个位置下标字符串,拼接格式为:
第一个字符行下标+”,“+第一个字符列下标+”,“+第二个字符行下标+”,“+第二个字符列下标…+”,“+第N个字符行下标+”,“+第N个字符列下标
示例输入
4
A,C,C,F
C,D,E,D
D,E,S,S
F,E,C,A
ACCESS
示例输出
0,0,0,1,0,2,1,2,2,2,2,3
示例代码
const readline = require("readline");
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
let res = [];
rl.on("line", (input) => {
let N = parseInt(input);
let matrix = [];
for (let i = 0; i < N; ++i) {
matrix[i] = rl.readLine().replaceAll(",", "").split("");
}
let word = rl.readLine();
solveMethod(matrix, word);
});
function solveMethod(matrix, word) {
if (exist(matrix, word)) {
for (let i = res.length - 1; i >= 0; --i) {
process.stdout.write(res[i]);
if (i !== 0) {
process.stdout.write(",");
}
}
} else {
console.log("N");
}
}
function exist(board, word) {
let h = board.length,
w = board[0].length;
let visited = Array(h)
.fill()
.map(() => Array(w).fill(false));
for (let i = 0; i < h; ++i) {
for (let j = 0; j < w; j++) {
let flag = check(board, visited, i, j, word, 0);
if (flag) {
res.push(i + "," + j);
return flag;
}
}
}
return false;
}
function check(board, visited, i, j, s, k) {
if (board[i][j] !== s.charAt(k)) {
return false;
} else if (k === s.length - 1) {
return true;
}
visited[i][j] = true;
let directions = [
[0, 1],
[0, -1],
[1, 0],
[-1, 0],
];
let result = false;
for (let dir of directions) {
let newi = i + dir[0],
newj = j + dir[1];
if (
newi >= 0 &&
newi < board.length &&
newj >= 0 &&
newj < board[0].length
) {
if (!visited[newi][newj]) {
let flag = check(board, visited, newi, newj, s, k + 1);
if (flag) {
res.push(newi + "," + newj);
result = true;
break;
}
}
}
}
visited[i][j] = false;
return result;
}
牛客输入模版
const rl = require("readline").createInterface({ input: process.stdin });
var iter = rl[Symbol.asyncIterator]();
const readline = async () => (await iter.next()).value;
void async function () {
while(line = await readline()){
}
}()
必会知识点
贪心算法
计算一个数组中两个数的最大差值
function greedyMaxDifference(arr) {
let max = arr[0];
let min = arr[0];
let difference = 0;
for (let i = 1; i < arr.length; i++) {
if (arr[i] > max) {
max = arr[i];
difference = max - min;
} else if (arr[i] < min) {
min = arr[i];
difference = max - min;
}
}
return difference;
}
const arr = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5];
console.log(greedyMaxDifference(arr)); // 输出:8
滑动窗口
滑动窗口算法是一种在处理字符串、数组等数据时,能够高效地获取连续的子集或片段的方法。它通过维护一个窗口,窗口在每次迭代中向右滑动,同时更新窗口中的数据。滑动窗口算法的应用范围非常广泛,例如在字符串匹配、文本处理、数据分析和过滤等场景中。
下面是一个简单的滑动窗口算法实现:
function slidingWindow(arr, windowSize) {
let result = [];
let left = 0;
let right = 0;
while (right < arr.length) {
// 扩大窗口
while (right - left < windowSize) {
right++;
}
// 更新窗口中的数据
// ...
// 缩小窗口
while (right - left >= windowSize) {
result.push(arr.slice(left, right));
left++;
}
}
return result;
}
进制转换
在JavaScript中,我们可以使用toString()方法将数字转换为指定进制的字符串表示形式。以下是进制转换的示例:
function convertToBase(num, base) {
const digits = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
if (num === 0) return '0';
let result = '';
while (num > 0) {
result = digits[num % base] + result;
num = Math.floor(num / base);
}
return result;
}
console.log(convertToBase(10, 2)); // 输出:1010
console.log(convertToBase(10, 8)); // 输出:12
console.log(convertToBase(10, 16)); // 输出:A
位元算
位运算是一种在计算机科学中用于操作二进制数的数学运算,它可以用于实现各种算法,例如位操作符、位移操作符、按位与、按位或等。
在JavaScript中,我们可以使用位运算符对二进制数进行操作。以下是位运算的一些示例:
1.按位与(&):按位与操作符&用于将两个二进制数的对应位进行逻辑与操作。只有当两个位都为1时,结果的对应位才为1,否则为0。
console.log(5 & 3); // 输出:3
console.log(6 & 3); // 输出:3
console.log(7 & 3); // 输出:1
在这个示例中,5的二进制表示为101,3的二进制表示为011。按位与操作后,只有第五位(从右到左)的两个位都为1时,结果的第五位才为1,否则为0。
2.按位或(|):按位或操作符|用于将两个二进制数的对应位进行逻辑或操作。只要有一个位为1,结果的对应位就为1,否则为0。
console.log(5 | 3); // 输出:7
console.log(6 | 3); // 输出:7
console.log(7 | 3); // 输出:7
在这个示例中,5的二进制表示为101,3的二进制表示为011。按位或操作后,只有第五位(从右到左)的两个位都为0时,结果的第五位才为0,否则为1。
- 按位异或(^):按位异或操作符^用于将两个二进制数的对应位进行逻辑异或操作。当两个位相同时,结果的对应位为0,否则为1。
console.log(5 ^ 3); // 输出:6
console.log(6 ^ 3); // 输出:6
console.log(7 ^ 3); // 输出:7
在这个示例中,5的二进制表示为101,3的二进制表示为011。按位异或操作后,只有第五位(从右到左)的两个位相同,结果的第五位才为0,否则为1。
4.按位非():按位非操作符“用于将一个二进制数的对应位进行逻辑非操作。只有当对应位为1时,结果的对应位才为0,否则为1。
console.log(~5); // 输出:-6
console.log(~6); // 输出:-7
console.log(~7); // 输出:-8
在这个示例中,5的二进制表示为101,按位非操作后,只有第五位(从右到左)的位为1,其他位都为0,所以结果为-6。
5.左移(<<
):左移操作符<<
用于将一个二进制数向左移动指定的位数。移动后的位数将被填充为0。
console.log(5 << 1); // 输出:10
console.log(6 << 1); // 输出:12
console.log(7 << 1); // 输出:14
6.右移(>>):右移操作符>>用于将一个二进制数向右移动指定的位数。移动后的位数将被填充为0。
console.log(5 >> 1); // 输出:2
console.log(6 >> 1); // 输出:3
console.log(7 >> 1); // 输出:4
在这个示例中,5的二进制表示为101,向右移动1位后,第五位(从右到左)的位被填充为0,其他位保持不变,所以结果为2。
队列
JavaScript中的队列是一种数据结构,用于存储一组元素,并允许在队首添加元素,在队尾删除元素。队列是一种先进先出(First In First Out, FIFO)的数据结构,与栈相反。
在JavaScript中,我们可以使用数组来模拟队列的操作。以下是使用数组实现一个简单的队列数据结构的示例:
class Queue {
constructor() {
this.items = [];
}
// 入队
enqueue(element) {
this.items.push(element);
}
// 出队
dequeue() {
if (this.isEmpty()) {
return '队列已空,无法出队';
}
return this.items.shift();
}
// 获取队列顶端元素
top() {
if (this.isEmpty()) {
return '队列已空,无法获取队列顶端元素';
}
return this.items[0];
}
// 判断队列是否为空
isEmpty() {
return this.items.length === 0;
}
// 获取队列大小
size() {
return this.items.length;
}
}
使用这个队列数据结构,我们可以轻松地执行入队、出队、获取队首元素、判断队列是否为空以及获取队列大小等操作。例如:
const queue = new Queue();
queue.enqueue(1);
queue.enqueue(2);
queue.enqueue(3);
console.log(queue.top()); // 输出:1
console.log(queue.dequeue()); // 输出:1
console.log(queue.top()); // 输出:2
console.log(queue.size()); // 输出:2
console.log(queue.isEmpty()); // 输出:false
二叉树前中后序遍历
前序遍历
二叉树的前序遍历是指先访问根节点,然后依次遍历左子树和右子树。前序遍历的顺序是根-左-右。下面是一个二叉树前序遍历的递归和非递归实现:
递归
function preorderTraversalRecursive(root) {
if (root === null) {
return [];
}
const result = [root.val];
result.push(...preorderTraversalRecursive(root.left));
result.push(...preorderTraversalRecursive(root.right));
return result;
}
非递归
function preorderTraversal(root) {
const result = [];
const stack = [];
let current = root;
while (current !== null || stack.length > 0) {
while (current !== null) {
result.push(current.val);
stack.push(current);
current = current.left;
}
current = stack.pop();
current = current.right;
}
return result;
}
判断某序列是否是二叉树的前序遍历,可以通过遍历该序列,比较其与二叉树前序遍历的规律是否一致来判断 递归
function isValidPreorder(arr) {
if (arr.length === 0) {
return true;
}
const root = arr[0];
let i = 1;
while (i < arr.length && arr[i] < root) {
i++;
}
if (i === arr.length) {
return false;
}
const left = arr.slice(1, i);
const right = arr.slice(i);
return isValidPreorder(left) && isValidPreorder(right);
}
非递归
function isValidPreorder(arr) {
if (arr.length === 0) {
return true;
}
const root = arr[0];
let i = 1;
let stack = [];
while (i < arr.length || stack.length > 0) {
while (i < arr.length && arr[i] < root) {
stack.push(arr[i]);
i++;
}
if (i === arr.length || arr[i] !== root) {
return false;
}
while (stack.length > 0 && stack[stack.length - 1] < root) {
arr[i] = stack.pop();
}
i++;
}
return true;
}function isValidPreorder(arr) {
if (arr.length === 0) {
return true;
}
const root = arr[0];
let i = 1;
let stack = [];
while (i < arr.length || stack.length > 0) {
while (i < arr.length && arr[i] < root) {
stack.push(arr[i]);
i++;
}
if (i === arr.length || arr[i] !== root) {
return false;
}
while (stack.length > 0 && stack[stack.length - 1] < root) {
arr[i] = stack.pop();
}
i++;
}
return true;
}
中序遍历
栈的迭代
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @return {number[]}
*/
var inorderTraversal = function(root) {
let stack = [];
let res = []
while(root||stack.length>0){
while(root){
stack.push(root);
root = root.left;
}
root = stack.pop();
res.push(root.val);
root = root.right;
}
return res
};
以下是这个函数的详细解释:
1.初始化一个空栈 stack 和一个空数组 res。
2.进入一个 while 循环,当根节点 root 不为空或者栈 stack 不为空时,循环继续。
3.在 while 循环中,将根节点的左子节点压入栈 stack 中,然后将根节点移动到它的左子节点。重复此步骤,直到根节点为空。
4.当根节点为空时,从栈 stack 中弹出一个节点,将其值添加到结果数组 res 中。然后将根节点移动到它的右子节点。
5.重复步骤 3 和 4,直到栈 stack 为空且根节点为空。
6.最后返回结果数组 res。
这个函数的优点是简单易懂,代码实现简单。但是,它的时间复杂度是 O(n),其中 n 是二叉树的节点数,因为每个节点都需要入栈和出栈一次
回溯
递归三部曲
1.确认递归函数的参数和返回值 --- backtrace(n,k,startindex) 2.确定递归的终止条件 --- paths.length === k 3.确认单层的递归逻辑
for (let i = startIndx;i<n;i++){
paths.push(i);
backtrace(n,k,i+1);
paths.pop();
}
组合问题
给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。
你可以按 任何顺序 返回答案。
输入:n = 4, k = 2
输出:
[
[2,4],
[3,4],
[2,3],
[1,2],
[1,3],
[1,4],
]
var combine = function (n, k) {
let res = []
let path = [];
const backtrace = (n, k, startIndex) => {
if (path.length === k) {
res.push(path.slice())
return;
}
for (let i = startIndex; i < n; i++) {
path.push(i+1);
backtrace(n,k,i+1);
path.pop();
}
}
backtrace(n,k,0)
return res
};
注意
第6行中需要推送的结果是path.slice()
第10行中需要推送的数为i+1,下标从0开始的话,每位下标都需要+1