Array 最全面的总结,从基础到奇技淫巧

本文全面总结了JavaScript数组的基础操作、常规操作和进阶操作,包括创建、访问、添加删除元素、排序查找、去重扁平化等,以及排序算法(冒泡、插入、选择、归并)和查找算法(线性、二分、插值)。同时,探讨了旋转算法、缺失数字算法和求众数算法的各种实现和比较分析。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

基础操作

创建数组

// 创建空数组
const arr1 = [];

// 创建具有初始值的数组
const arr2 = [1, 2, 3];

// 创建长度为 n 的数组
const arr3 = new Array(n);

// 创建具有初始值且长度为 n 的数组
const arr4 = new Array(n).fill(0);

访问数组元素

const arr = ['apple', 'banana', 'cherry'];

// 通过索引访问元素
console.log(arr[0]);   // 输出:'apple'
console.log(arr[1]);   // 输出:'banana'
console.log(arr[2]);   // 输出:'cherry'

// 通过循环访问数组元素
for (let i = 0; i < arr.length; i++) {
  console.log(arr[i]);
}

添加、删除元素

const arr = ['apple', 'banana', 'cherry'];

// 添加元素到数组末尾
arr.push('orange');

// 添加元素到数组开头
arr.unshift('pear');

// 删除数组末尾的元素
arr.pop();

// 删除数组开头的元素
arr.shift();

切分和连接数组

const arr1 = ['apple', 'banana', 'cherry'];
const arr2 = ['orange', 'pear'];

// 切分数组
const slicedArr = arr1.slice(1, 2);
console.log(slicedArr);   // 输出:['banana']

// 连接数组
const concatenatedArr = arr1.concat(arr2);
console.log(concatenatedArr);   // 输出:['apple', 'banana', 'cherry', 'orange', 'pear']

排序和查找元素

const arr = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5];

// 排序
arr.sort();
console.log(arr);   // 输出:[1, 1, 2, 3, 3, 4, 5, 5, 5, 6, 9]

// 查找元素
console.log(arr.indexOf(5));   // 输出:6
console.log(arr.lastIndexOf(5));   // 输出:8
console.log(arr.includes(7));   // 输出:false

数组遍历方法

const arr = [1, 2, 3, 4, 5];

// forEach
arr.forEach(function(element, index) {
  console.log(index + ': ' + element);
});

// map
const mappedArr = arr.map(function(element) {
  return element * 2;
});
console.log(mappedArr);   // 输出:[2, 4, 6, 8, 10]

// filter
const filteredArr = arr.filter(function(element) {
  return element % 2 === 0;
});
console.log(filteredArr);   // 输出:[2, 4]

// reduce
const reducedValue = arr.reduce(function(accumulator, element) {
  return accumulator + element;
}, 0);
console.log(reducedValue);   // 输出:15

常规操作

数组去重

const arr = [1, 2, 3, 1, 2, 3, 4, 5];

// 使用 Set 去重
const uniqueArr1 = [...new Set(arr)];
console.log(uniqueArr1);   // 输出:[1, 2, 3, 4, 5]

// 使用 filter 去重
const uniqueArr2 = arr.filter((element, index) => arr.indexOf(element) === index);
console.log(uniqueArr2);   // 输出:[1, 2, 3, 4, 5]

数组扁平化

const arr = [1, [2, [3, 4], 5], 6];

// 使用 flat 扁平化数组
const flattenedArr1 = arr.flat(Infinity);
console.log(flattenedArr1);   // 输出:[1, 2, 3, 4, 5, 6]

// 使用 reduce 扁平化数组
const flattenedArr2 = arr.reduce((prev, curr) => prev.concat(Array.isArray(curr) ? flattenedArr2(curr) : curr), []);
console.log(flattenedArr2);   // 输出:[1, 2, 3, 4, 5, 6]

数组随机排序

const arr = [1, 2, 3, 4, 5];

// 使用 sort 和 Math.random 随机排序
const randomizedArr1 = arr.sort(() => Math.random() - 0.5);
console.log(randomizedArr1);   // 输出:随机排序的数组

// 使用 Fisher-Yates 算法随机排序
function shuffleArray(array) {
  for (let i = array.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    [array[i], array[j]] = [array[j], array[i]];
  }
  return array;
}
const randomizedArr2 = shuffleArray(arr);
console.log(randomizedArr2);   // 输出:随机排序的数组


数组求平均数、最大值、最小值、求和

const arr = [1, 2, 3, 4, 5];

// 求平均数
const average = arr.reduce((acc, cur) => acc + cur) / arr.length;
console.log(average);   // 输出:3

// 求最大值
const max = Math.max(...arr);
console.log(max);   // 输出:5

// 求最小值
const min = Math.min(...arr);
console.log(min);   // 输出:1

// 求和
const sum = arr.reduce((acc, cur) => acc + cur);
console.log(sum);   // 输出:15

数组判空

const arr1 = [];
const arr2 = [1, 2, 3];

// 判断数组是否为空
const isEmpty1 = arr1.length === 0;
const isEmpty2 = !arr2.length;
console.log(isEmpty1);   // 输出:true
console.log(isEmpty2);   // 输出:false

数组去除 falsy 值

const arr = [1, 0, null, undefined, '', NaN, false];

// 使用 filter 去除 falsy 值
const filteredArr = arr.filter(Boolean);
console.log(filteredArr);   // 输出:[1]

数组分块

const arr = [1, 2, 3, 4, 5, 6, 7, 8];

// 将数组分成两个块
const chunkedArr = Array.from({length: Math.ceil(arr.length / 2)}, (v, i) =>
  arr.slice(i * 2, i * 2 + 2)
                             );
console.log(chunkedArr);   // 输出:[[1, 2], [3, 4], [5, 6], [7, 8]]

数组对象去重

const arr = [
  {name: 'Alice', age: 20},
  {name: 'Bob', age: 30},
  {name: 'Alice', age: 20},
  {name: 'Charlie', age: 40},
];

// 使用 Set 和 JSON.stringify 去重
const uniqueArr = Array.from(new Set(arr.map(item => JSON.stringify(item)))).map(item => JSON.parse(item));
console.log(uniqueArr);   // 输出:[{name: 'Alice', age: 20}, {name: 'Bob', age: 30}, {name: 'Charlie', age: 40}]

数组交集、并集、差集

const arr1 = [1, 2, 3, 4, 5];
const arr2 = [3, 4, 5, 6, 7];

// 求两个数组的交集
const intersection = arr1.filter(item => arr2.includes(item));
console.log(intersection);   // 输出:[3, 4, 5]

// 求两个数组的并集
const union = [...new Set([...arr1, ...arr2])];
console.log(union);   // 输出:[1, 2, 3, 4, 5, 6, 7]

// 求两个数组的差集
const difference = arr1.filter(item => !arr2.includes(item));
console.log(difference);   // 输出:[1, 2]

进阶操作(算法)

排序算法

冒泡排序

冒泡排序是最简单的排序算法之一。其基本思想是从头到尾比较相邻的两个元素,如果逆序就交换,每一轮可以将最大的元素放到最后。经过 n 轮后,就完成了排序。

function bubbleSort(arr) {
  const len = arr.length;
  for (let i = 0; i < len; i++) {
    for (let j = 0; j < len - i - 1; j++) {
      if (arr[j] > arr[j + 1]) {
        [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]]; // 交换位置
      }
    }
  }
  return arr;
}

const arr = [3, 2, 1, 5, 4];
console.log(bubbleSort(arr)); // [1, 2, 3, 4, 5]

插入排序

插入排序是一种简单直观的排序算法。它的基本思想是将一个数据插入到已经排好序的有序序列中,从而得到一个新的、个数加一的有序序列。

function insertSort(arr) {
  const len = arr.length;
  let j, temp;
  for (let i = 1; i < len; i++) {
    j = i;
    temp = arr[i];
    while (j > 0 && arr[j - 1] > temp) {
      arr[j] = arr[j - 1];
      j--;
    }
    arr[j] = temp;
  }
  return arr;
}

const arr = [3, 2, 1, 5, 4];
console.log(insertSort(arr)); // [1, 2, 3, 4, 5]

选择排序

选择排序是一种简单直观的排序算法。其基本思想是每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放到序列的起始位置,直到全部待排序的数据元素排完。

function selectSort(arr) {
  const len = arr.length;
  let minIndex, temp;
  for (let i = 0; i < len - 1; i++) {
    minIndex = i;
    for (let j = i + 1; j < len; j++) {
      if (arr[j] < arr[minIndex]) {
        minIndex = j;
      }
    }
    if (minIndex !== i) {
      [arr[i], arr[minIndex]] = [arr[minIndex], arr[i]]; // 交换位置
    }
  }
  return arr;
}

const arr = [3, 2, 1, 5, 4];
console.log(selectSort(arr)); // [1, 2, 3, 4, 5]

归并排序

归并排序(Merge Sort)是一种分治算法,它将一个数组分成两个子数组,对这两个子数组分别进行排序,然后再将排序后的子数组合并成一个有序的数组。归并排序的时间复杂度为 O(nlogn),空间复杂度为 O(n)。

function mergeSort(arr) {
  // 如果数组长度为 1 或 0,直接返回
  if (arr.length <= 1) {
    return arr;
  }

  // 找到中间值
  const mid = Math.floor(arr.length / 2);

  // 递归分割左右两个子数组
  const leftArr = mergeSort(arr.slice(0, mid));
  const rightArr = mergeSort(arr.slice(mid));

  // 合并左右两个有序数组
  return merge(leftArr, rightArr);
}

function merge(leftArr, rightArr) {
  let i = 0, j = 0;
  const result = [];

  // 比较左右两个数组的元素,将较小的元素加入结果数组
  while (i < leftArr.length && j < rightArr.length) {
    if (leftArr[i] < rightArr[j]) {
      result.push(leftArr[i]);
      i++;
    } else {
      result.push(rightArr[j]);
      j++;
    }
  }

  // 将左数组剩余的元素加入结果数组
  while (i < leftArr.length) {
    result.push(leftArr[i]);
    i++;
  }

  // 将右数组剩余的元素加入结果数组
  while (j < rightArr.length) {
    result.push(rightArr[j]);
    j++;
  }

  return result;
}

排序方式对比分析

排序算法时间复杂度空间复杂度稳定性
冒泡排序O(n^2)O(1)稳定
插入排序O(n^2)O(1)稳定
选择排序O(n^2)O(1)不稳定
快速排序O(nlogn)O(logn)~O(n)不稳定
归并排序O(nlogn)O(n)稳定

其中,时间复杂度表示在不同数据规模下,算法的运行时间复杂度;空间复杂度表示在运行算法时所占用的内存空间;稳定性表示在排序时,是否能够保持相同元素的原有顺序不变。

查找算法

线性查找(顺序查找)

线性查找是最简单的查找算法,适用于数据规模较小的情况。其思路是从数据的开头开始,顺序扫描每个元素,直到找到目标元素为止。

function linearSearch(arr, target) {
  for (let i = 0; i < arr.length; i++) {
    if (arr[i] === target) {
      return i; // 返回目标元素在数组中的下标
    }
  }
  return -1; // 目标元素不存在于数组中
}

二分查找(折半查找)

二分查找适用于有序数组,其思路是将数组分成两半,比较目标元素和中间元素的大小关系,如果相等则返回中间元素的下标,如果目标元素大于中间元素,则在后半部分继续查找,否则在前半部分继续查找,直到找到目标元素或者数组被遍历完。

function binarySearch(arr, target) {
  let left = 0; // 左边界
  let right = arr.length - 1; // 右边界

  while (left <= right) {
    let mid = Math.floor((left + right) / 2); // 中间位置

    if (arr[mid] === target) {
      return mid; // 返回中间位置
    } else if (arr[mid] < target) {
      left = mid + 1; // 在后半部分继续查找
    } else {
      right = mid - 1; // 在前半部分继续查找
    }
  }

  return -1; // 目标元素不存在于数组中
}

插值查找

插值查找是一种改进的二分查找算法,适用于数据分布比较均匀的情况。其思路是根据目标元素与数组元素的大小关系,计算出目标元素在数组中的位置,然后进行查找。

function interpolationSearch(arr, target) {
  let left = 0; // 左边界
  let right = arr.length - 1; // 右边界

  while (left <= right) {
    let mid = Math.floor(
      left + ((target - arr[left]) / (arr[right] - arr[left])) * (right - left)
    ); // 计算插值

    if (arr[mid] === target) {
      return mid; // 返回中间位置
    } else if (arr[mid] < target) {
      left = mid + 1; // 在后半部分继续查找
    } else {
      right = mid - 1; // 在前半部分继续查找
    }
  }

  return -1;

查找方式对比分析

算法时间复杂度是否有序适用场景
线性查找O(n)数组或列表中任意元素的查找
二分查找O(log n)有序数组或列表中元素的查找
差值查找O(log n)有序数组或列表中元素的查找

需要注意的是,时间复杂度只是一个理论值,实际运行时间受多种因素影响。在实际应用中,我们需要根据具体场景和需求选择合适的算法

旋转算法

是指将数组中的元素向左或向右移动若干个位置,旋转算法常用于矩阵旋转、字符串旋转等场景。

数组反转

数组反转是一种简单的旋转算法,其思路是将数组头尾对称位置上的元素交换,从而实现整个数组的反转。

function reverseArray(arr) {
  let left = 0;
  let right = arr.length - 1;

  while (left < right) {
    const temp = arr[left];
    arr[left] = arr[right];
    arr[right] = temp;
    left++;
    right--;
  }
  return arr;
}

const arr = [1, 2, 3, 4, 5];
console.log(reverseArray(arr)); // [5, 4, 3, 2, 1]

数组循环移动

数组循环移动是指将数组的前面若干个元素移动到数组的末尾,或将数组的后面若干个元素移动到数组的前面,从而实现数组的旋转。

function rotateArray(arr, k) {
  const n = arr.length;
  k = k % n; // 对 k 取模

  // 反转数组的前 n-k 个元素
  reverseArray(arr, 0, n - k - 1);
  // 反转数组的后 k 个元素
  reverseArray(arr, n - k, n - 1);
  // 反转整个数组
  reverseArray(arr, 0, n - 1);

  return arr;
}

function reverseArray(arr, left, right) {
  while (left < right) {
    const temp = arr[left];
    arr[left] = arr[right];
    arr[right] = temp;
    left++;
    right--;
  }
}

const arr = [1, 2, 3, 4, 5];
console.log(rotateArray(arr, 2)); // [3, 4, 5, 1, 2]

数组分段反转

数组分段反转是指将数组分成若干段,对每一段进行反转,从而实现整个数组的旋转。

function rotateArray(arr, k) {
  const n = arr.length;
  k = k % n; // 对 k 取模

  reverseArray(arr, 0, n - k - 1);
  reverseArray(arr, n - k, n - 1);
  reverseArray(arr, 0, n - 1);

  return arr;
}

function reverseArray(arr, left, right) {
  while (left < right) {
    const temp = arr[left];
    arr[left] = arr[right];
    arr[right] = temp;
    left++;
    right--;
  }
}

const arr = [1, 2, 3, 4, 5];
console.log(rotateArray(arr, 2)); // [3, 4, 5, 1, 2]

数组块交换

数组块交换是指将数组分成若干块,然后将这些块交换位置,以实现旋转数组的目的

function blockSwapArray(arr, k) {
  const n = arr.length;
  if (k % n === 0) {
    return arr; // k 是 n 的整数倍,数组不变
  }
  const gcd = (a, b) => (b === 0 ? a : gcd(b, a % b));
  const swap = (start, end) => {
    while (start < end) {
      [arr[start], arr[end]] = [arr[end], arr[start]];
      start++;
      end--;
    }
  };
  const numSwaps = gcd(n, k);
  const blockSize = n / numSwaps;
  for (let i = 0; i < numSwaps; i++) {
    const start = i * blockSize;
    const end = (i + 1) * blockSize - 1;
    swap(start, end);
  }
  swap(0, n - k - 1);
  swap(n - k, n - 1);
  swap(0, n - 1);
  return arr;
}

const arr = [1, 2, 3, 4, 5, 6, 7];
console.log(blockSwapArray(arr, 3)); // [5, 6, 7, 1, 2, 3, 4]

旋转算法对比分析

算法种类时间复杂度空间复杂度是否原地修改是否稳定
数组反转O(kn)O(1)
数组循环移动O(n)O(1)
数组块交换O(n)O(1)
旋转算法对比分析O(n)O(1)

缺失数字算法

遍历法

遍历法是最基本的解法,遍历数组中的每个元素,累加求和,再用数组中所有数的和减去累加和,就可以得到缺失的数字。该算法的时间复杂度为 O(n),空间复杂度为 O(1)。

function missingNumber1(nums) {
  let sum = 0;
  for (let i = 0; i < nums.length; i++) {
    sum += nums[i];
  }
  return (nums.length * (nums.length + 1)) / 2 - sum;
}

等差数列法

如果数组中只有一个数字缺失且其他数字在一定范围内连续,我们可以使用等差数列求和公式求出数组中所有数的和,再用数组中所有数的和减去缺失数字之前所有数的和,就可以得到缺失数字。该算法的时间复杂度为 O(n),空间复杂度为 O(1)。

function missingNumber2(nums) {
  let n = nums.length;
  let sum = (n * (n + 1)) / 2;
  let realSum = 0;
  for (let i = 0; i < n; i++) {
    realSum += nums[i];
  }
  return sum - realSum;
}

位运算法

如果数组中只有一个数字缺失且其他数字都出现了偶数次,我们可以将数组中的所有数和从 0 到 n 的所有数进行异或运算,因为两个相同的数异或的结果为 0,最后剩下的就是缺失的数字。该算法的时间复杂度为 O(n),空间复杂度为 O(1)。

function missingNumber3(nums) {
  let missing = nums.length;
  for (let i = 0; i < nums.length; i++) {
    missing ^= i ^ nums[i];
  }
  return missing;
}

缺失数字算法对比分析

算法名称时间复杂度空间复杂度应用场景
遍历法O(n)O(1)数组中只有一个数字缺失
等差数列法O(n)O(1)数组中只有一个数字缺失且其他数字在一定范围内连续
位运算法O(n)O(1)数组中只有一个数字缺失且其他数字都出现了偶数次

以上时间复杂度和空间复杂度均为最优情况下的复杂度,实际应用中可能会因为具体问题而有所不同。

求众数算法

摩尔投票法

摩尔投票法的思路也很简单,我们假设数组中的第一个数字是众数,然后遍历数组,如果下一个数字和当前数字相同,那么计数器加一,否则计数器减一。当计数器减为零的时候,我们就重新假设当前数字是众数。因为众数的出现次数超过了数组长度的一半,所以最后留下来的数字一定是众数。

function majorityElement(nums) {
  let count = 0;
  let candidate = null;
  for (let num of nums) {
    if (count === 0) {
      candidate = num;
    }
    count += (num === candidate) ? 1 : -1;
  }
  return candidate;
}

哈希表法

  1. 创建一个空的哈希表,键为数组中的元素,值为元素出现的次数。
  2. 遍历数组,如果当前元素不在哈希表中,则将其作为键插入哈希表,并将值设为1;如果当前元素已经在哈希表中,则将其对应的值加1。
  3. 遍历哈希表,找到出现次数最多的元素即可。
function majorityElement(nums) {
  const counts = {}; // 创建一个空的哈希表
  const n = nums.length;
  
  // 遍历数组,统计每个元素出现的次数
  for (let i = 0; i < n; i++) {
    if (counts[nums[i]] === undefined) {
      // 如果当前元素不在哈希表中,则将其作为键插入哈希表,并将值设为1
      counts[nums[i]] = 1;
    } else {
      // 如果当前元素已经在哈希表中,则将其对应的值加1
      counts[nums[i]]++;
    }
  }
  
  let majority = nums[0]; // 默认众数是数组的第一个元素
  let maxCount = 0; // 默认众数出现的次数是0
  
  // 遍历哈希表,找到出现次数最多的元素
  for (let key in counts) {
    if (counts[key] > maxCount) {
      majority = key;
      maxCount = counts[key];
    }
  }
  
  return majority;
}

排序法

可以先将数组排序,然后从左到右遍历,统计每个数出现的次数,直到找到一个数出现的次数超过了数组长度的一半,返回该数即可。

function majorityElement(nums) {
  nums.sort(); // 先将数组排序
  let count = 1; // 初始化第一个数字的出现次数为 1
  let majority = nums[0]; // 初始化当前数字为第一个数字
  for (let i = 1; i < nums.length; i++) { // 从第二个数字开始遍历
    if (nums[i] === majority) { // 如果遍历到的数字和当前数字相等
      count++; // 出现次数加 1
    } else { // 如果遍历到的数字和当前数字不相等
      if (count > nums.length / 2) { // 如果当前数字的出现次数超过了数组长度的一半
        return majority; // 直接返回当前数字
      }
      count = 1; // 将出现次数重新设置为 1
      majority = nums[i]; // 将当前数字设置为遍历到的数字
    }
  }
  // 如果最后一个数字出现的次数超过了数组长度的一半,或者数组长度为 1,直接返回最后一个数字
  return majority;
}

求众数算法对比分析

算法名称时间复杂度空间复杂度特点
摩尔投票O(n)O(1)超过了数组长度的一半,所以最后留下来的数字一定是众数。
哈希表O(n)O(n)使用哈希表记录每个数字出现的次数,适用于任意规模数据
排序法O(nlogn)O(1)先将数组排序,然后遍历数组找出出现次数最多的元素,适用于大规模数据

需要注意的是,虽然这三种算法的时间复杂度有所不同,但在实际使用时还需要考虑具体数据的规模和复杂度常数等因素。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Huang-ioi

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值