楼主: Myron2017
跳转到指定楼层
上一主题 下一主题
收起左侧

刷题记录帖子

🔗
 楼主| Myron2017 2026-9-25 10:06:41 | 只看该作者
全局:
2625. Flatten Deeply Nested Array
Medium
conpanies icon
Companies
Hint
Given a multi-dimensional array arr and a depth n, return a flattened version of that array.

A multi-dimensional array is a recursive data structure that contains integers or other multi-dimensional arrays.

A flattened array is a version of that array with some or all of the sub-arrays removed and replaced with the actual elements in that sub-array. This flattening operation should only be done if the current depth of nesting is less than n. The depth of the elements in the first array are considered to be 0.

Please solve it without the built-in Array.flat method.



Example 1:

Input
arr = [1, 2, 3, [4, 5, 6], [7, 8, [9, 10, 11], 12], [13, 14, 15]]
n = 0
Output
[1, 2, 3, [4, 5, 6], [7, 8, [9, 10, 11], 12], [13, 14, 15]]

Explanation
Passing a depth of n=0 will always result in the original array. This is because the smallest possible depth of a subarray (0) is not less than n=0. Thus, no subarray should be flattened.
Example 2:

Input
arr = [1, 2, 3, [4, 5, 6], [7, 8, [9, 10, 11], 12], [13, 14, 15]]
n = 1
Output
[1, 2, 3, 4, 5, 6, 7, 8, [9, 10, 11], 12, 13, 14, 15]

Explanation
The subarrays starting with 4, 7, and 13 are all flattened. This is because their depth of 0 is less than 1. However [9, 10, 11] remains unflattened because its depth is 1.
Example 3:

Input
arr = [[1, 2, 3], [4, 5, 6], [7, 8, [9, 10, 11], 12], [13, 14, 15]]
n = 2
Output
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]

Explanation
The maximum depth of any subarray is 1. Thus, all of them are flattened.


Constraints:

0 <= count of numbers in arr <= 105
0 <= count of subarrays in arr <= 105
maxDepth <= 1000
-1000 <= each number <= 1000
0 <= n <= 1000

这道题要求实现一个多维数组扁平化函数 flat,且展开的深度受层级 n 限制(不能直接使用原生 Array.prototype.flat)。解题思路与核心机制基准条件(Base Case):如果 n === 0,代表不能展开任何子数组,直接返回原数组。递归铺平(Recursive Flattening):遍历当前数组中的每一个元素 item。如果 item 是一个子数组(Array.isArray(item))且 n > 0:递归调用 flat(item, n - 1),并将展开后的元素加入结果集中。否则(item 是数字或者 n 已经为 0):直接将 item 加入结果集中。性能优化(避免 ... 展开运算符开销):使用 result.push(...flat(...)) 会造成多次数组解构和创建,对于海量数据的测试用例(如题目约束 $10^5$)容易导致内存溢出(MLE)或超时(TLE)。推荐方案:采用递归函数内部使用 for 循环追加,或者使用非递归栈(Stack)来迭代展开。
  1. type MultiDimensionalArray = (number | MultiDimensionalArray)[];

  2. var flat = function (arr:  MultiDimensionalArray, n: number):  MultiDimensionalArray {
  3.     if (n === 0) return arr;

  4.     const result: MultiDimensionalArray = [];

  5.     function helper(currentArr: MultiDimensionalArray, currentDepth: number) {
  6.         for (const item of currentArr) {
  7.             // 如果是数组且还可以继续展开
  8.             if (Array.isArray(item) && currentDepth < n) {
  9.                 helper(item, currentDepth + 1);
  10.             } else {
  11.                 result.push(item);
  12.             }
  13.         }
  14.     }

  15.     helper(arr, 0);
  16.     return result;
  17. };
复制代码
回复

使用道具 举报

🔗
 楼主| Myron2017 2026-9-25 10:10:30 | 只看该作者
全局:
本帖最后由 Myron2017 于 2026-9-24 21:11 编辑

2627. Debounce
Medium
conpanies icon
Companies
Hint
Given a function fn and a time in milliseconds t, return a debounced version of that function.

A debounced function is a function whose execution is delayed by t milliseconds and whose execution is cancelled if it is called again within that window of time. The debounced function should also receive the passed parameters.

For example, let's say t = 50ms, and the function was called at 30ms, 60ms, and 100ms.

The first 2 function calls would be cancelled, and the 3rd function call would be executed at 150ms.

If instead t = 35ms, The 1st call would be cancelled, the 2nd would be executed at 95ms, and the 3rd would be executed at 135ms.

Debounce Schematic

The above diagram shows how debounce will transform events. Each rectangle represents 100ms and the debounce time is 400ms. Each color represents a different set of inputs.

Please solve it without using lodash's _.debounce() function.



Example 1:

Input:
t = 50
calls = [
  {"t": 50, inputs: [1]},
  {"t": 75, inputs: [2]}
]
Output: [{"t": 125, inputs: [2]}]
Explanation:
let start = Date.now();
function log(...inputs) {
  console.log([Date.now() - start, inputs ])
}
const dlog = debounce(log, 50);
setTimeout(() => dlog(1), 50);
setTimeout(() => dlog(2), 75);

The 1st call is cancelled by the 2nd call because the 2nd call occurred before 100ms
The 2nd call is delayed by 50ms and executed at 125ms. The inputs were (2).
Example 2:

Input:
t = 20
calls = [
  {"t": 50, inputs: [1]},
  {"t": 100, inputs: [2]}
]
Output: [{"t": 70, inputs: [1]}, {"t": 120, inputs: [2]}]
Explanation:
The 1st call is delayed until 70ms. The inputs were (1).
The 2nd call is delayed until 120ms. The inputs were (2).
Example 3:

Input:
t = 150
calls = [
  {"t": 50, inputs: [1, 2]},
  {"t": 300, inputs: [3, 4]},
  {"t": 300, inputs: [5, 6]}
]
Output: [{"t": 200, inputs: [1,2]}, {"t": 450, inputs: [5, 6]}]
Explanation:
The 1st call is delayed by 150ms and ran at 200ms. The inputs were (1, 2).
The 2nd call is cancelled by the 3rd call
The 3rd call is delayed by 150ms and ran at 450ms. The inputs were (5, 6).


Constraints:

0 <= t <= 1000
1 <= calls.length <= 10
0 <= calls[i].t <= 1000
0 <= calls[i].inputs.length <= 10

什么是防抖 (Debounce)?
防抖(Debounce)是一种限制函数执行频率的技术。它的核心思想是:如果在设定的时间窗口内不断触发事件,函数不会执行;只有当事件停止触发,并且经过了设定的时间后,函数才会执行一次。

生活中的比喻:
想象一部电梯,设定等待 3 秒后关门。如果在此期间有人按了开门键,或者走进了电梯,这 3 秒的倒计时就会重置。只有在整整 3 秒内没有任何人打断的情况下,电梯门才会真正关闭。

在这道题中:

“电梯关门” = 执行原函数 fn。

“3秒” = 延迟时间 t。

“有人走进来” = 再次调用这个防抖函数。

TypeScript 实现代码
利用 JavaScript 的闭包 (Closure) 和定时器 setTimeout / clearTimeout,我们可以很优雅地实现这个逻辑。
  1. type F = (...args: number[]) => void

  2. function debounce(fn: F, t: number): F {
  3.    // 用于保存定时器的引用,放在闭包中以便多次调用时共享
  4.     let timerId: ReturnType<typeof setTimeout> | null = null;
  5.    
  6.     return function(...args) {
  7.         // 如果在 t 毫秒内再次调用了该函数,清除之前的定时器(重置倒计时)
  8.         if (timerId !== null) {
  9.             clearTimeout(timerId);
  10.         }
  11.         
  12.         // 重新开始 t 毫秒的倒计时
  13.         timerId = setTimeout(() => {
  14.             fn(...args); // 倒计时结束,执行原函数并传入参数
  15.         }, t);
  16.     }
  17. };

  18. /**
  19. * const log = debounce(console.log, 100);
  20. * log('Hello'); // cancelled
  21. * log('Hello'); // cancelled
  22. * log('Hello'); // Logged at t=100ms
  23. */
复制代码
代码原理解析
闭包存储状态 (timerId):
在 debounce 函数内部,我们声明了 timerId。因为返回的匿名函数捕获了这个变量,所以即便 debounce 执行完毕,返回的新函数在以后每次被调用时,依然能访问和修改同一个 timerId。

清除旧定时器 (clearTimeout):
每次触发返回的函数时,第一件事就是检查是否已经有一个在等待的定时器。如果有,立刻用 clearTimeout(timerId) 把它干掉。这就相当于“把电梯关门的倒计时清零”。

设置新定时器 (setTimeout):
清除了旧的定时器后,立刻创建一个新的 setTimeout。只有当这个新的定时器安安稳稳地度过了 t 毫秒,且没有被后续的调用打断时,它内部的箭头函数才会执行,从而调用真正的目标函数 fn(...args)。

TypeScript 类型细节:
在浏览器环境中 setTimeout 返回一个 number,而在 Node.js 环境中返回一个 NodeJS.Timeout 对象。为了让 TypeScript 代码在任何环境都不报错,使用 ReturnType<typeof setTimeout> 是最严谨的写法。
回复

使用道具 举报

🔗
 楼主| Myron2017 2026-9-25 10:14:00 | 只看该作者
全局:
2776. Convert Callback Based Function to Promise Based Function
Medium
Write a function that accepts another function fn and converts the callback-based function into a promise-based function.

The function fn takes a callback as its first argument, along with any additional arguments args passed as separate inputs.

The promisify function returns a new function that should return a promise. The promise should resolve with the argument passed as the first parameter of the callback when the callback is invoked without error, and reject with the error when the callback is called with an error as the second argument.

The following is an example of a function that could be passed into promisify.

function sum(callback, a, b) {
  if (a < 0 || b < 0) {
    const err = Error('a and b must be positive');
    callback(undefined, err);
  } else {
    callback(a + b);
  }
}
This is the equivalent code based on promises:

async function sum(a, b) {
  if (a < 0 || b < 0) {
    throw Error('a and b must be positive');
  } else {
    return a + b;
  }
}


Example 1:

Input:
fn = (callback, a, b, c) => {
    callback(a * b * c);
}
args = [1, 2, 3]
Output: {"resolved": 6}
Explanation:
const asyncFunc = promisify(fn);
asyncFunc(1, 2, 3).then(console.log); // 6

fn is called with a callback as the first argument and args as the rest. The promise based version of fn resolves a value of 6 when called with (1, 2, 3).
Example 2:

Input:
fn = (callback, a, b, c) => {
    callback(a * b * c, "Promise Rejected");
}
args = [4, 5, 6]
Output: {"rejected": "Promise Rejected"}
Explanation:
const asyncFunc = promisify(fn);
asyncFunc(4, 5, 6).catch(console.log); // "Promise Rejected"

fn is called with a callback as the first argument and args as the rest. As the second argument, the callback accepts an error message, so when fn is called, the promise is rejected with a error message provided in the callback. Note that it did not matter what was passed as the first argument into the callback.


Constraints:

1 <= args.length <= 100
0 <= args[i] <= 104

Promisify (回调函数转 Promise) 原理解析
在 JavaScript 早期,异步操作主要依赖于回调函数 (Callback)。但随着业务复杂度增加,多层嵌套的回调会导致“回调地狱 (Callback Hell)”。为了解决这个问题,ES6 引入了 Promise。

promisify 就是一个桥梁函数:它接收一个旧式的、基于回调的函数,将其包装并返回一个新的、基于 Promise 的函数。这样你就可以使用 .then() 或者更现代的 async / await 语法来调用它。

在这道题中,我们需要将原本通过 fn(callback, arg1, arg2) 调用的函数,转换成 fn(arg1, arg2).then(...) 的形式。
  1. type CallbackFn = (
  2.     next: (data: number, error: string) => void,
  3.     ...args: number[]
  4. ) => void
  5. type Promisified = (...args: number[]) => Promise<number>

  6. function promisify(fn: CallbackFn): Promisified {
  7.     return async function(...args) {
  8.         // 返回一个新的 Promise 对象
  9.         return new Promise((resolve, reject) => {
  10.             // 定义我们自己的回调函数来拦截结果和错误
  11.             const callback = (data: number, error: string) => {
  12.                 if (error) {
  13.                     // 如果存在第二个参数 (error),说明发生了错误,执行 reject
  14.                     reject(error);
  15.                 } else {
  16.                     // 否则说明执行成功,将第一个参数 (data) resolve 出去
  17.                     resolve(data);
  18.                 }
  19.             };
  20.             
  21.             // 调用原始函数,传入我们拦截用的 callback 以及其余参数
  22.             fn(callback, ...args);
  23.         });
  24.     };
  25. };

  26. /**
  27. * const asyncFunc = promisify(callback => callback(42));
  28. * asyncFunc().then(console.log); // 42
  29. */
复制代码
代码原理解析
返回 Promise:
在返回的函数内部,我们通过 new Promise((resolve, reject) => { ... }) 创建并返回了一个 Promise 实例。这使得转换后的函数能够支持 .then() 和 await 语法。

拦截并重写回调函数 (callback):
原始的 fn 期望它的第一个参数是一个回调函数。我们自定义了一个 callback(data, error) 传递给它。

当原始的 fn 处理完毕并调用这个 callback 时,由我们的逻辑来接管。

如果 fn 传入了 error(即第二个参数存在),我们就调用 Promise 的 reject(error),这会触发外部的 .catch() 或抛出异常。

如果没有 error,我们就调用 Promise 的 resolve(data),将数据传递给外部的 .then()。

透传参数 (...args):
通过扩展运算符 ...args,我们将原封不动的参数列表跟随在 callback 之后传递给 fn,即 fn(callback, arg1, arg2, ...)。这样就完美适配了原始函数的入参结构。
回复

使用道具 举报

🔗
 楼主| Myron2017 2026-9-25 10:18:33 | 只看该作者
全局:
2759. Convert JSON String to Object
Hard
conpanies icon
Companies
Given a string str, return parsed JSON parsedStr. You may assume the str is a valid JSON string hence it only includes strings, numbers, arrays, objects, booleans, and null. str will not include invisible characters and escape characters.

Please solve it without using the built-in JSON.parse method.



Example 1:

Input: str = '{"a":2,"b":[1,2,3]}'
Output: {"a":2,"b":[1,2,3]}
Explanation: Returns the object represented by the JSON string.
Example 2:

Input: str = 'true'
Output: true
Explanation: Primitive types are valid JSON.
Example 3:

Input: str = '[1,5,"false",{"a":2}]'
Output: [1,5,"false",{"a":2}]
Explanation: Returns the array represented by the JSON string.


Constraints:

str is a valid JSON string
1 <= str.length <= 105


什么是递归下降解析 (Recursive Descent Parsing)?
要把一个 JSON 字符串转换成真正的 JavaScript 对象,最经典的实现方式是写一个递归下降解析器 (Recursive Descent Parser)。
它的核心思路非常直观:通过一个游标(指针)从左到右依次读取字符串的每个字符,根据遇到的字符类型,决定接下来“按什么规则”去解析,解析完毕后再把游标往后推。

由于题目保证了输入一定是一个合法的 JSON 字符串,并且没有不可见字符(空格、换行等)和转义字符,这大大简化了我们的工作。

我们只需要根据当前字符 str[i] 判断接下来的数据类型:

遇到 ",说明接下来是一个字符串,一直读到下一个 " 为止。

遇到 {,说明接下来是一个对象,循环解析“键”和“值”,直到遇到 }。

遇到 [,说明接下来是一个数组,循环解析“值”,直到遇到 ]。

遇到 t、f、n,说明肯定是 true、false 或者 null,直接让指针跳过对应单词的长度即可。

剩下的情况肯定是一个数字(以 0-9 或 - 开头),读到不属于数字的字符为止,并转化为数字类型。
  1. type JSONValue = null | boolean | number | string | JSONValue[] | { [key: string]: JSONValue };

  2. function jsonParse(str: string): JSONValue {
  3.     let i = 0; // 全局游标,用于记录当前解析到了字符串的哪个位置

  4.     // 主解析函数,负责分发到具体的类型解析器
  5.     function parseValue(): JSONValue {
  6.         const char = str[i];
  7.         
  8.         if (char === '"') return parseString();
  9.         if (char === '{') return parseObject();
  10.         if (char === '[') return parseArray();
  11.         if (char === 't') return parseTrue();
  12.         if (char === 'f') return parseFalse();
  13.         if (char === 'n') return parseNull();
  14.         
  15.         // 如果不是以上字符开头,那一定是个数字 (包含负号 -)
  16.         return parseNumber();
  17.     }

  18.     function parseString(): string {
  19.         i++; // 跳过开头的 '"'
  20.         const start = i;
  21.         while (i < str.length && str[i] !== '"') {
  22.             i++;
  23.         }
  24.         const result = str.substring(start, i);
  25.         i++; // 跳过结尾的 '"'
  26.         return result;
  27.     }

  28.     function parseNumber(): number {
  29.         const start = i;
  30.         // 匹配合法的数字字符 (包括负号、小数点、科学计数法 e/E 和 +)
  31.         while (i < str.length) {
  32.             const c = str[i];
  33.             if ((c >= '0' && c <= '9') || c === '-' || c === '+' || c === '.' || c === 'e' || c === 'E') {
  34.                 i++;
  35.             } else {
  36.                 break;
  37.             }
  38.         }
  39.         return Number(str.substring(start, i));
  40.     }

  41.     function parseTrue(): boolean {
  42.         i += 4; // 跳过 "true"
  43.         return true;
  44.     }

  45.     function parseFalse(): boolean {
  46.         i += 5; // 跳过 "false"
  47.         return false;
  48.     }

  49.     function parseNull(): null {
  50.         i += 4; // 跳过 "null"
  51.         return null;
  52.     }

  53.     function parseArray(): JSONValue[] {
  54.         i++; // 跳过 '['
  55.         const arr: JSONValue[] = [];
  56.         
  57.         if (str[i] === ']') { // 处理空数组 []
  58.             i++;
  59.             return arr;
  60.         }

  61.         while (true) {
  62.             arr.push(parseValue()); // 递归解析数组内的值
  63.             
  64.             if (str[i] === ',') {
  65.                 i++; // 跳过逗号,继续解析下一个
  66.             } else if (str[i] === ']') {
  67.                 i++; // 遇到 ']' 数组结束
  68.                 break;
  69.             }
  70.         }
  71.         return arr;
  72.     }

  73.     function parseObject(): { [key: string]: JSONValue } {
  74.         i++; // 跳过 '{'
  75.         const obj: { [key: string]: JSONValue } = {};
  76.         
  77.         if (str[i] === '}') { // 处理空对象 {}
  78.             i++;
  79.             return obj;
  80.         }

  81.         while (true) {
  82.             const key = parseString(); // 对象的键一定是字符串
  83.             
  84.             i++; // 跳过键值对中间的 ':'
  85.             
  86.             const value = parseValue(); // 递归解析对应的值
  87.             obj[key] = value;
  88.             
  89.             if (str[i] === ',') {
  90.                 i++; // 跳过逗号,继续解析下一对
  91.             } else if (str[i] === '}') {
  92.                 i++; // 遇到 '}' 对象结束
  93.                 break;
  94.             }
  95.         }
  96.         return obj;
  97.     }

  98.     // 启动解析
  99.     return parseValue();
  100. };
复制代码
回复

使用道具 举报

🔗
 楼主| Myron2017 2026-9-25 10:24:16 | 只看该作者
全局:
2795. Parallel Execution of Promises for Individual Results Retrieval
Medium
Given an array functions, return a promise promise. functions is an array of functions that return promises fnPromise. Each fnPromise can be resolved or rejected.  

If fnPromise is resolved:

    obj = { status: "fulfilled", value: resolved value}

If fnPromise is rejected:

    obj = { status: "rejected", reason: reason of rejection (catched error message)}

The promise should resolve with an array of these objects obj. Each obj in the array should correspond to the promises in the original array function, maintaining the same order.

Try to implement it without using the built-in method Promise.allSettled().



Example 1:

Input: functions = [
    () => new Promise(resolve => setTimeout(() => resolve(15), 100))
]
Output: {"t":100,"values":[{"status":"fulfilled","value":15}]}
Explanation:
const time = performance.now()
const promise = promiseAllSettled(functions);
               
promise.then(res => {
    const out = {t: Math.floor(performance.now() - time), values: res}
    console.log(out) // {"t":100,"values":[{"status":"fulfilled","value":15}]}
})

The returned promise resolves within 100 milliseconds. Since promise from the array functions is fulfilled, the resolved value of the returned promise is set to [{"status":"fulfilled","value":15}].
Example 2:

Input: functions = [
    () => new Promise(resolve => setTimeout(() => resolve(20), 100)),
    () => new Promise(resolve => setTimeout(() => resolve(15), 100))
]
Output:
{
    "t":100,
    "values": [
        {"status":"fulfilled","value":20},
        {"status":"fulfilled","value":15}
    ]
}
Explanation: The returned promise resolves within 100 milliseconds, because the resolution time is determined by the promise that takes the longest time to fulfill. Since promises from the array functions are fulfilled, the resolved value of the returned promise is set to [{"status":"fulfilled","value":20},{"status":"fulfilled","value":15}].
Example 3:

Input: functions = [
    () => new Promise(resolve => setTimeout(() => resolve(30), 200)),
    () => new Promise((resolve, reject) => setTimeout(() => reject("Error"), 100))
]
Output:
{
    "t":200,
    "values": [
        {"status":"fulfilled","value":30},
        {"status":"rejected","reason":"Error"}
    ]
}
Explanation: The returned promise resolves within 200 milliseconds, as its resolution time is determined by the promise that takes the longest time to fulfill. Since one promise from the array function is fulfilled and another is rejected, the resolved value of the returned promise is set to an array containing objects in the following order: [{"status":"fulfilled","value":30}, {"status":"rejected","reason":"Error"}]. Each object in the array corresponds to the promises in the original array function, maintaining the same order.


Constraints:

1 <= functions.length <= 10

什么是 Promise.allSettled()?
在处理多个并发 Promise 时,常用的 Promise.all() 有一个特点:一损俱损。只要其中一个 Promise 失败(被 reject),整个 Promise.all() 就会立刻抛出错误,导致你丢失其他已经成功的数据。

而 Promise.allSettled() 的逻辑是:耐心等待所有人交卷,不管及格还是不及格。它永远不会被 reject,而是等待所有传入的 Promise 均达到稳定状态(无论是 resolved 还是 rejected)后,把每个 Promise 的最终结果收集到一个数组中并返回。

在这道题中,我们需要手动实现这个逻辑:

维护一个长度等于入参数量的结果数组,确保输出顺序与输入顺序严格一致。

维护一个计数器,每当一个 Promise 完成(无论成功还是失败),计数器加 1。

当计数器等于函数数组的长度时,说明所有的 Promise 都已经有了结果,此时将结果数组 resolve 出去。
  1. type FulfilledObj = {
  2.     status: 'fulfilled';
  3.     value: any; // 修改为 any,因为示例中 value 可能是数字 (如 15)
  4. }
  5. type RejectedObj = {
  6.     status: 'rejected';
  7.     reason: any; // 同样修改为 any 保持兼容
  8. }
  9. type Obj = FulfilledObj | RejectedObj;

  10. function promiseAllSettled(functions: Function[]): Promise<Obj[]> {
  11.     return new Promise((resolve) => {
  12.         // 如果传入的是空数组,直接 resolve 一个空数组
  13.         if (functions.length === 0) {
  14.             return resolve([]);
  15.         }

  16.         // 创建一个固定长度的数组,用于按索引记录结果,保证顺序
  17.         const results: Obj[] = new Array(functions.length);
  18.         // 用于记录已经结束 (settled) 的 Promise 数量
  19.         let settledCount = 0;

  20.         functions.forEach((fn, index) => {
  21.             try {
  22.                 // 执行函数获取 Promise
  23.                 fn()
  24.                     .then((value: any) => {
  25.                         // 成功时,按原索引存入 fulfilled 状态
  26.                         results[index] = { status: 'fulfilled', value };
  27.                     })
  28.                     .catch((reason: any) => {
  29.                         // 失败时,按原索引存入 rejected 状态
  30.                         results[index] = { status: 'rejected', reason };
  31.                     })
  32.                     .finally(() => {
  33.                         // 无论成功还是失败,都算作一次“结束”
  34.                         settledCount++;
  35.                         // 如果所有的 Promise 都已结束,将最终的数组交出去
  36.                         if (settledCount === functions.length) {
  37.                             resolve(results);
  38.                         }
  39.                     });
  40.             } catch (error) {
  41.                 // 兼容边界情况:如果 fn() 本身在同步阶段抛出了异常而不是返回 rejected Promise
  42.                 results[index] = { status: 'rejected', reason: error };
  43.                 settledCount++;
  44.                 if (settledCount === functions.length) {
  45.                     resolve(results);
  46.                 }
  47.             }
  48.         });
  49.     });
  50. };


  51. /**
  52. * const functions = [
  53. *    () => new Promise(resolve => setTimeout(() => resolve(15), 100))
  54. * ]
  55. * const time = performance.now()
  56. *
  57. * const promise = promiseAllSettled(functions);
  58. *              
  59. * promise.then(res => {
  60. *     const out = {t: Math.floor(performance.now() - time), values: res}
  61. *     console.log(out) // {"t":100,"values":[{"status":"fulfilled","value":15}]}
  62. * })
  63. */
复制代码
回复

使用道具 举报

🔗
 楼主| Myron2017 2026-9-26 09:37:07 | 只看该作者
全局:
2633. Convert Object to JSON String
Medium
conpanies icon
Companies
Hint
Given a value, return a valid JSON string of that value. The value can be a string, number, array, object, boolean, or null. The returned string should not include extra spaces. The order of keys should be the same as the order returned by Object.keys().

Please solve it without using the built-in JSON.stringify method.



Example 1:

Input: object = {"y":1,"x":2}
Output: {"y":1,"x":2}
Explanation:
Return the JSON representation.
Note that the order of keys should be the same as the order returned by Object.keys().
Example 2:

Input: object = {"a":"str","b":-12,"c":true,"d":null}
Output: {"a":"str","b":-12,"c":true,"d":null}
Explanation:
The primitives of JSON are strings, numbers, booleans, and null.
Example 3:

Input: object = {"key":{"a":1,"b":[{},null,"Hello"]}}
Output: {"key":{"a":1,"b":[{},null,"Hello"]}}
Explanation:
Objects and arrays can include other objects and arrays.
Example 4:

Input: object = true
Output: true
Explanation:
Primitive types are valid inputs.


Constraints:

value is a valid JSON value
1 <= JSON.stringify(object).length <= 105
maxNestingLevel <= 1000
all strings contain only alphanumeric characters


什么是 JSON 序列化 (JSON Stringify)?
JSON 序列化是将 JavaScript 中的数据结构(对象、数组、字符串、数字等)转换成 JSON 格式的字符串的过程。
这道题要求我们手动实现 JSON.stringify()。由于 JSON 的数据结构天生就是树状的(Tree-like)——对象可以包含对象,数组可以包含数组。因此,处理这种嵌套结构最自然的方法就是递归 (Recursion)。

我们需要针对不同的数据类型采取不同的格式化策略:

基础类型 (Primitive types):

null -> 直接返回字符串 "null"。

number 和 boolean -> 直接将其转换为字符串,例如 12 -> "12",true -> "true"。

string -> 需要在字符串首尾加上双引号,例如 "Hello" -> '"Hello"'。

引用类型 (Reference types):

Array -> 递归处理每个元素,用逗号 , 拼接,并在首尾加上 [ 和 ]。

Object -> 获取所有键,并在键的两侧加双引号。然后递归处理对应的值,拼接成 "key":value 的形式,用逗号拼接多个键值对,最后首尾加上 { 和 }。
  1. type JSONValue = null | boolean | number | string | JSONValue[] | { [key: string]: JSONValue };

  2. function jsonStringify(object: JSONValue): string {
  3.     // 1. 处理 null (注意:在 JS 中 typeof null 也是 'object',所以要优先判断)
  4.     if (object === null) {
  5.         return "null";
  6.     }

  7.     // 2. 处理字符串:首尾增加双引号
  8.     if (typeof object === "string") {
  9.         return '"' + object + '"';
  10.     }

  11.     // 3. 处理数字和布尔值:直接转成字符串形式
  12.     if (typeof object === "number" || typeof object === "boolean") {
  13.         return String(object);
  14.     }

  15.     // 4. 处理数组:递归序列化每一个元素,然后拼装
  16.     if (Array.isArray(object)) {
  17.         const arrayValues = object.map(item => jsonStringify(item));
  18.         return "[" + arrayValues.join(",") + "]";
  19.     }

  20.     // 5. 处理普通对象:获取所有 keys,递归序列化对应的值,然后拼装
  21.     if (typeof object === "object") {
  22.         const keys = Object.keys(object);
  23.         const objectValues = keys.map(key => {
  24.             return '"' + key + '":' + jsonStringify(object[key]);
  25.         });
  26.         return "{" + objectValues.join(",") + "}";
  27.     }

  28.     // 理论上不会走到这里,因为题目的输入保证是合法的 JSONValue
  29.     return "";
  30. };
复制代码
代码原理解析
类型判断的顺序:
在 JavaScript 中,typeof null 会返回 "object",这是一个历史遗留的 Bug。同时,typeof [] 也会返回 "object"。所以我们在处理对象之前,必须先拦截掉 null 和 Array.isArray()。

为什么用 .map() 和 .join(",")?:
对于数组 [1, 2, 3],我们希望输出 "[1,2,3]"。如果用 for 循环手动拼接字符串,需要在最后一个元素后面小心地去掉多余的逗号 ,。
使用 .join(",") 则非常优雅,它会自动在元素之间插入逗号,而不会在首尾产生多余的逗号,完美符合 JSON 没有多余空格和多余逗号的约束。

递归的终点:
当递归到最深层,遇到 string、number、boolean 或 null 这些基本数据类型时,不再继续调用 jsonStringify,而是直接返回转换好的字符串片段。这些片段像乐高积木一样,随着递归栈的返回,被外层的数组和对象一层层拼接起来,最终形成完整的 JSON 字符串。
回复

使用道具 举报

🔗
 楼主| Myron2017 2026-9-26 09:45:57 | 只看该作者
全局:
2805. Custom Interval
Medium
Function customInterval

Given a function fn, a number delay and a number period, return a number id.

customInterval is a function that should execute the provided function fn at intervals based on a linear pattern defined by the formula delay + period * count.

The count in the formula represents the number of times the interval has been executed starting from an initial value of 0.

Function customClearInterval

Given the id. id is the returned value from the function customInterval.

customClearInterval should stop executing provided function fn at intervals.

Note: The setTimeout and setInterval functions in Node.js return an object, not a number.



Example 1:

Input: delay = 50, period = 20, cancelTime = 225
Output: [50,120,210]
Explanation:
const t = performance.now()  
const result = []
        
const fn = () => {
    result.push(Math.floor(performance.now() - t))
}
const id = customInterval(fn, delay, period)
        
setTimeout(() => {
    customClearInterval(id)
}, 225)

50 + 20 * 0 = 50 // 50ms - 1st function call
50 + 20 * 1 = 70 // 50ms + 70ms = 120ms - 2nd function call
50 + 20 * 2 = 90 // 50ms + 70ms + 90ms = 210ms - 3rd function call
Example 2:

Input: delay = 20, period = 20, cancelTime = 150
Output: [20,60,120]
Explanation:
20 + 20 * 0 = 20 // 20ms - 1st function call
20 + 20 * 1 = 40 // 20ms + 40ms = 60ms - 2nd function call
20 + 20 * 2 = 60 // 20ms + 40ms + 60ms = 120ms - 3rd function call
Example 3:

Input: delay = 100, period = 200, cancelTime = 500
Output: [100,400]
Explanation:
100 + 200 * 0 = 100 // 100ms - 1st function call
100 + 200 * 1 = 300 // 100ms + 300ms = 400ms - 2nd function call


Constraints:

20 <= delay, period <= 250
20 <= cancelTime <= 1000


核心思路:递归的 setTimeout
因为题目要求每次执行的间隔时间是动态变化的(遵循公式 delay + period * count),所以我们不能直接使用原生的 setInterval(它的间隔是固定的)。

解决这类问题的标准做法是:使用递归的 setTimeout。即每次定时器触发完当前任务后,计算下一次需要等待的时间,并再次启动一个新的 setTimeout。

此外,题目还有一个关键限制:要求返回一个数字类型的 id,但在 Node.js 环境中,setTimeout 默认返回的是一个对象。因此,我们需要自己在全局维护一个映射表(Map),把我们生成的“数字 ID”和“真实的定时器对象”绑定起来,以便 customClearInterval 可以找到并清除它。
  1. // 使用一个全局的 Map 来存储我们自定义的数字 ID 和真实的定时器对象之间的映射关系
  2. const activeIntervals = new Map<number, ReturnType<typeof setTimeout>>();
  3. // 全局自增 ID,确保每次调用的 ID 都是唯一的
  4. let nextId = 0;

  5. function customInterval(fn: Function, delay: number, period: number): number {
  6.     const id = nextId++; // 获取当前 ID,并让 nextId 自增
  7.     let count = 0;       // 记录执行的次数
  8.    
  9.     // 定义一个递归函数来安排下一次执行
  10.     function scheduleNext() {
  11.         // 根据题目公式计算下一次需要等待的时间
  12.         const timeToWait = delay + period * count;
  13.         
  14.         // 开启定时器
  15.         const timeoutId = setTimeout(() => {
  16.             fn();             // 1. 执行用户传入的函数
  17.             count++;          // 2. 执行次数加 1
  18.             scheduleNext();   // 3. 递归调用,安排下一次执行
  19.         }, timeToWait);
  20.         
  21.         // 将最新的定时器对象保存到 Map 中,覆盖旧的定时器对象
  22.         activeIntervals.set(id, timeoutId);
  23.     }
  24.    
  25.     // 启动第一次执行
  26.     scheduleNext();
  27.    
  28.     return id; // 返回我们自定义的数字 ID
  29. }

  30. function customClearInterval(id: number): void {
  31.     // 如果 Map 中存在这个 ID
  32.     if (activeIntervals.has(id)) {
  33.         clearTimeout(activeIntervals.get(id)); // 取消真实的定时器
  34.         activeIntervals.delete(id);            // 从 Map 中移除记录,防止内存泄漏
  35.     }
  36. }
复制代码
代码原理解析
全局状态管理 (activeIntervals & nextId):
我们创建了一个 activeIntervals (Map 对象)。当用户调用 customClearInterval(1) 时,我们就会去字典里查找 1 对应的真实 setTimeout 引用,并用原生的 clearTimeout 将其终止。

核心递归 (scheduleNext):
在 customInterval 内部,闭包变量 count 初始为 0。
每次调用 scheduleNext,都会根据 delay + period * count 算出一个等待时间。
当时间一到,箭头函数触发:执行 fn() -> count 变成 1 -> 再次执行 scheduleNext()。此时公式算出的时间就会变长,如此往复。

动态更新定时器引用 (activeIntervals.set):
因为我们每次都在创建新的 setTimeout,所以同一个自定义 id 对应的原生定时器对象是不断变化的。我们必须在每次 scheduleNext 执行时,用 activeIntervals.set(id, timeoutId) 把最新的定时器引用更新到字典里。这样当外部随时调用 customClearInterval 喊停时,我们撤销的永远是当前正在等待的那一个定时器。
回复

使用道具 举报

🔗
 楼主| Myron2017 2026-9-26 10:06:47 | 只看该作者
全局:
2636. Promise Pool
Medium
conpanies icon
Companies
Hint
Given an array of asynchronous functions functions and a pool limit n, return an asynchronous function promisePool. It should return a promise that resolves when all the input functions resolve.

Pool limit is defined as the maximum number promises that can be pending at once. promisePool should begin execution of as many functions as possible and continue executing new functions when old promises resolve. promisePool should execute functions[i] then functions[i + 1] then functions[i + 2], etc. When the last promise resolves, promisePool should also resolve.

For example, if n = 1, promisePool will execute one function at a time in series. However, if n = 2, it first executes two functions. When either of the two functions resolve, a 3rd function should be executed (if available), and so on until there are no functions left to execute.

You can assume all functions never reject. It is acceptable for promisePool to return a promise that resolves any value.



Example 1:

Input:
functions = [
  () => new Promise(res => setTimeout(res, 300)),
  () => new Promise(res => setTimeout(res, 400)),
  () => new Promise(res => setTimeout(res, 200))
]
n = 2
Output: [[300,400,500],500]
Explanation:
Three functions are passed in. They sleep for 300ms, 400ms, and 200ms respectively.
They resolve at 300ms, 400ms, and 500ms respectively. The returned promise resolves at 500ms.
At t=0, the first 2 functions are executed. The pool size limit of 2 is reached.
At t=300, the 1st function resolves, and the 3rd function is executed. Pool size is 2.
At t=400, the 2nd function resolves. There is nothing left to execute. Pool size is 1.
At t=500, the 3rd function resolves. Pool size is zero so the returned promise also resolves.
Example 2:

Input:
functions = [
  () => new Promise(res => setTimeout(res, 300)),
  () => new Promise(res => setTimeout(res, 400)),
  () => new Promise(res => setTimeout(res, 200))
]
n = 5
Output: [[300,400,200],400]
Explanation:
The three input promises resolve at 300ms, 400ms, and 200ms respectively.
The returned promise resolves at 400ms.
At t=0, all 3 functions are executed. The pool limit of 5 is never met.
At t=200, the 3rd function resolves. Pool size is 2.
At t=300, the 1st function resolved. Pool size is 1.
At t=400, the 2nd function resolves. Pool size is 0, so the returned promise also resolves.
Example 3:

Input:
functions = [
  () => new Promise(res => setTimeout(res, 300)),
  () => new Promise(res => setTimeout(res, 400)),
  () => new Promise(res => setTimeout(res, 200))
]
n = 1
Output: [[300,700,900],900]
Explanation:
The three input promises resolve at 300ms, 700ms, and 900ms respectively.
The returned promise resolves at 900ms.
At t=0, the 1st function is executed. Pool size is 1.
At t=300, the 1st function resolves and the 2nd function is executed. Pool size is 1.
At t=700, the 2nd function resolves and the 3rd function is executed. Pool size is 1.
At t=900, the 3rd function resolves. Pool size is 0 so the returned promise resolves.


Constraints:

0 <= functions.length <= 10
1 <= n <= 10

核心思路:工作池 (Worker Pool) 模式
解决并发限制问题,最优雅且高效的方式是使用“工作池”的概念。

你可以把并发限制 n 想象成有 n 个工人。每个工人(异步循环)只要看到任务队列里还有任务,就会去领一个任务来做。做完之后,如果队列里还有任务,他就会接着做下一个,直到所有任务都被做完。

因为 JavaScript 是单线程的,我们可以安全地在所有工人之间共享一个索引变量 i,用来记录下一个该执行哪个函数,而不用担心“线程安全”或“竞态条件”问题。
  1. type F = () => Promise<any>;

  2. function promisePool(functions: F[], n: number): Promise<any> {
  3.     let i = 0; // 共享的任务索引

  4.     // 定义一个“工人”函数,它会不断去领任务执行
  5.     async function worker() {
  6.         // 只要还有未分配的任务
  7.         while (i < functions.length) {
  8.             // 获取当前任务,并且立刻把索引 +1,这样其他工人就不会拿到重复的任务
  9.             const fn = functions[i++];
  10.             // 等待当前任务执行完毕
  11.             await fn();
  12.         }
  13.     }

  14.     // 启动 n 个并发的工人(如果任务总数少于 n,则启动任务总数个工人就够了)
  15.     const workers = [];
  16.     for (let j = 0; j < Math.min(n, functions.length); j++) {
  17.         workers.push(worker());
  18.     }

  19.     // 使用 Promise.all 等待所有的工人都完成他们手头的工作并退出循环
  20.     return Promise.all(workers);
  21. };

  22. /**
  23. * const sleep = (t) => new Promise(res => setTimeout(res, t));
  24. * promisePool([() => sleep(500), () => sleep(400)], 1)
  25. *   .then(console.log) // After 900ms
  26. */
复制代码
代码原理解析:
共享索引 (i):
我们在最外层定义了一个变量 i = 0。它代表着 functions 数组中下一个需要被执行的函数的下标。所有的 worker 函数共享这同一个 i。

工作函数 (worker):
这是一个 async 函数,内部包含一个 while 循环。只要 i < functions.length,说明还有没被执行的任务。
const fn = functions[i++] 这一步非常关键,它在拿到当前任务的同时,把游标往后推了一格。接着用 await fn() 暂停当前这个 worker 的循环。等这个 Promise 解决后,循环继续,再去拿下一个任务。

启动并发工人:
我们通过一个 for 循环,启动了 Math.min(n, functions.length) 个 worker。这里用 Math.min 是为了优化:如果池子限制 n = 5,但实际上我们只有 2 个任务,那么启动 2 个工人就足够了。

等待所有工人完工 (Promise.all):
我们把所有的 worker() 调用的返回值(它们本身也是 Promise)收集到 workers 数组里,然后交给 Promise.all(workers)。当所有的任务都被分配光,且最后一个执行中的任务 resolve 时,所有的 while 循环都会结束,所有的 worker 也会 resolve,最终触发外层的 Promise.all 完成。
回复

使用道具 举报

🔗
 楼主| Myron2017 2026-9-26 10:11:29 | 只看该作者
全局:
2637. Promise Time Limit
Medium
conpanies icon
Companies
Hint
Given an asynchronous function fn and a time t in milliseconds, return a new time limited version of the input function. fn takes arguments provided to the time limited function.

The time limited function should follow these rules:

If the fn completes within the time limit of t milliseconds, the time limited function should resolve with the result.
If the execution of the fn exceeds the time limit, the time limited function should reject with the string "Time Limit Exceeded".


Example 1:

Input:
fn = async (n) => {
  await new Promise(res => setTimeout(res, 100));
  return n * n;
}
inputs = [5]
t = 50
Output: {"rejected":"Time Limit Exceeded","time":50}
Explanation:
const limited = timeLimit(fn, t)
const start = performance.now()
let result;
try {
   const res = await limited(...inputs)
   result = {"resolved": res, "time": Math.floor(performance.now() - start)};
} catch (err) {
   result = {"rejected": err, "time": Math.floor(performance.now() - start)};
}
console.log(result) // Output

The provided function is set to resolve after 100ms. However, the time limit is set to 50ms. It rejects at t=50ms because the time limit was reached.
Example 2:

Input:
fn = async (n) => {
  await new Promise(res => setTimeout(res, 100));
  return n * n;
}
inputs = [5]
t = 150
Output: {"resolved":25,"time":100}
Explanation:
The function resolved 5 * 5 = 25 at t=100ms. The time limit is never reached.
Example 3:

Input:
fn = async (a, b) => {
  await new Promise(res => setTimeout(res, 120));
  return a + b;
}
inputs = [5,10]
t = 150
Output: {"resolved":15,"time":120}
Explanation:
​​​​The function resolved 5 + 10 = 15 at t=120ms. The time limit is never reached.
Example 4:

Input:
fn = async () => {
  throw "Error";
}
inputs = []
t = 1000
Output: {"rejected":"Error","time":0}
Explanation:
The function immediately throws an error.


Constraints:

0 <= inputs.length <= 10
0 <= t <= 1000
fn returns a promise

核心思路一:使用 Promise.race()(最简洁的写法)
在 JavaScript 中处理超时问题,最直接的方法是使用 Promise.race()。它接收一个 Promise 数组,并返回最先改变状态(无论是 resolve 还是 reject)的那个 Promise 的结果。

我们可以让传入的函数 fn 与一个“计时器 Promise”进行赛跑。如果计时器先触发,就抛出超时错误
  1. type Fn = (...params: any[]) => Promise<any>;

  2. function timeLimit(fn: Fn, t: number): Fn {
  3.    
  4.     return async function(...args) {
  5.         // 创建一个在 t 毫秒后自动 reject 的“计时器 Promise”
  6.         const timeoutPromise = new Promise((_, reject) => {
  7.             setTimeout(() => {
  8.                 reject("Time Limit Exceeded");
  9.             }, t);
  10.         });

  11.         // 让原函数和计时器赛跑
  12.         return Promise.race([fn(...args), timeoutPromise]);
  13.     }
  14. };

  15. /**
  16. * const limited = timeLimit((t) => new Promise(res => setTimeout(res, t)), 100);
  17. * limited(150).catch(console.log) // "Time Limit Exceeded" at t=100ms
  18. */
复制代码
核心思路二:手动清除定时器(更严谨,推荐在生产环境使用)
虽然方法一在 LeetCode 上能完美通过,但在真实的生产环境中有一个小瑕疵:如果 fn 在 10 毫秒内就飞速执行完毕了,那个设置了比如 1000 毫秒的 setTimeout 依然会在后台默默倒计时,这不仅浪费内存,还可能导致 Node.js 进程无法及时退出。

更严谨的做法是:如果函数提前执行完了,我们应该手动把定时器清理掉(clearTimeout)。
  1. type Fn = (...params: any[]) => Promise<any>;

  2. function timeLimit(fn: Fn, t: number): Fn {
  3.     return async function(...args) {
  4.         return new Promise((resolve, reject) => {
  5.             // 1. 开启一个倒计时定时器
  6.             const timer = setTimeout(() => {
  7.                 reject("Time Limit Exceeded");
  8.             }, t);

  9.             // 2. 同时开始执行原函数
  10.             fn(...args)
  11.                 .then((result) => {
  12.                     resolve(result); // 如果在限定时间内执行成功,则 resolve 结果
  13.                 })
  14.                 .catch((error) => {
  15.                     reject(error);   // 如果函数本身报错了,则透传错误
  16.                 })
  17.                 .finally(() => {
  18.                     // 3. 无论原函数是成功还是失败,只要它执行完了,就立刻清理掉定时器
  19.                     clearTimeout(timer);
  20.                 });
  21.         });
  22.     }
  23. };
复制代码
回复

使用道具 举报

🔗
 楼主| Myron2017 2026-9-26 10:16:51 | 只看该作者
全局:
2630. Memoize II
Hard
conpanies icon
Companies
Hint
Given a function fn, return a memoized version of that function.

A memoized function is a function that will never be called twice with the same inputs. Instead it will return a cached value.

fn can be any function and there are no constraints on what type of values it accepts. Inputs are considered identical if they are === to each other.



Example 1:

Input:
getInputs = () => [[2,2],[2,2],[1,2]]
fn = function (a, b) { return a + b; }
Output: [{"val":4,"calls":1},{"val":4,"calls":1},{"val":3,"calls":2}]
Explanation:
const inputs = getInputs();
const memoized = memoize(fn);
for (const arr of inputs) {
  memoized(...arr);
}

For the inputs of (2, 2): 2 + 2 = 4, and it required a call to fn().
For the inputs of (2, 2): 2 + 2 = 4, but those inputs were seen before so no call to fn() was required.
For the inputs of (1, 2): 1 + 2 = 3, and it required another call to fn() for a total of 2.
Example 2:

Input:
getInputs = () => [[{},{}],[{},{}],[{},{}]]
fn = function (a, b) { return ({...a, ...b}); }
Output: [{"val":{},"calls":1},{"val":{},"calls":2},{"val":{},"calls":3}]
Explanation:
Merging two empty objects will always result in an empty object. It may seem like there should only be 1 call to fn() because of cache-hits, however none of those objects are === to each other.
Example 3:

Input:
getInputs = () => { const o = {}; return [[o,o],[o,o],[o,o]]; }
fn = function (a, b) { return ({...a, ...b}); }
Output: [{"val":{},"calls":1},{"val":{},"calls":1},{"val":{},"calls":1}]
Explanation:
Merging two empty objects will always result in an empty object. The 2nd and 3rd third function calls result in a cache-hit. This is because every object passed in is identical.


Constraints:

1 <= inputs.length <= 105
0 <= inputs.flat().length <= 105
inputs[i][j] != NaN


这道题目要求我们实现一个高级版本的记忆化(Memoize)函数。

在普通的算法题中,我们通常用 JSON.stringify(args) 把参数转换成字符串,然后存到普通的对象 {} 中作为缓存键。但在这道题里,这是行不通的,原因在于:

严格相等 (===) 的要求:JavaScript 中的对象(Object)、数组(Array)等引用类型,只有在内存地址相同时才满足 ===。

比如:{} 和 {} 是不同的对象。如果用 JSON.stringify,它们都会变成 "{}",导致错误的缓存命中(如题目中的 Example 2)。

参数个数不确定:函数可以接收任意数量的参数。

核心解题思路:利用 Map 构建参数字典树 (Trie)
JavaScript 中的 Map 允许我们将任意类型的值(包括对象本身)作为 Key,并且它内部就是使用类似于 === 的算法(SameValueZero)来匹配键的。这完美符合题目的要求。

因为我们有多个参数,我们可以用一棵字典树(前缀树)的思想,每一层嵌套一个 Map 来代表一个参数。

举个例子,假设参数是 (a, b, c):

我们创建一个全局的根 Map。

拿到参数 a,在根 Map 里找,没有就创建一个新的 Map,然后进入下一层。

拿到参数 b,在第二层的 Map 里找,没有再创一个,进入下一层。

拿到参数 c,在第三层的 Map 里找。

遍历完参数后,在当前的终点 Map 里,用一个独一无二的标记(例如 Symbol)将函数结果存下来。

这样,只有当传入完全相同的对象引用时,才会顺着同一条 Map 路径走到同一个终点,实现 O(1) 时间复杂度的精准缓存查找。
  1. type Fn = (...params: any) => any

  2. function memoize(fn: Fn): Fn {
  3.    
  4.     // 根 Map,用来存储参数字典树
  5.     const cacheTrie = new Map();
  6.     // 使用 Symbol 创建一个独一无二的键,用来在 Map 节点上存储最终的计算结果。
  7.     // 这样可以防止与普通参数的值(比如用户碰巧传入字符串 'result')发生冲突。
  8.     const RESULT_KEY = Symbol("result");

  9.     return function(...args: any[]) {
  10.         let currentMap = cacheTrie;
  11.         
  12.         // 遍历所有传入的参数,在字典树中逐层往下走
  13.         for (const arg of args) {
  14.             // 如果当前层级没有这个参数对应的分支,就新建一个 Map 作为下一层
  15.             if (!currentMap.has(arg)) {
  16.                 currentMap.set(arg, new Map());
  17.             }
  18.             // 走到下一层
  19.             currentMap = currentMap.get(arg);
  20.         }
  21.         
  22.         // 走完了所有参数,到了树的叶子节点,检查是否已经计算过
  23.         if (currentMap.has(RESULT_KEY)) {
  24.             // 缓存命中,直接返回之前存好的结果
  25.             return currentMap.get(RESULT_KEY);
  26.         }
  27.         
  28.         // 缓存未命中,调用原函数计算结果
  29.         const result = fn(...args);
  30.         
  31.         // 将结果存入当前节点
  32.         currentMap.set(RESULT_KEY, result);
  33.         
  34.         return result;
  35.     }
  36. }


  37. /**
  38. * let callCount = 0;
  39. * const memoizedFn = memoize(function (a, b) {
  40. *         callCount += 1;
  41. *   return a + b;
  42. * })
  43. * memoizedFn(2, 3) // 5
  44. * memoizedFn(2, 3) // 5
  45. * console.log(callCount) // 1
  46. */
复制代码
复杂度分析

时间复杂度:$O(N)$,其中 $N$ 是传入参数的数量。对于每次函数调用,我们只需要遍历参数数组,在 Map 中进行 $N$ 次 O(1) 的查找或插入操作。这非常高效,完全能扛住 10^5 的数据量约束。

空间复杂度:最坏情况下是 $O(K \times N)$,其中 $K$ 是独立且不重复的调用次数,$N$ 是参数个数。由于缓存需要保存所有不重复调用的层级路径,会消耗一定的内存空间来建立 Map。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册账号
隐私提醒:
  • ☑ 禁止发布广告,拉群,贴个人联系方式:找人请去🔗同学同事飞友,拉群请去🔗拉群结伴,广告请去🔗跳蚤市场,和 🔗租房广告|找室友
  • ☑ 论坛内容在发帖 30 分钟内可以编辑,过后则不能删帖。为防止被骚扰甚至人肉,不要公开留微信等联系方式,如有需求请以论坛私信方式发送。
  • ☑ 干货版块可免费使用 🔗超级匿名:面经(美国面经、中国面经、数科面经、PM面经),抖包袱(美国、中国)和录取汇报、定位选校版
  • ☑ 查阅全站 🔗各种匿名方法

本版积分规则

>
快速回复 返回顶部 返回列表