基础操作
创建数组
// 创建空数组
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;如果当前元素已经在哈希表中,则将其对应的值加1。
- 遍历哈希表,找到出现次数最多的元素即可。
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) | 先将数组排序,然后遍历数组找出出现次数最多的元素,适用于大规模数据 |
需要注意的是,虽然这三种算法的时间复杂度有所不同,但在实际使用时还需要考虑具体数据的规模和复杂度常数等因素。