概念
- 数组的创建: 有两种方式,
const arr = [1, 2, 3, 4]或const arr = new Array()。 - 数组的访问与遍历: 访问通过访问索引下标
arr[0],遍历有for、forEach、map等方法,从效率上讲,for是最快的。 - 二维数组,又叫矩阵。二维数组的初始化,不要用fill完成
const arr =(new Array(7)).fill([]),否则填充的是同一个数组的引用。而应该使用for来初始化。
const len = arr.length
for(let i=0;i<len;i++) {
// 将数组的每一个坑位初始化为数组
arr[i] = []
}
数组API: concat、some、join、sort、push(添加到尾部)、pop(删除尾部)、unshift(添加到头部)、shift(删除头部)、slice(返回一个截取的新数组)、splice(修改原有数组)。
splice() 方法通过删除或替换现有元素或者原地添加新的元素来修改数组,并以数组形式返回被修改的内容。此方法会改变原数组。 示例:
const months = ['Jan', 'March', 'April', 'June'];
// 在index 1位置插入
months.splice(1, 0, 'Feb');
console.log(months); //["Jan", "Feb", "March", "April", "June"]
// 在index 4位置替换
months.splice(4, 1, 'May');
console.log(months); // ["Jan", "Feb", "March", "April", "May"]
// 在index 2位删除一个元素
months.splice(2, 1);
console.log(months); // ["Jan", "Feb", "April", "May"]
// 在倒数第二2位 删除一个元素
months.splice(-2, 1);
console.log(months); // ["Jan", "Feb", "May"]
slice() 方法返回一个新的数组对象,这一对象是一个由 begin 和 end 决定的原数组的浅拷贝(包括 begin,不包括end)。原始数组不会被改变。 示例:
const animals = ['ant', 'bison', 'camel', 'duck', 'elephant'];
console.log(animals.slice(2)); // ["camel", "duck", "elephant"]
//从index 2位开始,到index 4结束(不包括4)返回
console.log(animals.slice(2, 4)); // ["camel", "duck"]
console.log(animals.slice(1, 5)); // ["bison", "camel", "duck", "elephant"]
reduce
reduce() 方法对数组中的每个元素执行一个由您提供的reducer函数(升序执行),将其结果汇总为单个返回值。
arr.reduce(callback(accumulator, currentValue[, index[, array]])[, initialValue])
reducer的callback函数接收4个参数:
- Accumulator (acc) (累计器)
- Current Value (cur) (当前值)
- Current Index (idx) (当前索引)
- Source Array (src) (源数组) reducer 函数的返回值分配给累计器,该返回值在数组的每个迭代中被记住,并最后成为最终的单个结果值。
回调函数第一次执行时,accumulator 和currentValue的取值有两种情况:
- 如果调用reduce()时提供了initialValue,accumulator取值为initialValue,currentValue取数组中的第一个值;
- 如果没有提供 initialValue,那么accumulator取数组中的第一个值,currentValue取数组中的第二个值
//一、数组求和
//0+1+2+3
var total0 = [0, 1, 2, 3].reduce((acc, cur) => acc + cur)
console.log(total0) //6
//10+0+1+2+3
var total1 = [0, 1, 2, 3].reduce((acc, cur) => acc + cur, 10)
console.log(total1) //16
var total2 = [{ x: 1 }, { x: 2 }, { x: 3 }].reduce((acc, cur) => acc + cur.x, 0)
console.log(total2) //6
var total3 = [{ x: 1 }, { x: 2 }, { x: 3 }].map(e => e.x).reduce((acc, cur) => acc + cur, 0)
console.log(total3) //6
//二、二维数组转一维数组
var flattened = [[0, 1], [2, 3], [4, 5]].reduce(( acc, cur ) => acc.concat(cur),[]);
console.log(flattened) //[0,1,2,3,4,5]
去重
Set
const unique = arr => Array.from(new Set(arr))
或
const unique = arr => [...new Set(arr)]
filter + indexOf
const unique = arr => arr.filter((e,i)=> arr.indexOf(e) === i)
Object
const unique = arr => {
const container = {}
return arr.filter((e,i)=> container.hasOwnProperty(e)?false:(container[e] = true))
}
reduce
let arr = [1, 2, 1, 2, 3, 5, 4, 5, 3, 4, 4, 4, 4]
let result = arr.sort().reduce((acc, cur) => {
if (acc.indexOf(cur) === -1) {
acc.push(cur)
}
return acc
}, [])
或者
let result2 = arr.sort().reduce((acc, cur) => {
if (acc.length === 0 || acc[acc.length - 1] !== cur) {
acc.push(cur)
}
return acc
}, [])
console.log(result) //[1,2,3,4,5]
去除重复的值(全部去掉):
const filterNonUnique = arr => arr.filter(i =>
arr.indexOf(i) === arr.lastIndexOf(i)
)
扁平化
function flat(arr) {
if (!Array.isArray(arr)) return []
let result = []
for (let i = 0; i < arr.length; i++) {
if (Array.isArray(arr[i])){
result = result.concat(flat(arr[i]))
}else{
result.push(arr[i])
}
}
return result
}
console.log(flat([1, 3, 'a', [4, 6, 'b', [7, 9]]]))
//[1,3,'a',4,6,'b',7,9]
或者
function flatten(arr) {
return arr.reduce((acc,cur)=>{
Array.isArray(cur)?acc.concat(flatten(cur)):acc.concat(cur)
},[])
}
双指针
主要是利用两个或多个不同位置的指针,通过速度和方向的变换解决问题。注意这种技巧经常在排序数组中使用。
变换奇偶数位置
输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分
步骤:
- 设置两个指针,一个less一个more,
- 如果当前less位置的元素为偶数,那么和more位置的元素交换位置,more--,
- 接着继续判断交换过来的元素是否是偶数,如果继续是偶数,那么继续和more位置的元素交换,一直循环到less>=more,复杂度为O(N)
function reOrderArr(arr) {
if (!Array.isArray(arr)) {
return null
}
let less = 0
let more = arr.length - 1
while(less < more){
while (arr[less]%2==0){
swap(arr, less, more)
more-- //交换后more位置已经是偶像了,那么往左边前进
}
less++
}
return arr
}
function swap(arr, i, j) {
const temp = arr[i]
arr[i] = arr[j]
arr[j] = temp
}
console.log(reOrderArr([2,2,1,7,5,4,6,5,8,9,0]))
//9,5,1,7,6,5,4,8,2,0,2
进阶版:输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分,
并保证奇数和奇数,偶数和偶数之间的相对位置不变。
步骤:
只要前面的数为偶数,当前的数为奇数,那么就一直交换位置到最前面的奇数后面,这个指针为less, 时间复杂度为O(N),不过一直从后往前交换,交换次数较多,但是这种方法没有使用额外空间。
function reOrderArr(arr) {
if (!Array.isArray(arr)) {
return null
}
let left = 0
for(let i = 0; i < arr.length; i++){
if( arr[i]%2 == 1){
let cur = i //遇到奇数,就逐个交换到最左边奇数的后面
while(cur > left){
swap(arr, cur, --cur)
}
left++ //交换完后奇数下标右移
}
}
return arr
}
function swap(arr, i, j) {
const temp = arr[i]
arr[i] = arr[j]
arr[j] = temp
}
console.log(reOrderArr([2,2,1,7,5,4,6,5,8,9,0]))
//1,7,5,5,9,2,2,4,6,8,0
和为S的两个数字
输入一个递增排序的数组和一个数字S,在数组中查找两个数,使得他们的和正好是S,如果有多对数字的和等于S,输出两个数的乘积最小的。
步骤:
数组是有序的,可以使用使用大小指针求解,不断逼近结果,最后取得最终值。
function findTwoNumber(arr, sum) {
if (!Array.isArray(arr)) {
return null
}
let left = 0
let right = arr.length - 1
while (left < right) {
const s = arr[left] + arr[right]
if (s > sum) {
right--
} else if (s < sum) {
left++
} else {
return [arr[left], arr[right]]
}
}
return []
}
和为S的连续正整数序列
输入一个正数S,打印出所有和为S的连续正数序列。 例如:输入15,有序1+2+3+4+5 = 4+5+6 = 7+8 = 15 所以打印出3个连续序列1-5,5-6和7-8。
步骤:
- 创建一个容器container,用于表示当前的子序列,初始元素为1,2
- 记录子序列的开头元素left和末尾元素right
- right向右移动子序列末尾增加一个数 left向右移动子序列开头减少一个数
- 当子序列的和大于目标值,left向右移动,子序列的和小于目标值,right向右移动
function findSerialNumber(s) {
if (s <= 0) return []
let result = []
let left = 1, right = 2
let container = [left, right]
while (left < right) {
let sum = sumArr(container)
if (sum === s) {
let targetArr = Object.assign([], container)
result.push(targetArr)
container.shift(left)
left++
} else if (sum < s) {
right++
container.push(right)
} else {
container.shift(left)
left++
}
}
return result
}
function sumArr(arr) {
return arr.reduce(function(prev, curr, idx, arr) {
return prev + curr
})
}
console.log(findSerialNumber(15))
// [ [ 1, 2, 3, 4, 5 ], [ 4, 5, 6 ], [ 7, 8 ] ]
N数之和问题
非常常见的问题,基本上都是一个套路,主要考虑如何比暴利法降低时间复杂度,而且也会用到上面的双指针技巧
两数之和
给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那两个整数,并返回他们的数组下标。
给定 nums = [2, 7, 11, 15], target = 9 因为 nums[0] + nums[1] = 2 + 7 = 9, 所以返回 [0, 1]
function sumTwo(arr, target) {
if (!Array.isArray(arr)) return []
const map = new Map()
for (let i = 0; i < arr.length; i++) {
let value = target - arr[i]
if (map[value] != undefined) {
return [map[value], i]
} else {
map[arr[i]] = i
}
}
return []
}
console.log(sumTwo([2, 7, 11, 15], 9))
//[0,1]
三数之和
给定一个包含 n 个整数的数组nums,判断 nums 中是否存在三个元素a,b,c ,使得 a + b + c = 0 ?找出所有满足条件且不重复的三元组。
注意:答案中不可以包含重复的三元组。
例如, 给定数组 nums = [-1, 0, 1, 2, -1, -4], 满足要求的三元组集合为:[[-1, 0, 1], [-1, -1, 2]]
步骤:
- 为了方便去重,我们首先将数组排序
- 对数组进行遍历,取当前遍历的数nums[i]为一个基准数,遍历数后面的数组为寻找数组
- 在寻找数组中设定两个起点,最左侧的left(i+1)和最右侧的right(length-1)
- 判断nums[i] + nums[left] + nums[right]是否等于0,如果等于0,加入结果,并分别将left和right移动一位
- 如果结果大于0,将right向左移动一位,向结果逼近
- 如果结果小于0,将left向右移动一位,向结果逼近
function sumThree(arr) {
if (!Array.isArray(arr)) return []
//由小到大排序
arr.sort((a, b) => a - b)
let result = []
for (let i = 0; i < arr.length; i++) {
let left = i + 1
let right = arr.length - 1
while (left < right) {
const sum = arr[i] + arr[left] + arr[right]
if (sum === 0) {
result.push([arr[i], arr[left], arr[right]])
break
} else if (sum < 0) {
left++
} else {
right--
}
}
}
return result
}
console.log(sumThree([-1, 0, 1, 2, -1, -4]))
//[ [ -1, -1, 2 ], [ -1, 0, 1 ] ]
四数之和
给定一个包含 n 个整数的数组nums,判断 nums 中是否存在四个元素a,b,c,d ,使得 a + b + c + d = 0 ?找出所有满足条件且不重复的四元组。
给定数组 nums = [1, 0, -1, 0, -2, 2],和 target = 0。 满足要求的四元组集合为: [ [-1, 0, 0, 1], [-2, -1, 1, 2], [-2, 0, 0, 2] ]
可以像三数之和那样,我们可以通过大小指针来逼近结果,从而达到降低一层时间复杂度的效果。 不管是几数之和,我们都用这种方法来进行优化。
function sumFour(arr) {
if (!Array.isArray(arr)) return []
//由小到大排序
arr.sort((a, b) => a - b)
let result = []
for (let i = 0; i < arr.length; i++) {
for (let j = i + 1; j < arr.length; j++) {
let left = j + 1
let right = arr.length - 1
while (left < right) {
const sum = arr[i] + arr[j] + arr[left] + arr[right]
if (sum === 0) {
result.push([arr[i], arr[j], arr[left], arr[right]])
break
} else if (sum < 0) {
left++
} else {
right--
}
}
}
}
return result
}
console.log(sumFour([1, 0, -1, 0, -2, 2]))
//[ [ -2, -1, 1, 2 ], [ -2, 0, 0, 2 ], [ -1, 0, 0, 1 ] ]
二维数组
构建乘积数组
给定一个数组A[0,1,...,n-1],请构建一个数组B[0,1,...,n-1],其中B中的元素B[i]=A[0]A[1]...*A[i-1]A[i+1]...*A[n-1]。不能使用除法。
步骤:
- B[i]的值是A数组所有元素的乘积再除以A[i],但是题目中给定不能用除法,我们换一个思路,将B[i]的每个值列出来
- B[i]的值可以看作下图的矩阵中每行的乘积
- 可以将B数组分为上下两个三角,先计算下三角,然后把上三角乘进去

function multiply(arr) {
if (!Array.isArray(arr) || !arr.length) return []
let result = []
for (let j = 0; j < arr.length; j++) {
let temp = arr[j]
arr[j] = 1
result[j] = 1
for (let i = 0; i < arr.length; i++) {
result[j] = result[j] * arr[i]
}
arr[j] = temp
}
return result
}
console.log(multiply([1, 2, 3, 4, 5, 6, 7, 8]))
//[40320,20160,13440,10080,8064,6720,5760,5040]
数据统计
出现次数超数组一半的数字
数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组[1,2,3,2,2,2,5,4,2]。
由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。
function counterGreaterThanHalf(arr) {
if (!Array.isArray(arr) || !arr.length) return 0
const half = arr.length / 2
const map = new Map()
for (let i = 0; i < arr.length; i++) {
if (!map[arr[i]]) {
map[arr[i]] = 1
} else {
map[arr[i]] = map[arr[i]] + 1
}
}
let target = 0
Object.keys(map).forEach(k => {
if (map[k] > half) {
target = k
return k
}
})
return target
}
console.log(counterGreaterThanHalf([1, 2, 3, 2, 2, 2, 5, 4, 2]))
连接子数组的最大和
输入一个整型数组,数组里有正数也有负数。数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值,要求时间复杂度为O(n)
例如:[6,-3,-2,7,-15,1,2,2],连续子向量的最大和为8(从第0个开始,到第3个为止)
步骤:
- 记录一个当前连续子数组最大值 max 默认值为数组第一项
- 记录一个当前连续子数组累加值 sum 默认值为数组第一项
- 从数组第二个数开始,若 sum<0 则当前的sum不再对后面的累加有贡献,sum = 当前数
- 若 sum>0 则sum = sum + 当前数
- 比较 sum 和 max ,max = 两者最大值
function findGreatestSubArr(arr) {
if (!Array.isArray(arr) || !arr.length) return []
let max = arr[0]
let sum = arr[0]
for (let i = 1; i < arr.length; i++){
if (sum < 0) {
sum = arr[i];
} else {
sum = sum + arr[i];
}
if (sum > max) {
max = sum;
}
}
return max
}
console.log(findGreatestSubArr([6, -3, -2, 7, -15, 1, 2, 2]))
//8
扑克牌顺子
扑克牌中随机抽5张牌,判断是不是一个顺子,即这5张牌是不是连续的。 2-10为数字本身,A为1,J为11...大小王可以看成任何数字,可以把它当作0处理
步骤:
- 字符串映射成数字
- 数组从小到大排序
- 判断数组末位比首位大4,则是连接的,否则不连续
function IsContinuous(arr) {
if (!Array.isArray(arr) || !arr.length) return []
let result = false
for (let i = 0; i < arr.length; i++) {
if (arr[i] === 'A') arr[i] = 1
else if (arr[i] === 'J') arr[i] = 11
else if (arr[i] === 'Q') arr[i] = 12
else if (arr[i] === 'K') arr[i] = 13
else if (Number.isInteger(arr[i])) arr[i] = arr[i]
else arr[i] = 0
}
arr.sort((a, b) => a - b)
if(arr[arr.length - 1] - arr[0] == 4){
result = true
}
return result
}
console.log(IsContinuous(['Q', 3, 'A', 2, 5]))
首个只出现一次的字符
在一个字符串(0<=字符串长度<=10000,全部由字母组成)中找到第一个只出现一次的字符,并返回它的位置, 如果没有则返回-1(需要区分大小写)
思路1:
- 用一个map存储每个字符出现的字数
- 第一次循环存储次数,第二次循环找到第一个出现一次的字符。 时间复杂度O(n)、空间复杂度O(n)
#思路二:
- 使用js的array提供的indexOf和lastIndexOf方法
- 遍历字符串,比较每个字符第一次和最后一次出现的位置是否相同。 indexOf的时间复杂度为O(n),所以整体的时间复杂度为O(n2),空间复杂度为0
function findFirstOnlyOneLetter(str) {
if (!str) return -1
const map = new Map()
const arr = str.split('')
for (let i = 0; i < arr.length; i++) {
if (map[arr[i]]) {
map[arr[i]] = map[arr[i]] + 1
} else {
map[arr[i]] = 1
}
}
for (let i = 0; i < arr.length; i++) {
if (map[arr[i]] === 1)
return i
}
return -1
}
console.log(findFirstOnlyOneLetter('ggsafDZFaesrewa'))
//4
function findFirstOnlyOneLetter(str) {
if (!str) return -1
const arr = str.split('')
for (let i = 0; i < arr.length; i++) {
const indexOf = arr.indexOf(arr[i])
const lastIndexOf = arr.lastIndexOf(arr[i])
if (indexOf === lastIndexOf) {
return i
}
}
return -1
}
console.log(findFirstOnlyOneLetter('ggsafDZFaesrewa'))
//4