数组 Array
Array 实战题目
- 两数之和(近半年内,字节跳动在面试中考查此题达到 152 次)
1、声明一个map, 遍历数组,如果target - nums[i] 的差值不在map中,将该值存在map中(值为key, 下标为index) 2、如果target - num 在 map中,返回 num 对应的下表和map 中对应key 的index
// 两数之和
const towNum = function(nums, target){
let numMap = new Map()
for(let i=0; i<nums.length; i++){
if(numMap.has(target - nums[i])){
return [i, numMap.get(target - nums[i])]
}else{
numMap.set(nums[i], i)
}
}
}
- 盛最多水的容器(腾讯、百度、字节跳动在近半年内面试常考)
暴力法 1、双层循环,i 从数组头开始, 在arr.length-1 结尾; j 从数组的i+1开始,在arr.length结尾 2、计算 area = Math.min(num[i], num[j]) * (j-i) 的面积
let maxContainer = function(nums){
if(nums === null || nums.length <2){return}
let maxArea = 0;
for(let i=0; i<nums.length-1; i++){
for(let j=1; j<nums.length; j++){
let area = Math.min(nums[i], nums[j]) * (j-i);
maxArea = Math.max(area, maxArea)
}
}
return maxArea;
}
双指针方法(左右夹逼) 1、数组的左右声明两个指针 2、左右指针未相遇时,每一次移动纵坐标小的那个指针(横坐标距离越远,纵坐标越大面积越大) 3、直到两个指针相遇
let maxContainer = function(nums){
if(nums === null || nums.length <2){return}
let i = 0, j=nums.length-1, maxArea = 0;
while(i<j){
let area = Math.min(nums[i], num[j]) * (j-i)
maxArea = Math.max(area, maxArea);
if(nums[i] < nums[j]){
i++;
}else{
j--;
}
}
return maxArea;
}
- 移动零(华为、字节跳动在近半年内面试常考)
快、慢指针 1、 声明一个指针i 慢指针 j 2、遍历数组,如果当前元素不是0,移动到j的位置,j 前进一步 3、直到数组遍历完成
/**
* // 初始化 j = 0、i = 0, i不为0,i前进1步
[0,1,0,2] // i的值非0,和 j 互换位置, j、i 前进1步
j i
[1,0,0,2] // i的值为0,不操作,i前进
j i
[1,0,0,2] // i的值非0,和 j 互换位置, j、i 前进1步
j i
[1,2,0,0] // 遍历结束
*/
/**
[1,0,0,2]
i
j
*/
let moveZero = function(nums){
if(nums === null || nums.length === 0){return }
let j = 0
for(let i=0; i<nums.length;i++){
if(nums[i] !== 0){
let temp = nums[j];
nums[j] = nums[i];
nums[i] = temp;
j++;
}
}
}
- 爬楼梯(阿里巴巴、腾讯、字节跳动在半年内面试常考)
1、问题的本质是斐波那契数列 暴力递归法(2 n)
var climbStairs = function(n) {
if(n<=2){return n}
return climbStairs(n-1) + climbStairs(n-2)
};
遍历指针移动
三指针移动法 知道前两个指针对应的值,推导第三个 推导第三个后,前两个指针向前移动
var climbStairs = function(n) {
if(n<=2){return n}
let a=1, b=2,c;
for(let i=2; i<n.length;i++){
c = a + b;
a = b;
b = c;
}
return c;
};
- 三数之和(国内、国际大厂历年面试高频老题)
双指针法 1、排序,遍历数组,左右定义两个指针 j = i+1; k = num.length-1 2、如果三个数的和等于0,两个指针向中间靠近; j++; k— 3、如果三个数的和小于0, j++, 如果三个数的和大于0, k— 4、⚠️ 需要对重复元素去重
let threeNum = function(nums){
if(nums === null || nums.length<=2 ){return []}
let threeNumArr = []
nums.sort((a, b)=>{return a-b})
for(let i=0; i<nums.length-2; i++){
if(nums[i]>0) break; // 如果i大于0, 则j、k肯定大于0 无效
if(i >= 1 && nums[i] === nums[i-1]) continue; // 对i去重
let j = i+1, k= nums.length-1;
while(j<k){
let sum = nums[i] + nums[j] + nums[k]
if(sum === 0){
threeNumArr.push([nums[i], nums[j], nums[k]])
while(j < k && nums[j] === nums[j+1]) j++; // 对j去重
while(j < k && nums[k] === nums[k-1]) k--; // 对k去重
j++;
k--;
}else if(sum < 0){
j++;
}else {
k--;
}
}
}
return threeNumArr;
}
链表 linked list
命题规律
跳表 skip List
跳表只能应用于元素有序的链表下 对标 平衡树 和 二分查找,插入/删除/搜索 都是O(log n)
如何提高链表查找的效率?
建立索引
时间复杂度:O(log n)
空间复杂度:O(n)
Linked List 实战题目
- 反转链表(字节跳动、亚马逊在半年内面试常考)
- 两两交换链表中的节点(阿里巴巴、字节跳动在半年内面试常考)
- 环形链表(阿里巴巴、字节跳动、腾讯在半年内面试常考)
- 环形链表 II
- K 个一组翻转链表(字节跳动、猿辅导在半年内面试常考)
做题的最大误区
优化的思想
做题懵逼的时候
1、暴力解法
2、最基本的问题如何解决:想最简单的问题(找最小重复单元)
计算机只能处理 for while / if else / 递归 recursion
数组常用API总结
API 用法汇总、返回值、对原数组的影响
新增数组
let arr1 = [] // 数组字面量
let arr2 = new Array(3) // 构造函数 [empty × 3] 会创建“稀疏数组”,不推荐
let arr3 = Array.of(3,3,4,5) // [3, 3, 4, 5]
let arr4 = Array.from({length:3}) //将类数组对象转为数组对象(创建给定长度的数组) [undefined, undefined, undefined]
- 数组字面量
- let arr1 = []
Array( ) / new Array( )
- 构造函数,会创建“稀疏数组”,不推荐,可以使用Array.of() 代替
new Array(3) // 传入一个参数,指函数的长度 [empty × 3]
new Array(1,2,3) // 当传入的参数大于等于2个时,指传入数组的元素 [1,2,3]
- 构造函数,会创建“稀疏数组”,不推荐,可以使用Array.of() 代替
Array.from( )
- 对于不支持ES6的,可以使用 Array.prototype.slice()
将类数组对象(array-like Object)和可遍历对象(包括Set 和 Map)转化为真正的数组
类数组对象: 本质是含有length属性的对象
let arr4 = Array.from({length:3}) //将类数组对象转为数组对象(创建给定长度的数组) [undefined, undefined, undefined]
可遍历对象:字符串、Set、Map
Array.of( )
push(val, val2)
- 向数组尾部增加一/n个元素,返回数组的新的长度, 改变原数组
unshift(val, val2)
pop()
- 删除并返回最后一个元素,改变原数组
shift()
splice(index, length, [增加元素1,增加元素2…]): 从index起,删除length个元素,[可选]增加几个元素
- 返回被删除元素组成的数组
- 改变原数组
let arr = [1,2,3]
arr.splice(1,2,2.1,2.2) // 返回删除元素组成的数组,即[2,3] 如果没有删除元素,则返回空数组
slice(startIndex, endtIndex) 剪切 返回从startIndex开始【包括】到endIndex【不包括】之间的元素组成的数组
-
拼接
concat(val, [val, val, [val]])
- 合并两个或者多个数组,返回新数组
- 不改变原数组
- 如果拼接的是数组,会合并到拼接的数组上(只会打开外层的数组)
let arr = [1,2,3]
let newArr = arr.concat(4,[5,6,[7]]) // [1, 2, 3, 4, 5, 6, [7]]
join()
- 用特定的标识符将数组中元素拼接为字符串
- 返回转换后的字符串
- 不改变原数组
- sort()
- 按ascii码排序
- 返回排序后的数组
- 改变原数组
- reverse()
- 颠倒数组中元素的顺序
- 返回颠倒后的数字组成的数组
- 改变原数组
- indexOf(元素,startIndex)
- 从startIndex开始,从头到尾查找元素在数组中的位置
- 如果存在,返回一个元素位置的下标,否则返回-1
- 不改变原数组
- lastIndexOf(元素,startIndex)
- 从startIndex开始,从尾到头查找元素在数组中的位置
- 如果存在,返回一个元素位置的下标,否则返回-1
- 不改变原数组
find()
- 找出数组中第一次满足条件的元素,并返回该元素,如果找不到,返回 undefined
- 不改变原数组
arr.find(function(ele, index, array){
return ele === 1
})
findIndex()
- 作用同indexOf(), 区别是参数是回调函数
- 返回第一个满足条件的下标,并停止寻找
- 不改变原数组
arr.findIndex(function(ele, index, array){
return ele === 2
})
includes(元素)
- 检查数组中是否包含某个元素,返回布尔值
- 不改变原数组
Array.isArray()
for
for(let i=0; i<arr.length;i++){
console.log(i);
console.log(arr[i]);
}
for (let item of arr)
for(let item of arr){
console.log(item) // 数组中的元素
}
for (let index in arr)
for(let index in arr){
console.log(index) // 数组下标
console.log(arr[index]) // 数组中的元素
}
forEach()
- 遍历整个数组,中途不能中断
map()
- 根据需求格式化原数组,返回「格式化后的」新数组
- 不改变原数组
let formatArr = arr.map(function(ele, index, array){
return ele + 1;
})
some()
- 对数组中的项目运行给定的函数,若存在一项或者多项符合条件,则返回true,否则返回false(返回布尔值)
- 不改变原数组
let isSomeBig = arr.some(function(ele, index, array){
return ele > 3;
})
every()
- 对数组中的每一项运行给定的函数,若每一项都符合,则返回true,否则返回false(返回布尔值)
- 不改变原数组
let isEveryBig = arr.every(function(ele, index, array){
return ele > 3;
})
reduce()
- 数组归并方法:利用指定的函数将数组中的两个值化简为一个值,并返回化简后的值
- 不改变原数组
let arrSum = arr.reduce(function(prev,cur,index,array ){
// [1] 初始变量(默认为数组的第一个元素,函数执行第一次后的返回值作为函数第二次执行的初始变量,以此类推)
// [2] 当前变量
// [3] 当前变量在数组中的索引(从0开始)
// [4] 原数组对象
return prev + cur;
})
filter()
- 返回 「数组中满足条件的元素组成的」新数组
- 不改变原数组
let filterArr = arr.filter(function(ele, index, array){ // ele: 当前元素 index:当前下标 array:原数组对象
return ele < 10;
})
flat()
- 用于将嵌套数组“拉平”
- 返回一个新数组,不改变原数组
- 默认只拉平1层,可以通过传入参数拉平多层,传flat(Infinity) 全部拉平
let arr = [1,2,3,[4],[[5]]]
let flatArr = arr.flat(); // [1,2,3,4,[5]]
let flatArr2 = arr.flat(Infinity); // [1,2,3,4,5]
flatMap()
- 对原数组每一个成员执行一个函数(Array.prototype.map()),然后对返回值组成的数组执行flat() 方法
- 只能展开一层
- 不改变原数组
let arr = [1,2,3]
let flatMap = [1,2,3].flatMap(x=>[x,x*2]) // [1,2,2,4,3,6]
fill()
- 使用给定的值,填充一个数组
- 可以接受第二个和第三个参数,用于指定填充的起始位置和结束位置
- 改变原数组
let arr = ['a','b','c'];
['a','b','c'].fill('1',1,2) // ['a','1','c'] (含头不含尾)
copyWithin()
- 在当前数组内部,将指定的位置的成员复制到其他的位置(会覆盖原有的成员)
返回当前数组,改变原有数组
Array.prototype.copyWithin(target, start = 0, end = this.length)
target(必须)从该位置开始替换
- start:从该位置开始读取数据
- end:到该位置停止读取数据
- 注意: 不会改变数组的长度,只会将元素移动到特定的位置
// 将3号位复制到0号位
[1, 2, 3, 4, 5].copyWithin(0, 3, 4)
// [4, 2, 3, 4, 5]
sort(sortFn)
- 如果省略sortFn,表示按照字符的ASCII码从小到大(升序)进行排序
- sortFu(a, b) 接收两个参数, a, b代表每次参与排序的两个项目
- 改变原数组
let arr = [1,2,3]
arr.sort(function(a,b){
console.log('a', a,'-b',b, '--a-b', a-b);
return a-b; // 从小到大
// return b-a; // 从大到小
})
console.log(arr);