前 30 个 Javascript 面试热身练习第 3 部分对指导或培训感兴趣吗?

2025-06-10

30 个最佳 JavaScript 面试热身练习(第 3 部分)

对指导或培训感兴趣吗?

这是本系列的第三部分,可惜也是最后一部分。在本部分中,我们将学习一些 JavaScript 面试中需要注意的额外但有趣的算法问题。

我们开始吧。

问题🤔

  • 1. 编写一个函数来检查一个字符串是否是另一个字符串的字母重排。

一个字符串可以有多种排列,但本质上该字符串的所有字符都具有相同的频率表。因此,要检查一个字符串是否是另一个字符串的字母重排,我们只需比较它们的频率表(如果相同)。

我们之前已经了解过如何计算频率表,因此我们只需使其适用于两个字符串。

代码要点如下:

function areAnagrams(first, second) { // Find their freq tables const firstFreq = computeFrequency(first); const secondFreq = computeFrequency(second); // Compare them one by one if (areMapsEqual(firstFreq, secondFreq)) { return true; } return false; } function areMapsEqual(a, b) { // First check their sizes if (a.size !== b.size) { return false; } for (let [key, val] of a) { // Then check if each one of the key exists in the other and are the same let other = b.get(key); if (other !== val || other === undefined) { return false; } } return true; } function computeFrequency(s) { const freqTable = new Map(); for (ch of s) { if (!freqTable.has(ch)) { freqTable.set(ch, 1); } else { freqTable.set(ch, freqTable.get(ch) + 1); } } return freqTable; } console.log(areAnagrams("abc", "cab")); console.log(areAnagrams("tyrannos", "polemos")); console.log(areAnagrams("polemos", "solepom"));
  • 2. 编写一个函数,对函数的参数进行柯里化。例如,它不会同时接受所有参数,而是返回一个新函数,该函数接受第一个参数并返回,然后接受第二个参数并返回,依此类推。

您需要这样调用它:

function mul(a, b, c) {
  return a * b * c;
}

let curriedMul = curry(mul);
curriedMul(1)(2)(3) // 6
curriedMul(1)(5, 3) // 15
curriedMul(1, 3, 3) // 9
Enter fullscreen mode Exit fullscreen mode

我们需要一个函数curry来接受另一个函数作为参数。然后,我们需要一种方法来检查每次调用时传递的参数是否更少,如果是,我们就curry用这些参数再次调用该函数。否则,当我们拥有所有参数时,我们就用这些参数调用原始函数。

代码要点如下:

function curry(fn) { // Return a function that captures the arguments passed return function handler(...args) { // If we passed all the arguments we call the function if (args.length >= fn.length) { return fn(...args); } else { // Otherwise we return another function capturing the rest of the arguments // and we call handler again aggregating the current arguments with the rest of the // arguments return function(...argsLeft) { return handler(...args.concat(argsLeft)); } } }; } function mul(a, b, c) { return a * b * c; } let curriedMul = curry(mul); console.log(curriedMul(1)(2)(3)) // 6 console.log(curriedMul(1, 5, 3)) // 15 console.log(curriedMul(1)(3, 3)) // 9 console.log(curriedMul(1)) // function
  • 3. 编写一个函数,给定两个排序列表,返回一个合并并排序所有元素的列表。

这是合并排序算法中我们熟悉的合并部分。你应该非常熟悉它,并且能够解释它的工作原理。

思路是迭代两个数组,并为每次迭代 i 和 j 保留一个索引。我们比较 arr1[i] 和 arr2[j],并将最小元素放入结果表中。然后,我们增加包含最小元素的数组的索引。

最后,如果我们已经完成了对一个数组的迭代,但另一个数组中仍有更多元素,我们需要确保移动其余元素。

代码要点如下:

function mergeTwoSortedArrays(arr1, arr2) { // reserve the result table const result = Array(arr1.length + arr2.length); // Initialized the indexes let i = 0; let j = 0; let k = 0; // we iterate over the two arrays. This will stop when one of them is fully iterated while (i < arr1.length && j < arr2.length) { if (arr1[i] <= arr2[j]) { result[k] = arr1[i]; i += 1; } else { result[k] = arr2[j]; j += 1; } k += 1; } // We have finished iterating arr2. Now we need to move the rest of arr1 into the result list while ( i < arr1.length ) { result[k] = arr1[i]; i += 1; k += 1; } // We have finished iterating arr1. Now we need to move the rest of arr2 into the result list while ( j < arr2.length ) { result[k] = arr2[j]; j += 1; k += 1; } return result; } console.log(mergeTwoSortedArrays([1, 3, 11], [2, 4, 6, 8])); console.log(mergeTwoSortedArrays([1, 2, 3, 15], [5, 7 ,9 , 11])); console.log(mergeTwoSortedArrays([1, 3, 5, 6], [])); console.log(mergeTwoSortedArrays([10, 14, 16], [11, 15]));
  • 4.编写一个函数,接受两个日期并返回相差的天数。

当你创建两个 Date 对象并将它们相减时,结果就是它们之间的毫秒数。例如:

let d = new Date()
// Mon Feb 03 2020 07:23:09 GMT+0000 (Greenwich Mean Time)
let m = new Date()
// Mon Feb 03 2020 07:23:18 GMT+0000 (Greenwich Mean Time)

console.log(d -  m) // -8406
console.log(m - d) // 8406

Enter fullscreen mode Exit fullscreen mode

因此,如果我们可以将毫秒转换为天数,我们就可以返回天数的差值。

代码要点如下:

function daysDifference(first, second) { const diff = Math.abs(second - first); const result = Math.floor(diff / (1000 * 60 * 60 * 24)); return result } console.log(daysDifference(new Date('2020-01-01'), new Date('2020-01-02')))
  • 5. 编写一个函数,接受一个字符串并删除该字符串中出现超过一次的任何字符。

我们可以使用集合或映射来统计某个字符串字符的现有出现次数。因此,我们只需迭代所有字符,如果还没找到最后一个字符,就将其放入列表中。然后,我们使用它join来返回结果。

代码要点如下:

function removeDuplicateChars(s) { const result = []; let seen = new Set(); for (let c of s) { if (!seen.has(c)) { seen.add(c); result.push(c); } } return result.join(''); } console.log(removeDuplicateChars('aba')); console.log(removeDuplicateChars('tyasua'));
  • 6. 编写一个函数,接受一个对象并返回其内部对象和函数的深度冻结副本。

这里我们需要做以下事情:

  1. 使用 冻结对象Object.freeze
  2. 对该对象的任何函数或对象属性递归调用相同的冻结函数。

代码要点如下:

function deepFreeze(obj) { // We first freeze the base object here Object.freeze(obj); // Then we recursively iterate over its own properties and call deepFreeze on them const ownProperties = Object.getOwnPropertyNames(obj); for (prop of ownProperties) { if ( obj.hasOwnProperty(prop) && obj[prop] !== null && (typeof obj[prop] === "object" || typeof obj[prop] === "function") ) { deepFreeze(obj[prop]); } } } let o = { item: 1, cb: { cb: function() { return 2; } } }; deepFreeze(o); o.item = 2; o.cb.cb = function() { return 3; }; console.log(o.cb.cb()); console.log(o.item);
  • 7. 编写一个函数,给定一个列表和该列表中的索引位置,将返回一个列表,该列表的所有元素都小于其左侧索引的元素,并且所有元素都大于其右侧索引。

这是快速排序算法的划分方法。
你应该非常熟悉它,并且能够解释它是如何工作的。

思路是使用索引位置作为枢轴点。然后有两个迭代器,一个从头开始,另一个从尾开始。首先使用左迭代器查找小于枢轴点的元素。然后使用右迭代器查找大于枢轴点的元素。

如果两者都找到,则交换它们在数组中的位置。当两个迭代器相互交叉时,循环中断。

代码要点如下:

function partition(arr, index) { // Pivot point out of bounds if (index < 0 || index > arr.length) { return; } let pivot = arr[index]; // Left index from the beginning let i = 0; // Right index from the end let j = arr.length - 1; // As long as they do not cross each other while (i <= j) { while (arr[i] < pivot) { i += 1; // move i left until we find an item less that pivot } while (arr[j] > pivot) { j -= 1; // move j right until we find an item less that pivot } if (i <= j) { swap(arr, i, j); i += 1; j -= 1; } } return i; } function swap(arr, i, j) { let temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } let arr = [1, 5, 11, 9, 4, 22, 7]; partition(arr, 3); console.info(arr);
  • 8. 编写一个将十进制数转换为二进制的函数。

其背后的原理是,每次我们取代表最后一位的数字的模。例如:

2 --> 10 == 2 % 2
3 --> 11 == 3 % 2
5 --> 101 == 5  % 2
Enter fullscreen mode Exit fullscreen mode

这样我们就能计算出最后一位了。然后,为了计算倒数第二位,我们需要对 num / 2 取底,以此类推。更多详情,请阅读此 wiki 。

代码要点如下:

function decToBin(num) { const result = []; let i = 0; while(num > 0) { // We grab the result of the mudulo num 2 which corresponds to the last bit of the result binary number result[i] = num % 2; i += 1; // We divide by 2 so that we can get the last but one bit on the next run num = Math.floor(num / 2); } // The result has the bits in reverse order. So we reverse first before returning the result. return result.reverse().join(''); } console.log(decToBin(10)); console.log(decToBin(134)); console.log(decToBin(999));
  • 9. 编写一个将二进制数转换为十进制的函数。

这里我们有一个二进制字符串,想把它转换成整数。首先,我们从字符串末尾开始遍历。每次找到一个 a 时,1我们就用这个索引来获取 2 乘以 i 的值。然后,我们将这个值加到结果中。例如,34 的二进制表示是 100010,因此:

1 * 2 ^ 8 + 1 * 2 ^ 1 = 32 + 2 = 34
Enter fullscreen mode Exit fullscreen mode

代码要点如下:

function binToDec(num) { let result = 0; // Start from the last bit digit and down to the first for (let i = num.length-1; i >= 0; i -= 1) { // Calculate the current value of exponent let exp = num.length - 1 - i; if (num[i] === '1') { result += Math.pow(2, exp); } } return result; } console.log(binToDec("101010")); console.log(binToDec("100010")); console.log(binToDec("111110101"));
  • 10. 编写一个函数,给定一个字符串,返回其排列列表。

例如:

in: "ab" out: ["ab", "ba"]
Enter fullscreen mode Exit fullscreen mode

这是最棘手的问题。其思路是使用递归从字符串中构造一个排列。然后我们回溯生成下一个排列,依此类推。

举个简单的例子,两个字符:“ab”

首先,我们将“a”固定到位,然后对剩余的字符串“b”调用 permutate 函数。因为“b”是最后一个字符,所以我们得到了序列“ab”,因此将其添加到结果中。

然后,我们将“b”固定在前面,并对剩余的字符串“a”调用 permutate 函数。因为“a”是最后一个字符,所以我们得到了序列“ba”,所以将它添加到结果中。

类似地,我们可以对任何长度为 n 的字符串执行此操作。

代码要点如下:

function permutate(str) { let result = [] // Here we call a recursive function passing all the required parameters permutateRecursive(str.split(''), 0, str.length-1, result); return result; } function permutateRecursive(str, start, end, result) { if (start === end) { // We have completed a unique permutation string. Push it to the result string return result.push(str.join('')); } // else for each char in the sequence starting from start and ending to end for (let i = start; i <= end; i += 1) { // We swap the current i with the end swap(str, i, start) // We call the same function again but we advance the start ahead by one permutateRecursive(str, start+1, end, result); // We restore the swap we did earlier with the current i and the end swap(str, start, i) } } function swap(arr, i, j) { let temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } console.info(permutate("abc")); console.info(permutate("artfg"));

还没结束

本文列出了一些热身练习,并附有详细的答案。我们希望您在下次重要的面试前能有丰富的问题​​作为参考。敬请期待未来更多关于编程面试的文章。

对指导或培训感兴趣吗?

请通过www.techway.io联系我以获取更多信息。

鏂囩珷鏉ユ簮锛�https://dev.to/theodesp/top-30-javascript-interview-warmup-exercises-part-3-55nm
PREV
热门 HTML 面试问题及详细解答🌺🌴😃🤽🏨🏖️ 第二部分
NEXT
2024 年构建 AI 代理的 5 大框架(另加 1 个额外内容)