提前准备
- 仔细阅读邀请邮件
- 准备良好的网络环境,一部手机扫描,一个带摄像头的电脑
- 笔试在牛客网进行,提前了解牛客网的标准输入输出(只刷过力扣的我第一次考试像傻子一样,居然还有多行输入*&¥%#)https://www.nowcoder.com/discuss/276
- 认清实力,好好刷题。。
考题分析
考题 —> https://www.nowcoder.com/discuss/666736?type=post&order=time&pos=&page=1&channel=-1&source_id=search_post_nctrack
(本菜一题都没做出来)
最长全字母子串
本题是力扣第三题改编,官方解法是滑动窗口(左右指针移动)
法一:这里给出祖传暴力法。。
let str = "abbbaaccb"; //正常输入需要node的readline()
let set = new Set(); //准备去重容器,存放出现过的所有字母
let ans = str; //准备一个答案的容器
let num = 0; //准备一个存放答案起始位置的容器
for(let i=0; i<str.length; i++){
let res = ""; //每次都重新用空串拼接
str.split("").map(v=>{set.add(v)}); //每次进入都将set恢复为所有字母,也可以放在外面,每次重新赋值,自行优化
for(let j=i; j<str.length; j++){ //外层循环对比,内层循环得到不同的全字母字串
if(set.size !== 0){ //若字母全部匹配,则不执行
res += str[j]; //拼串
set.delete(str[j]); //管它存不存在,删就完了
}else break
}
if(res.length < ans.length && set.size === 0){ //记得判断set是否全部匹配
ans = res; //结果存起来,别让局部变量跑了
num = i;
}
}
console.log(num+","+ans.length);
法二:更新一下滑动窗口解法:
// 以下为滑动窗口
let str = "abbbaaccb"; //正常输入需要node的readline()
let set = new Set();
str.split("").map(v=>{set.add(v)}); //存入所有不同字符
let arr = Array.from(set), newarr = [];
arr.sort(); //排序比较两个set;也可以每次删除再判断是否清空
let newset = new Set();
let len = arr.length; //初始化len为滑动窗口的长度
let strchild = ""; //子串初始化
while (len <= str.length){
for(let i=0; i<str.length-len; i++){ //i为滑动窗口的起始位置
strchild = str.slice(i,i+len); //slice取子串
strchild.split("").map(v=>{newset.add(v)}); //split变数组,map遍历数组,add进newset
newarr = Array.from(newset); //让Set对象变成数组,再排序
newarr.sort();
if(newarr.toString() === arr.toString()){ //数组比较要变成字符串
console.log(i,len,str.slice(i,i+len));
len = str.length+1; //结束循环
break;
}
newset.clear(); //不匹配则要清空,注意!!
}
len++; //滑动窗口长度加一
}
总结:滑动窗口步骤:定义子串长度len,while判断子串是否达到边界(整串长度),内层定义i为滑动窗口的起始位置,循环每一个长度为len的子串,if判断,达到条件则跳出。
象棋之单马将军可达路径
没有找到力扣类似题,但是此题可以用回溯(搜索解决问题的所有解)
- 回溯对比dfs:
1. 回溯法采用试错的思想,它尝试分步的去解决一个问题。当它发现分步的答案不能得到有效的解决时就会退回上一步甚至上几步。回溯法通常用递归的方法来解决。
2. 深度遍历是树的一种遍历方法,它强调的是一头扎到底然后再往回走的途中去遍历其它结点,这个过程会一直持续到初始的根结点。 - 回溯对比动态规划
共同点:
1.一个问题都能够分为很多步骤来解决;
2.每一个步骤包含了很多种选择;
不同点:
1.动态规划强调的是问题的最优解,它只需要知道最终的结果;
2.回溯可以搜索到问题成立的所有解,是一种遍历算法,时间复杂度更高;
废话不多说,直接上代码
// 别马腿还没实现。。
function top1(cur){ //向上两步,向右一步函数
// if(cur[1]+1 === ma[1] && cur[0] === ma[0]){ //别马腿
// res.pop(); //出栈
// return res[res.length-1] //结束当前
// }
newcur = JSON.parse(JSON.stringify(cur)); //解决深拷贝
newcur[0] +=1;
newcur[1] +=2;
res.push(newcur); //将新的当前状态入栈
}
function right(cur){ //向上一步,向右两步函数
// if(cur[0]+1 === ma[0] || cur[1] === ma[1]){ //别马腿
// res.pop(); //出栈
// return res[res.length-1] //结束当前
// }
newcur = JSON.parse(JSON.stringify(cur));
newcur[0] +=2;
newcur[1] +=1;
res.push(newcur);
}
// 此处输入为示例,考试时要readline()读标准输入流
let cur = [0,0], ma = [2,2], jiang = [6,3], newcur = [0,0]; //数组来存坐标
let count = 0; //可达路径数
let res = []; //用一个栈来存状态
res.push(cur); //初始化
function dfs(cur) {
if(cur[0] === jiang[0] && cur[1] === jiang[1]){ //将军
count++; //记一种可达路径
res.pop(); //出栈
return res[res.length-1] //结束当前
}
if(cur[0] > jiang[0] || cur[1] > jiang[1]){ //马跳出边界
res.pop(); //出栈
return res[res.length-1] //结束当前
}
//不能踩在马上,且不能被马吃掉
if(cur[0] === ma[0]&&cur[1] === ma[1] || cur[0] === ma[0]-1&&cur[1] === ma[1]-2 || cur[0] === ma[0]+1&&cur[1] === ma[1]-2 || cur[0] === ma[0]-2&&cur[1] === ma[1]-1 || cur[0] === ma[0]+2&&cur[1] === ma[1]-1 || cur[0] === ma[0]-2&&cur[1] === ma[1]+1 || cur[0] === ma[0]+2&&cur[1] === ma[1]+1 || cur[0] === ma[0]-1&&cur[1] === ma[1]+2 || cur[0] === ma[0]+1&&cur[1] === ma[1]+2){
res.pop();
return res[res.length-1]
}
top1(res[res.length-1]); //向上两步,向右一步。压栈压到底
dfs(res[res.length-1]);
right(res[res.length-1]); //向上一步,向右两步
dfs(res[res.length-1]);
res.pop(); //关键!如果左右子节点都不通,要回,不然会死循环
}
dfs(cur);
console.log(count);
先画递归树图。如本题如下:
回溯法固定格式:递归函数里,先写判断,判断中需要return来结束,再写左子节点,嵌套递归,再写右子节点嵌套递归,然后终止。可以用栈来记录状态(或当前value),push入栈,pop出栈。
递归的好处是,不用考虑递归树有几层,如果暴力 有几层就要几层循环。。
而回溯的好处是,可以得出所有可行解。
最大幸福值
首先要会用node读多行数据,该题还没有思路,有思路的大佬请留言
20210603更新一下baseline吧,无算法。。
const fs = require('fs');
let fd = fs.createReadStream('./test3_data.txt'); //创建文件读取流,在流中数据是如何存储的??
const readline = require('readline');
const rl = readline.createInterface({ //创建输入输出接口
// input: process.stdin,
input: fd, //将数据流作为输入
// output: process.stdout
});
let k = -1; //先给行数置-1,表示还没开始读取
let inputs = [], result = 0; //初始化
rl.on('line', function(data) { //监听事件line,一行一行地作为data参数传入
// 获取输入
inputs.push(data); //数据存入数组
if(k === -1) {
k = parseInt(data.trim()); //读取第一行,以此得到接下来输入的行数
} else {
if(k*2 === inputs.length) { //因为是回调函数,所以判断最后一次的data输入,才能查看结果
// 处理
deal(inputs);
// 输出结果
// console.log(result);
// inputs.length = 0; k=-1; // 清空数组,可写
}
}
});
function deal(inputs) {
/**
* N个员工,N-1个限制条件,D、E、F三种选择
* - 目前见到的解决方案有 并查集、DFS
* -https://www.nowcoder.com/discuss/666692?channel=-1&source_id=discuss_terminal_discuss_sim_nctrack&ncTraceId=5a0fbdec61d043a3a8d3f9274454dd00.321.16226525713963264
* -https://www.nowcoder.com/discuss/666694
* - 并查集 是将所有同个直系限制归为一个root
* - DFS 从最大 boss 0 开始搜索, 每次搜到 cur,不能和 lastColor 同色,遍历所有的下属,递归搜索每个下属的最大染色值,作和即可
* - aaa头疼-_- 让第i个员工穿D,那在判断它和下属重的时候如何返回再改变他穿E呢。这是把所有的情况都搜索一遍然后再比出最大吗
*/
//------------这里输入自己的代码
console.log(inputs); //[ '3', '2 4 9', '1 3 5', '1 2 3', '0 1', '0 2' ]
let cur = []; //用来存每个员工选的型号D/E/F用1/2/3表示,索引为第几个员工
let i = 1;
function dfs(num) {
if(i === k+1){ //遍历完所有的员工
return;
}
cur.push(Number(num[0]));
if(cur[inputs[i+k][0]] === cur[inputs[i+k][2]]){ // 判断是否与直系一样
return
}
i += 1;
dfs(inputs[i])
}
dfs(inputs[i]);
// console.log(cur);
//------------
return result
}
经验总结
这次考试中连api都没记熟,没有代码提示,没有debug,还是挺痛苦的。用开发者工具不知道违不违规。
刷题不能停啊,常用算法都得过一遍,什么dfs,双指针,滑动窗口,动态规划,回溯,贪心,递归,分治,二分,并查集等等