一、前言
LeetCode 在前不久出了一个 JavaScript 专栏,这个专栏一个目的是为了非前端工程师学习 JS,另一个是为了前端工程师提升 JS 能力。
因此在这个专栏中,基本不涉及什么具体算法问题,都是一些 JS 的入门语法与常见的 JS 面试题, 但我在给朋友推荐该专栏时阻力非常大,绝大部分当看到是 LeetCode 链接时就直接失去了点击的欲望,认为里面都是十分烧脑的算法题,而实际题目远比想象中的简单,甚至远比平时面试题的解答都要简单。
由于整个专栏中的题目难易程度参差不齐,甚至有 HelloWorld 一类的题目,为了保证整体文章质量,不会对过于简单的题目进行讲解。
我写这篇文章的目的有以下几方面:
- 减缓一部分前端开发对 LeetCode 及算法的抗拒性
- 巩固及梳理自己的做题笔记
- 帮助刚入门或基础较差的前端工程师梳理实现思路
注: 本篇写于 2023-07-09,最早发表于掘金,因 CSDN 同步文章格式有问题,故重新发表。
二、算法题目
1. 记忆函数
LeetCode地址:2623. 记忆函数
请你编写一个函数,它接收另一个函数作为输入,并返回该函数的 记忆化 后的结果。
记忆函数 是一个对于相同的输入永远不会被调用两次的函数。相反,它将返回一个缓存值。
你可以假设有 3 个可能的输入函数:sum 、fib 和 factorial 。
- sum 接收两个整型参数 a 和 b ,并返回 a + b 。
- fib 接收一个整型参数 n ,如果 n
// 创建缓存对象
const map = new Map();
return function(...args) {
// 将参数转换为字符串
// 可以通过 JSON.string、 args.join、 args.toString 等方法
// 注意:虽然 Map 的 key 值可以是对象、数组,
// 但是由于每次接收的参数列表都是一个全新的引用地址,所以不能直接作为 key 值
const argsStr = args.join(',');
// 如果没有命中缓存则进行计算后再返回
if(!map.has(argsStr)){
map.set(argsStr, fn(...args))
}
return map.get(argsStr)
}
}
const n = this.length;
if(rowsCount * colsCount !== n) return [];
const array = [];
for(let i = 0; i
3.扁平化嵌套数组
LeetCode地址:2625. 扁平化嵌套数组
请你编写一个函数,它接收一个 多维数组 arr 和它的深度 n ,并返回该数组的 扁平化 后的结果。
多维数组 是一种包含整数或其他 多维数组 的递归数据结构。
数组 扁平化 是对数组的一种操作,定义是将原数组部分或全部子数组删除,并替换为该子数组中的实际元素。只有当嵌套的数组深度大于 n 时,才应该执行扁平化操作。第一层数组中元素的深度被认为是 0。
请在没有使用内置方法 Array.flat 的前提下解决这个问题。
思路
这是一道经典的面试题:手写 Array.proptype.flat 方法, 即手写拍平数组。
这道题我给出的解法是最常见的 DFS解法,但类似的,还可以使用 BFS、迭代 方法实现。
具体实现思路就是:通过递归并遍历数组的每个元素,如果是子元素是数组,则递归调用 flat 函数来进一步展开子数组。
通过使用递归和回溯的方式,可以逐层深入地处理多维数组,直到达到指定的展开深度。这种深度优先搜索的方法使得展开过程可以有效地处理多层嵌套的数组,并按照深度优先的顺序将元素依次展开到一维数组中。
具体实现
/** * @param {any[]} arr * @param {number} depth * @return {any[]} */ var flat = function (arr, n) { if(n === 0) return arr; const array = []; for(let i = 0; i
4.函数防抖
LeetCode地址:2627. 函数防抖
请你编写一个函数,接收参数为另一个函数和一个以毫秒为单位的时间 t ,并返回该函数的 函数防抖 后的结果。
函数防抖 方法是一个函数,它的执行被延迟了 t 毫秒,如果在这个时间窗口内再次调用它,它的执行将被取消。你编写的防抖函数也应该接收传递的参数。
例如,假设 t = 50ms ,函数分别在 30ms 、 60ms 和 100ms 时调用。前两个函数调用将被取消,第三个函数调用将在 150ms 执行。如果改为 t = 35ms ,则第一个调用将被取消,第二个调用将在 95ms 执行,第三个调用将在 135ms 执行。
上图展示了了防抖函数是如何转换事件的。其中,每个矩形表示 100ms,反弹时间为 400ms。每种颜色代表一组不同的输入。
请在不使用 lodash 的 _.debounce() 函数的前提下解决该问题。
思路
同样是一道经典面试题:手写 防抖 函数,相比起实际面试中可能还会给出更复杂的业务场景,这个道题只要求实现基本功能即可。
debounce 主要功能是,在一定时间内频繁触发,只执行最后一次操作。
创建一个定时器,在 t 毫秒后执行方法,如果这段时间内再次触发,则直接清除掉上个定时器,重新计时即可。
具体实现
/** * @param {Function} fn * @param {number} t milliseconds * @return {Function} */ var debounce = function(fn, t) { let timer = ''; return function(...args) { clearInterval(timer) timer = setTimeout(()=> fn(...args),t) } };
5.完全相等的 JSON 字符串
LeetCode地址:2628. 完全相等的 JSON 字符串
给定两个对象 o1 和 o2 ,请你检查它们是否 完全相等 。
对于两个 完全相等 的对象,它们必须包含相同的键,并且相关的值也必须 完全相等 。如果两个对象通过了 '===' 相等性检查,它们也被认为是 完全相等 的。
你可以假设这两个对象都是 JSON.parse 的输出。换句话说,它们是有效的 JSON 。
请你在不使用 lodash 的 _.isEqual() 函数的前提下解决这个问题。
思路
由于通过 === 在判断两个对象是否相等时,判断的是两个对象的引用地址,所以必须通过递归的方式逐层解析到基本类型才能实现该功能。
比较完全相等主要是考虑 数组 与 对象 这两类引用类型的数据,如果是基本类型可直接通过 === 进行判断。
如果是数组或者是对象时,考虑到数组及对象中 可能还会嵌套对象、数组,所以需要递归判断每一个元素是否相等。
具体实现
/** * @param {any} o1 * @param {any} o2 * @return {boolean} */ var getType = (val) => Object.prototype.toString.call(val); var areDeeplyEqual = function (o1, o2) { // 如果两个对象类型不同,则值一定不同 if (getType(o1) !== getType(o2)) return false; // 如果传入参是基本类型、或者是 null、undefined 等直接进行比较 if (!o1 || !o2 || typeof o1 !== 'object') return o1 === o2; // 递归检查数组、对象 if (Object.keys(o1).length !== Object.keys(o2).length) return false; for (const k in o1) if (!areDeeplyEqual(o1[k], o2[k])) return false; return true; };
三、结语
由于算法讲解本身就很难通过 少量的语言就能表述清除,让文字通俗易懂更是难上加难,为了避免篇幅太长而导致放弃或者直接丢到收藏夹中吃灰,所以本篇只提取了 5 道题进行讲解。
算法的实现本身就多种多样的,我的个人见解未必是最优解,我非常欢迎读者对我在文章中提出的观点、方法或示例进行评价和反馈。这对于我个人的成长和进步非常重要,也有助于确保我传达的信息准确无误。所以,请不要犹豫,如果你有任何想法或建议,请在阅读文章后留下你的评论。