前端模拟面试:7个JavaScript数组进阶面试题

时间:2024-11-13 08:28:09

8db2d57fe60bf285ee8f7c78b489db34.jpeg

你坐在面试室里,对面的面试官微笑着,轻敲着桌面问道:“那我们来聊聊 JavaScript 吧。数组操作你有多熟悉?”你意识到,眼前这个问题看似简单,但面试官可能在考察你对 JavaScript 基础知识的深度掌握和灵活运用。你深吸一口气,准备好好展示一番,从最常见的查找最大值问题入手,一步步展示自己的思路。

你知道,这不仅是一次技术能力的测试,更是一次展示你对问题解决思维的机会。于是,你信心十足地开始了……

面试题 1:如何找到数组中的最大值?????

场景引入:你坐在面试官对面,面试官提出了一个基础问题:“给定一个数字数组,如何找出其中的最大值?” 你点点头,快速思考了一下,准备向面试官展示你对 JavaScript 内置方法的理解。

方法一:使用 Math.max 和展开运算符

这是最直接的解决方案。你向面试官解释说,可以使用 Math.max,并通过展开运算符将数组元素传递进去。这样不仅语法简洁,而且逻辑也很清晰。

const numbers = [1, 5, 3, 9, 2];
function findLargest(arr) {
  return Math.max(...arr);
}

console.log(findLargest(numbers)); // 输出:9

解析:在这里,Math.max(...arr) 将数组解包成单个参数传入 Math.max,一行代码就能返回最大值。面试官对此表示认可,但也追问了一句:“如果数组很大,这种方法还适用吗?”

你解释说,展开运算符可能会在大数组上有性能问题,因为它会消耗较多的内存。在百万级别的数组上,更推荐使用 for 循环。

方法二:使用 for 循环逐一比较

为了展示自己对算法效率的考量,你提出了使用 for 循环的方式。你解释说,虽然这看起来不像是“最简单”的方法,但它能够处理任何长度的数组,无论多大,都不会受到展开运算符带来的内存限制。

你展示了如下代码:

const numbers = [1, 5, 3, 9, 2];
function findLargest(arr) {
  let max = arr[0];
  for (let i = 1; i < arr.length; i++) {
    if (arr[i] > max) {
      max = arr[i];
    }
  }
  return max;
}

console.log(findLargest(numbers)); // 输出:9

解释:在这个例子中,我们通过 for 循环从头遍历数组,将当前最大值保存在 max 中。当遇到更大的值时,更新 max。这种方法在时间复杂度上是 O(n),而且不会引发内存溢出,是一种更加稳妥的处理方式。

面试题 2:如何找到数组中的第二大值?????

场景引入:面试官接着问道:“如果要找到数组中的第二大值呢?” 你知道这是对算法理解的进一步考察,于是准备向面试官展示几种不同的方法。

方法一:排序后取第二大值

你解释说,最直观的方法就是将数组降序排列,然后取第二个值。

const numbers = [1, 5, 3, 9, 7];
function secondLargest(arr) {
  let sorted = arr.sort((a, b) => b - a);
  return sorted[1];
}

console.log(secondLargest(numbers)); // 输出:7

分析:数组排序后,sorted[1] 就是第二大值。不过排序的时间复杂度为 O(n log n),且会改变原数组,不是最高效的方法。

方法二:遍历找到第二大值

如果希望更高效,可以在一次遍历中找到第二大值。你解释说,使用两个变量 maxsecondMax 来记录最大值和次大值,可以在不排序的情况下得到结果。

const numbers = [1, 5, 3, 9, 7];
function findSecondLargest(arr) {
  let max = -Infinity;
  let secondMax = -Infinity;
  for (let num of arr) {
    if (num > max) {
      secondMax = max;
      max = num;
    } else if (num > secondMax && num < max) {
      secondMax = num;
    }
  }
  return secondMax;
}

console.log(findSecondLargest(numbers)); // 输出:7

解释:这段代码在一次循环中完成,时间复杂度为 O(n)max 记录当前最大值,secondMax 记录次大值。每次遇到新的最大值时,更新这两个变量,保证 secondMax 是最大值之外的最大元素。

面试题 3:如何去除数组中的重复项?????

场景引入:面试官进一步询问,“能写一个函数来去除数组中的重复项吗?” 你知道这个问题在实际开发中很常见,立刻想到了用 Set 去重的方法。

方法:使用 Set 去重

使用 Set 是最快捷的方式,Set 会自动去除重复元素。你向面试官展示了以下代码:

const numbers = [1, 2, 2, 3, 4, 4, 5];
function removeDuplicates(arr) {
  return [...new Set(arr)];
}

console.log(removeDuplicates(numbers)); // 输出:[1, 2, 3, 4, 5]

解析:在这里,Set 自动去重,然后再用展开运算符将 Set 转换回数组。操作简单且性能优越,特别适用于中小规模的数据处理。

面试题 4:如何合并两个有序数组并保持有序?????

场景引入:接下来,面试官问你如何将两个已排序的数组合并并保持顺序。你知道,concatsort 是一种快速实现的方式,适合面试展示。

方法:使用 concatsort

const arr1 = [1, 3, 5];
const arr2 = [2, 4, 6];
function mergeArrays(arr1, arr2) {
  return arr1.concat(arr2).sort((a, b) => a - b);
}

console.log(mergeArrays(arr1, arr2)); // 输出:[1, 2, 3, 4, 5, 6]

解释concat 拼接数组,然后用 sort 进行升序排列。这种方法简单直观,但如果有大量数据时,concat 的内存消耗和 sort 的效率需要注意。

面试题 5:如何旋转数组?????

场景引入:面试官看着你,提出一个稍微有点棘手的问题:“写一个函数,将数组右移 k 次。”你意识到,这要求数组中的每个元素向右“移动”指定的次数。比如,给定 [1, 2, 3, 4, 5],右移 2 次后数组应变成 [4, 5, 1, 2, 3]

这类旋转操作在数据处理和算法应用中十分常见。你脑海中浮现出一个思路:可以通过 sliceconcat 两个方法巧妙地完成旋转。于是,你自信地向面试官展示了这段代码。

方法解析:使用 sliceconcat 拼接旋转后的数组

你写下了代码,解释道:

const arr = [1, 2, 3, 4, 5];
function rotateArray(arr, k) {
  k = k % arr.length; // 防止 k 超出数组长度
  return arr.slice(-k).concat(arr.slice(0, -k));
}

console.log(rotateArray(arr, 2)); // 输出:[4, 5, 1, 2, 3]

代码详解

  1. 处理超长旋转次数:首先,k = k % arr.length 这一行确保旋转次数不会超出数组长度。你解释道,比如当 k = 7 时,这段代码将 k 变为 27 % 5 = 2),这相当于只旋转了 2 次,避免了多余的操作。

  2. 分割数组:接下来,用 slice 方法将数组分成两个部分,分别取出数组的尾部和前面的部分:

  • arr.slice(-k) 用来取出数组最后 k 个元素。比如当 k = 2 时,arr.slice(-2) 会返回 [4, 5]

  • arr.slice(0, -k) 则获取数组的前面部分,不包括最后 k 个元素。对 [1, 2, 3, 4, 5] 使用 arr.slice(0, -2),结果就是 [1, 2, 3]

拼接旋转后的数组:最后,你用 concat 将这两个部分组合起来,把尾部的 [4, 5] 放到 [1, 2, 3] 的前面,完成右移操作。整个代码执行后得到 [4, 5, 1, 2, 3],实现了右移 2 次的效果。

举例说明

你接着向面试官详细说明:

假设 arr = [1, 2, 3, 4, 5]k = 2。我们需要将数组右移 2 次,让 [4, 5] 出现在数组开头。

  1. 使用 slice(-2) 得到 [4, 5]

  2. 使用 slice(0, -2) 得到 [1, 2, 3]

  3. 把这两个部分拼接起来,得到最终的 [4, 5, 1, 2, 3]

关键点总结

  • 循环优化:通过 k % arr.length 确保旋转次数不会超过数组长度,从而优化效率。

  • 分割和拼接:利用 sliceconcat 组合,可以轻松实现数组的旋转效果。

这段代码不仅展示了如何高效地旋转数组,同时也展现了你在面试中对细节的考量和巧妙的思路。面试官满意地点点头,对你的答案非常认可。

面试题 6:如何判断两个数组是否相等?????

场景引入:面试官继续追问,“写个函数来判断两个数组内容是否完全一致。” 你知道,这不仅要内容相同,还要顺序一致。

方法:逐个元素比较

const arr1 = [1, 2, 3];
const arr2 = [1, 2, 3];
function arraysEqual(arr1, arr2) {
  if (arr1.length !== arr2.length) return false;
  
  for (let i = 0; i < arr1.length; i++) {
    if (arr1[i] !== arr2[i]) return false;
  }
  
  return true;
}

console.log(arraysEqual(arr1, arr2)); // 输出:true
console.log(arraysEqual([1, 2, 3], [1, 2, 4])); // 输出:false

解析:逐个元素对比可以确保内容和顺序一致。此方法适用于小型数组,因为时间复杂度为 O(n)

面试题 7:如何找到数组中和为特定值的所有数对?????

场景引入:最后,面试官抛出一道更复杂的问题:“找到数组中所有和为特定值的数对。” 这涉及到去重和优化算法的问题。你准备展示 Set 来解决这类查找问题。

方法:使用 Set 记录和查找补数

const arr = [1, 2, 4, 3, 5, 7, 8, 9];
const target = 10;
function findPairs(arr, target) {
  let result = [];
  let seen = new Set();

  for (let num of arr) {
    let complement = target - num;

    if (seen.has(complement)) {
      result.push([num, complement]);
    }

    seen.add(num);
  }

  return result;
}

console.log(findPairs(arr, target)); // 输出:[[3, 7], [2, 8], [1, 9]]

解析:通过 Set 记录遍历过的数,找到符合条件的数对。利用查找补数的方式,避免重复数对,且比双重循环效率更高。

总结 ????

整个面试过程结束,你与面试官展开了深入的讨论,逐步展示了如何用 JavaScript 操作数组,从最简单的查找最大值到复杂的数对查找,涵盖了去重、排序、旋转等经典操作。你展示了每种方法的优缺点和适用场景,让面试官看到你不仅掌握了基础的技术,还能够针对不同场景灵活选用最优解。

面试官露出欣赏的微笑,对你的细致解答表示了充分的肯定。这场面试不仅展示了你的 JavaScript 基础功底,更展现了你在实际开发中解决问题的敏捷思维。你从面试室中走出来,心里对自己这次表现感到满意。回想起刚才那些考题,你知道,自己在面试中的思路与深度,已经给面试官留下了深刻的印象。

你微微一笑,准备迎接下一个挑战。