(十二)数组 面试题详解(2024)

165 阅读15分钟

关于数组这个数据结构

1、访问元素

要访问数组里特定位置的元素,可以用中括号传递数值位置,得到想知道的值或者赋新的值。 假如我们想输出数组arr里的所有元素,可以通过循环遍历数组,打印元素,如下所示:

for (var i = 0; i < arr.length; i++) {
  console.log(arr[i]);
}

例子:求斐波那契数列的前 20 个数字。已知斐波那契数列中第一个数字是 1, 第二个是 2,从第三项开始,每一项都等于前两项之和:

斐波那契数列为:1 1 2 3 5 8 13 21...(这里题设第一个数为 1,所以代码设计去掉了数列的第一个 1)

    var fibonacci = []; //声明并创建了一个数组
    fibonacci[1] = 1; //按题意赋值
    fibonacci[2] = 2; //按题意赋值
    for (var i = 3; i < 20; i++) {
        fibonacci[i] = fibonacci[i - 1] + fibonacci[i - 2]; //循环赋值
    }
    for (var i = 1; i < fibonacci.length; i++) { //循环遍历
        console.log(fibonacci[i]); //打印每次遍历的元素
    }
    console.log(fibonacci);//输出数组

2、数组中添加元素

注意:在JavaScript中,数组是一个可以修改的对象。如果添加元素,它就会动态增长。在 C 和 Java 等其他语言里,我们要先决定数组的大小,想添加元素就要创建 一个全新的数组,不能简单地往其中添加所需的元素。

初始化一个数组:

var arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];

(1)在数组末位插入元素——使用 push  方法

思想:给数组添加一个元素(比如 10),只要把值赋给数组中最后一个空位上的元素即可

arr[arr.length] = 10;

使用push方法

arr.push(11);

(2)在数组首位插入元素——使用 unshift 方法

思想

  1. 要腾出数组里第一个元素的位置,把所有的元素向右移动一位。
  2. 循环数组中的元素,从最后一位(arr[arr.length])开始,将其对应的前一个元素的值赋给它,依次处理
  3. 最后把想要的值赋给第一个位置(arr[0])上
for (var i = arr.length; i >= 0; i--) {
  arr[i] = arr[i - 1];
}
arr[0] = -1; // -1 为自定义值

使用unshift方法

arr.unshift(-1);

3、数组中删除元素

(1)删除数组末位元素——使用 pop  方法

arr.pop(11)

(2)删除数组首位元素——使用 shift 方法

思想:把数组里所有的元素都左移了一位

for (var i = 0; i < numbers.length; i++) {
  numbers[i] = numbers[i + 1];
}

注意:这样的话,数组的长度仍然是原长度,即数组中还有额外的一个元素(未定义,值是undefined)。只是把数组第一位的值用第二位覆盖了,并没有删除元素。

要确实删除数组的第一个元素,可以用shift方法实现:

arr.shift();

4、在任意位置添加或删除元素——使用 splice 方法

使用splice方法,简单地通过指定位置/索引,就可以删除相应位置和数量的元素:

arr.splice(5, 3); //删除了从数组索引 5 开始的 3 个元素

注意:对于JavaScript数组和对象,我们还可以用delete操作符删除数组中的元素,例如delete numbers[0] 。然而,数组位置 0 的值会变成undefined,也就是说,以上操作等同于numbers[0] = undefined 。因此,我们应该始终使用splicepopshift方法来删除数组元素。

把数字 2、3、4 插入数组里:

var arr = [0, 1, 2, 3, 4, 5, 6];
arr.splice(5, 0, 2, 3, 4);
console.log(arr); //[0, 1, 2, 3, 4, 2, 3, 4, 5, 6]

var arr = [0, 1, 2, 3, 4, 5, 6];
arr.splice(5, 1, 2, 3, 4);
console.log(arr); //[0, 1, 2, 3, 4, 2,3, 4, 6]  注意是删除第5个元素后的元素
  • 第一个参数:要在其后删除或插入元素   的元素的索引值(在该元素后做操作)
  • 第二个参数:删除元素的个数(上面例子里,我们的目的不是删除元素,所以传入 0)。
  • 第三个参数往后:要添加到数组里的值(元素 2、3、4)

5、二维和多维数组

JavaScript只支持一维数组,并不支持矩阵。但是,我们可以用数组套数 组,实现矩阵或任一多维数组。

//day 1
averageTemp[0] = [];
averageTemp[0][0] = 72;
averageTemp[0][1] = 75;
averageTemp[0][2] = 79;
averageTemp[0][3] = 79;
averageTemp[0][4] = 81;
averageTemp[0][5] = 81;
//day 2
averageTemp[1] = [];
averageTemp[1][0] = 81;
averageTemp[1][1] = 79;
averageTemp[1][2] = 75;
averageTemp[1][3] = 75;
averageTemp[1][4] = 73;
averageTemp[1][5] = 72;

(1)迭代二维数组的元素

function printMatrix(myMatrix) {
  for (var i = 0; i < myMatrix.length; i++) {
    for (var j = 0; j < myMatrix[i].length; j++) {
      console.log(myMatrix[i][j]);
    }
  }
}

需要遍历所有的行和列。因此,我们需要使用一个嵌套的for循环来处理,其中变量i为行,变量j为列。

(2)多维数组

for (var i = 0; i < matrix3x3x3.length; i++) {
  for (var j = 0; j < matrix3x3x3[i].length; j++) {
    for (var z = 0; z < matrix3x3x3[i][j].length; z++) {
      console.log(matrix3x3x3[i][j][z]);
    }
  }
}

6、数组方法一览

一般方法:

方法名描述
concat连接 2 个或更多数组,并返回结果
every对数组中的每一项运行给定函数,如果该函数对每一项都返回 true,则返回 true(迭代到结束或出现 false 为止)
filter对数组中的每一项运行给定函数,返回该函数会返回 true 的项组成的数组
forEach对数组中的每一项运行给定函数。这个方法没有返回值
join将所有的数组元素连接成一个字符串
indexOf返回第一个与给定参数相等的数组元素的索引,没有找到则返回-1
lastIndexOf返回在数组中搜索到的与给定参数相等的元素的索引里最大的值
map对数组中的每一项运行给定函数,返回每次函数调用的结果组成的数组
reverse颠倒数组中元素的顺序
slice传入索引值,将数组里对应索引范围内的元素作为新数组返回
some对数组中的每一项运行给定函数,如果任一项返回 true,则返回 true(迭代到结束或出现 true 为止)
sort按照字母顺序对数组排序,支持传入指定排序方法的函数作为参数
toString将数组作为字符串返回
valueOf和 toString 类似,将数组作为字符串返回

一个迭代器函数

​ 先看个例子

假如有 一个数组,它值是从 1 到 15,如果数组里的元素可以被 2 整除(偶数),函数就返回 true,否则返 回 false:

const isEven = function (x) {
  // 如果x是2的倍数,就返回true
  console.log(x);
  return x % 2 == 0 ? true : false; //这里直接也可用 return (x % 2 == 0)
};
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

ES6 新增方法

@@iterator返回一个包含数组键值对的迭代器对象,可以通过同步调用得到数组元素的键值对
copyWithin复制数组中一系列元素到同一数组指定的起始位置
entries返回包含数组所有键值对的@@iterator
includes如果数组中存在某个元素则返回 true,否则返回 false。ES7 新增
find根据回调函数给定的条件从数组中查找元素,如果找到则返回该元素
findIndex根据回调函数给定的条件从数组中查找元素,如果找到则返回该元素在数组中的索引
fill用静态值填充数组
from根据已有数组创建一个新数组
keys返回包含数组所有索引的@@iterator
of根据传入的参数创建一个新数组
values返回包含数组中所有值的@@iterator
some
sort
toString
valueOf

几个 ES6 方法的使用

​from

Array.from 方法根据已有的数组创建一个新数组。比如,要复制 numbers 数组,可以这样做:

let numbers2 = Array.from(numbers);

还可以传入一个用来过滤值的函数,例子如下:

let evens = Array.from(numbers, (x) => x % 2 == 0);

上面的代码会创建一个 evens 数组,其中只包含 numbers 数组中的偶数。

fill

fill 方法用静态值填充数组。以下面的代码为例:

let numbersCopy = Array.of(1, 2, 3, 4, 5, 6);

numbersCopy 数组的 length 是 6,也就是有 6 个位置。再看下面的代码:

numbersCopy.fill(0);

numbersCopy 数组所有位置的值都会变成 0([0, 0, 0, 0, 0, 0])。

我们还可以指定开始填充的索引,如下:

numbersCopy.fill(2, 1);

上面的例子里,数组中从 1 开始的所有位置,值都是 2([0, 2, 2, 2, 2, 2])。

同样,也可以指定结束填充的索引:

numbersCopy.fill(1, 3, 5);

上面的例子里,我们会把 1 填充到数组索引 3 到 5 的位置(不包括 5),得到的数组为[0, 2, 2, 1, 1, 2]。

创建数组并初始化值的时候,fill 方法非常好用,就像下面这样:

let ones = Array(6).fill(1);

上面的代码创建了一个长度为 6,所有的值都是 1 的数组([1, 1, 1, 1, 1, 1])。

关于数组的元素简单排序

arr.reverse():反序输出数组

关于  sort 方法,需要注意

sort 方法在对数组做排序时,把元素默认成字符串进行相互比较。

let arr = [1, 2, 3, 4, 5, 10, 11];
console.log(arr.sort()); //[1, 10, 11, 2, 3, 4, 5]

解决办法:传入自己写的比较函数

let arr = [1, 2, 3, 4, 5, 10, 11];
arr.sort(function (a, b) {
  return a - b;
});
console.log(arr); //[1, 2, 3, 4, 5, 10, 11]

思想解释:sort 方法接受一个函数作为参数,然后 sort 会用它排序数组

以上方法相当于如下过程:

function compare(a, b) {
  if (a < b) {
    return -1;
  }
  if (a > b) {
    return 1;
  }
  // a必须等于b
  return 0;
}
arr.sort(compare);

自定义排序

可以对任何对象类型的数组排序,也可以创建比较函数来比较元素

例子:对象 Person 有名字和年龄属性,我们希望根据年龄排序,就可以这么写:

let friends = [
  { name: "John", age: 30 },
  { name: "Ana", age: 20 },
  { name: "Chris", age: 25 },
];
function comparePerson(a, b) {
  if (a.age < b.age) {
    return -1;
  }
  if (a.age > b.age) {
    return 1;
  }
  return 0;
}
console.log(friends.sort(comparePerson));

字符串排序

思考:如下代码输出的是?

let names = ["Ana", "ana", "john", "John"];
console.log(names.sort());
//["Ana", "John", "ana", "john"]

JavaScript 在做字符比较的时候,是根据字符对应的 ASCII 值来比较的。例如,A、J、a、j 对应的 ASCII 值分别是 65、75、97、106。 ​ ​

数组扁平化

问题抛出:对于[1, [1,2], [1,2,3]这样多层嵌套的数组,我们如何将其扁平化为[1, 1, 2, 1, 2, 3]这样的一维数组呢(数组降维):

1、ES6 的 flat()

先来简单看一下关于它的使用:

const arr = [1, [1, 2], [1, 2, 3]];
arr.flat(Infinity); // [1, 1, 2, 1, 2, 3]

note: MDN:flat()  方法会按照一个可指定的深度(传入的参数)递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回

Array.prototype.flat() 特性总结:

  • 用于将嵌套的数组扁平化,变成一维的数组。该方法返回一个新数组,对原数据没有影响。
  • 不传参数时,默认扁平化一层,可以传入一个整数,表示想要扁平化的层数。
  • 传入  <=0  的整数将返回原数组,不扁平化
  • Infinity  关键字作为参数时,无论多少层嵌套,都会转为一维数组
  • 如果原数组有空位,Array.prototype.flat()  会跳过空位。

方法虽好,但是使用这个方法前要先考虑一下兼容性问题哦!!!

我们来实现一个 flat吧!!!

note: MDN:reduce()  方法对数组中的每个元素执行一个由提供的 reducer 函数(升序执行),将其结果汇总为单个返回值

// 传入两个参数:数组 arr 扁平化层数 depth(默认是一层)
function flat(arr, depth = 1) {
  return depth > 0 // 层数大于 0 才进行扁平化
    ? arr.reduce((acc, cur) => {
        // acc:reducer 函数的返回值(累计器),cur:当前值(arr 中遍历到的当前元素值)
        if (Array.isArray(cur)) {
          // 当前值是 数组
          return [...acc, ...flat(cur, depth - 1)]; // 递归执行 flat 函数,插入返回数组中 累计器的值后面
        }
        return [...acc, cur]; // 当前值不是数组,直接插入返回数组中 累计器后面
      }, [])
    : arr; // 扁平化层数小于等于 0,直接返回原数组 arr
}

2、序列化后正则

const arr = [1, [1, 2], [1, 2, 3]];
const str = `[${JSON.stringify(arr).replace(/(\[|\])/g, "")}]`;
JSON.parse(str); // [1, 1, 2, 1, 2, 3]

3、递归

对于树状结构的数据,最直接的处理方式就是递归

const arr = [1, [1, 2], [1, 2, 3]];
function flat(arr) {
  let result = [];
  for (const item of arr) {
    item instanceof Array
      ? (result = result.concat(flat(item)))
      : result.push(item);
  }
  return result;
}

flat(arr); // [1, 1, 2, 1, 2, 3]

4、reduce()递归

const arr = [1, [1, 2], [1, 2, 3]];
function flat(arr) {
  return arr.reduce((acc, cur) => {
    return acc.concat(cur instanceof Array ? flat(cur) : cur);
  }, []);
}

// 或者
function flat(arr) {
  return Array.isArray(arr)
    ? arr.reduce((acc, cur) => [...acc, ...flattenDeep(cur)], [])
    : [arr];
}

flat(arr); // [1, 1, 2, 1, 2, 3]

5、迭代+展开运算符

// 每次 while 都会合并一层的元素,这里第一次合并结果为[1, 1, 2, 1, 2, 3, [4,4,4]]
// 然后 arr.some 判定数组中是否存在数组,因为存在[4,4,4],继续进入第二次循环进行合并
let arr = [1, [1, 2], [1, 2, 3, [4, 4, 4]]];
while (arr.some(Array.isArray)) {
  arr = [].concat(...arr);
}

console.log(arr); // [1, 1, 2, 1, 2, 3, 4, 4, 4]

数组去重

​ 首先全局定义一个数组,让我们开始由复杂到简单的方法探索一下吧!!!

var arr1 = [11, 11, 1, 1, 2, 2, "huo", "huo"];

1、双重 for 循环+新数组

思想:建立一个新数组,并放入原数组的第一个元素。将原数组元素依次与新数组元素比较,元素不同就放入新数组。最后返回新数组,达到去重效果。

function darray2(arr) {
  let newArr = [arr[0]];
  for (let i = 0; i < arr.length; i++) {
    for (let j = 0; j < newArr.length; j++) {
      var repeat = false; //这里一开始我用了 let 声明,会报错
      //因为后面的 if 判断里 repeat 取不到 let 声明的块级作用域
      if (arr[i] === newArr[j]) {
        repeat = true;
        break;
      }
    }
    if (!repeat) {
      newArr.push(arr[i]);
    }
  }
  return newArr;
}
console.log(darray2(arr1)); // [11, 1, 2, "huo"]

优化一下

for (let i = 0; i < arr.length; i++) {
  let repeat = false; // 放到这里就行了,let的块级作用域包含后面的if
  for (let j = 0; j < newArr.length; j++) {
    if (arr[i] === newArr[j]) {
      // var repeat = false;
      repeat = true;
      break;
    }
  }
  if (!repeat) {
    newArr.push(arr[i]);
  }
}

2、双重 for 循环 + splice(第一种方法的简化)

思想:双层循环,外层循环元素,内层循环比较元素值。值相同则删除。

function darray1(arr) {
  for (let i = 0; i < arr.length; i++) {
    for (let j = i + 1; j < arr.length; j++) {
      if (arr[i] === arr[j]) {
        arr.splice(j, 1);
        j--;
      }
    }
  }
  return arr;
}
console.log(darray1(arr1)); // [11, 1, 2, "huo"]

3、sort 排序+相邻元素比较

思想:使用 sort()方法将原数组排序。创建新数组,第一个元素为原数组第一个元素。原数组相邻元素进行比较,不同则放入新数组。最后返回新数组。

function darray3(arr) {
  arr = arr.sort();
  let newArr = [arr[0]];
  for (let i = 1; i < arr.length; i++) {
    if (arr[i] !== arr[i - 1]) {
      newArr.push[arr[i]];
    }
  }
  return newArr;
}
console.log(darray3(arr1)); // [11, 1, 2, "huo"]

4、indexOf 方法+遍历原数组元素

思想:创建新的空数组。遍历原数组,使用 indexOf  方法(查找某个元素的位置,如果不存在就返回-1)依次遍历原数组元素,判断新数组中是否含有该元素,有则放入新数组。最后返回新数组。

function darray4(arr) {
  let newArr = [];
  for (let i = 0; i < arr.length; i++) {
    if (newArr.indexOf(arr[i]) === -1) {
      newArr.push(arr[i]);
    }
  }
  return newArr;
}
console.log(darray4(arr1)); // [11, 1, 2, "huo"]

5、includes 方法+遍历原数组元素

思想:创建新的空数组。遍历原数组,使用 includes 方法(判断是否包含某一元素,包含返回 true,不包含返回 false)依次遍历原数组元素,判断新数组中是否含有该元素,有则放入新数组。最后返回新数组。

function darray5(arr) {
  let newArr = [];
  for (let i = 0; i < arr.length; i++) {
    if (!newArr.includes(arr[i])) {
      newArr.push(arr[i]);
    }
  }
  return newArr;
}
console.log(darray5(arr1)); // [11, 1, 2, "huo"]

6、对象特性(不建议使用)

思想:利用对象的属性不能相同的特点进行去重

function darray6(arr) {
  let newArr = [];
  let obj = {};
  for (let i = 0; i < arr.length; i++) {
    if (!obj[arr[i]]) {
      newArr.push(arr[i]);
      obj[arr[i]] = 1;
    } else {
      obj[arr[i]]++;
    }
  }
  return newArr;
}
console.log(darray6(arr1)); // [11, 1, 2, "huo"]

7、filter +includes

思想:使用过滤器,过滤重复的元素,返回新数组

function darray7(arr) {
  let newArr = [];
  newArr = arr.filter(function (item) {
    return newArr.includes(item) ? "" : newArr.push(item);
  });
  return newArr;
}
console.log(darray7(arr1)); // [11, 1, 2, "huo"]

8、filter + hasOwnProperty

思想:利用 hasOwnProperty 判断是否存在对象属性

function darray8(arr) {
  let obj = {};
  return arr.filter(function (item, index, arr) {
    return obj.hasOwnProperty(typeof item + item)
      ? false
      : (obj[typeof item + item] = true);
  });
}
console.log(darray8(arr1)); // [11, 1, 2, "huo"]

9、递归

function darray9(arr) {
  let array = arr;
  let len = array.length;

  array.sort(function (a, b) {
    //排序后更加方便去重
    return a - b;
  });

  function loop(index) {
    if (index >= 1) {
      if (array[index] === array[index - 1]) {
        array.splice(index, 1);
      }
      loop(index - 1); //递归loop,然后数组去重
    }
  }
  loop(len - 1);
  return array;
}
console.log(darray9(arr1)); // [1, 2, 11, "huo"]

10、利用 Map 数据结构

function darray10(arr) {
  let map = new Map();
  let array = new Array(); // 数组用于返回结果
  for (let i = 0; i < arr.length; i++) {
    if (map.has(arr[i])) {
      // 如果有该 key 值
      map.set(arr[i], true);
    } else {
      map.set(arr[i], false); // 如果没有该 key 值
      array.push(arr[i]);
    }
  }
  return array;
}
console.log(darray10(arr1)); // [11, 1, 2, "huo"]

11、利用 reduce+includes

function darray11(arr) {
  return arr.reduce(
    (prev, cur) => (prev.includes(cur) ? prev : [...prev, cur]),
    []
  );
}

12、利用 Set 数据结构 + Array.from

function darray12(arr) {
  //Set数据结构,它类似于数组,其成员的值都是唯一的
  return Array.from(new Set(arr)); // 利用Array.from将Set结构转换成真正的数组
}
console.log(darray12(arr1)); //[11, 1, 2, "huo"]

13、利用 Set 数据结构 + 展开运算符(最简便的方法了)

let arr = [...new Set(arr1)];
console.log(arr); // [11, 1, 2, "huo"]

​ ​