先来个段子提提神~
算法好难?哪里难了!这么多年一直都是这个难度,有些时候得找找自己原因,有没有认真刷题,刷题数量有没有涨,好吧,我简直都快疯掉了!
所以!我反省一下,我面试前做了多少算法题。
根据上面的图可以看到,也就八九十个算法题吧,然后屁颠屁颠的就跑去面试了,果不其然,面试凉了,内心更是凉透了。考的不会,会的不考,哎~。
话不多说,上才艺!
整体的面试流程是这样的,参加了两轮面试,每轮面试出了两道算法题,共四道:
题目1解法:用proxy实现一个数据劫持。
const obj = new Proxy({}, {
get: function (target, propKey, receiver) {
console.log(`getting ${propKey}!`);
return Reflect.get(target, propKey, receiver);
},
set: function (target, propKey, value, receiver) {
console.log(`setting ${propKey}!`);
return Reflect.set(target, propKey, value, receiver);
}
});
题目2解法:最大子数组和。给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。子数组 是数组中的一个连续部分。
// 动态规划
const maxSubArray = function(nums) {
let pre = 0, maxAns = nums[0];
nums.forEach((x) => {
pre = Math.max(pre + x, x);
maxAns = Math.max(maxAns, pre);
});
return maxAns;
};
// 暴力解法
const maxSubArray = function(nums) {
let max = Math.min(...nums);
for (let i = 0; i < nums.length; i++) {
let total = 0;
for (let j = i; j < nums.length; j++) {
total += nums[j];
max = Math.max(total, max)
}
}
return max;
};
题目3解法:LRU 缓存
class LRUCache {
constructor(length) {
if (length < 1) throw new Error("长度不能小于1");
this.length = length;
this.data = new Map(); // 数据map
}
set(key, value) {
const data = this.data;
// 如果存在该对象,则直接删除
if (data.has(key)) {
data.delete(key);
}
// 将数据对象添加到map中
data.set(key, value);
if (data.size > this.length) {
// 如果map长度超过最大值,则取出map中的第一个元素,然后删除
const delKey = data.keys().next().value;
data.delete(delKey);
}
}
get(key) {
const data = this.data;
// 数据map中没有key对应的数据,则返回null
if (!data.has(key)) return null;
const value = data.get(key);
// 返回数据前,先删除原数据,然后在添加,就可以保持在最新
data.delete(key);
data.set(key, value);
return value;
}
}
const lruCache = new LRUCache(2);
lruCache.set('1', 1); // Map(1) {1 => 1}
lruCache.set('2',2); // Map(2) {1 => 1, 2 => 2}
console.log(lruCache.get('1')); // Map(2) {2 => 2, 1 => 1}
lruCache.set('3',3); // Map(2) {1 => 1, 3 => 3}
console.log(lruCache.get('2')); // null
lruCache.set('4',4); // Map(2) {3 => 3, 4 => 4}
console.log(lruCache.get('1')); // null
console.log(lruCache.get('3')); // Map(2) {4 => 4, 3 => 3}
console.log(lruCache.get('4')); // Map(2) {3 => 3, 4 => 4}
LRU(Least recently used,最近最少使用)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。
那么该数据结构就是当存储队列到达上限时,清除的是最久未被访问的节点,该节点一般认为是最可能无用的节点,保留下来的是最近都有使用过的节点,因此可以实现对"有用"数据的最大程度保留。
Vue中在 keep-alive 组件中使用了 LRU 算法:官方文档。
题目4解法:实现撤销和重做(undo 和 redo)功能。
class Command {
constructor(executor) {
this.executor = executor;
}
execute() {
this.executor();
}
}
class AddCommand extends Command {
constructor(undoCommand, value) {
super(() => {
console.log(`Add: ${value}`);
undoCommand.savedValue = value;
});
this.undoCommand = undoCommand;
}
undo() {
console.log(`Undo Add: ${this.undoCommand.savedValue}`);
this.undoCommand.savedValue = null;
}
}
class DeleteCommand extends Command {
constructor(undoCommand, value) {
super(() => {
console.log(`Delete: ${value}`);
undoCommand.savedValue = value;
});
this.undoCommand = undoCommand;
}
undo() {
console.log(`Redo Delete: ${this.undoCommand.savedValue}`);
this.undoCommand.savedValue = null;
}
}
class History {
constructor() {
this.commands = [];
this.savedValue = null;
}
addCommand(command) {
this.commands.push(command);
}
undo() {
if (this.commands.length > 0) {
const lastCommand = this.commands[this.commands.length - 1];
lastCommand.undo();
this.commands.pop();
} else {
console.log("No commands to undo");
}
}
redo() {
if (this.commands.length > 0) {
const lastCommand = this.commands[this.commands.length - 1];
lastCommand.execute();
this.commands.pop();
} else {
console.log("No commands to redo");
}
}
execute() {
for(let command of this.commands) {
command.execute();
}
}
}
const history = new History();
const addCommand1 = new AddCommand(history, "Value 1");
const addCommand2 = new AddCommand(history, "Value 2");
const deleteCommand = new DeleteCommand(history, "Value 2");
history.addCommand(addCommand1);
history.addCommand(addCommand2);
history.addCommand(deleteCommand);
history.execute();
history.undo();
history.redo();
首先,这段代码主要实现了撤销和重做功能,采用的是命令模式(Command Pattern)。这是一种软件设计模式,它封装了一个或多个操作的对象,直到这些操作被调用。
Command 类:这是一个抽象类,定义了一个 execute() 方法,这个方法是在发出命令时要执行的逻辑。同时,这个类还定义了一个 undo() 方法,这个方法是在撤销命令时执行的逻辑。
AddCommand 和 DeleteCommand 类:这两个类是继承了 Command 类的具体子类,分别实现了添加和删除操作的命令。在构造函数中,它们都接受一个 undoCommand 和一个值,当执行命令时,它们会将这个值保存在 undoCommand 的 savedValue 中。
History 类:这个类负责保存所有的命令,并且提供了撤销和重做的功能。在执行命令时,它会在命令列表的末尾添加新的命令。在撤销命令时,它会调用最后一个命令的 undo() 方法,并且从命令列表中移除这个命令。在重做命令时,它会再次执行最后一个命令,并且将这个命令重新添加到命令列表的末尾。
所以,当创建一个 History 对象并添加 AddCommand 或 DeleteCommand 对象,然后依次执行这些命令时,可以通过 undo() 和 redo() 方法来回退和重做这些命令。