前言
春招要来了,大三的我目标找到一家实习公司,趁着这段空闲时间,来温习温习数据结构与算法吧。来看数组篇吧。
数组知识点回顾
这里是在刷题过程中一些小的知识点的总结
- Array的常规方法
- 迭代方法:
every()
,some()
,forEach()
,map()
,filter()
所有参数均为(item,index,arr)
- 缩小方法:
reduce()
和reduceRight()
参数为(prev,cur,index,arr)
- Array的
includes
方法
用于检测数组中是否包含某一项
arr.includes(searchEle,searchIndex)
- ES6引入的
Set()
集合类型,不允许有重复元素
var set = new Set();
- 求数组中最大值的几种方法
- 方法一:ES6的拓展运算符
var array = [1,2,3,4]
return Math.max(...array)
- 方法二:ES5的
apply
var array = [1,2,3,4]
return Math.max.apply(null,array)
- 方法三:
reduce
方法
var array = [1,2,3,4]
return arr.reduce((prev,cur,arr)=>{
return prev > cur ? prev:cur;
})
- 方法四、五
for
循环或sort
方法
- 数组的
concat
方法与slice
方法均生成另一数组副本,原本数组并不改变。而splice
方法可改变原数组。
var arr1 = [1,2,3,4]
var arr2 = [5]
var arr3 = arr1.concat(arr2) //[1,2,3,4,5]
return arr1 //[1,2,3,4]
arr1.slice(0,1)
return arr1 //[1,2,3,4]
arr1.splice(0,1)
return arr1 //[2,3,4]
6.find()
函数
查询数组中某一项
let age = [1,2,3]
age.find(item => 1) //1
age.find(item > 2) //3
- 一些小问题
[1,2,3] === [1,2,3] //false
[1,2,3] == "1,2,3" //true
new Set([1,1,2,[1,2],[1,2]])//{1,2,[1,2],[1,2]}
两个数组的交集_简单
给定两个数组:编写一个函数来计算它们的交集。 示例 1: 输入: nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2] 示例 2: 输入: nums1 = [4,9,5], nums2 = [9,4,9,8,4] 输出: [9,4]
说明:输出结果中的每个元素一定是唯一的。我们可以不考虑输出结果的顺序。
1. 使用filter()
函数暴力求解
两次遍历数组,将相同项push
进结果数组中,再使用filter()
函数进行重复元素过滤。
/**
* @param {number[]} nums1
* @param {number[]} nums2
* @return {number[]}
*/
var intersection = function(nums1, nums2) {
var tempArray = [];
for(var i of nums1){
for(var j of nums2){
if(i===j){
tempArray.push(i);
}
}
}
var returnNum = [];
returnNum = tempArray.filter(function(item, index, arr){
return index === arr.indexOf(item);
})
return returnNum;
};
- 使用ES6的
Set()
先试用filter()
函数对两个数组进行过滤,再调用ES6的Set()
来实现去重。
/**
* @param {number[]} nums1
* @param {number[]} nums2
* @return {number[]}
*/
var intersection = function(nums1, nums2) {
return [...new Set(nums1.filter((item) => {
return nums2.includes(item);
}))]
};
3. 排序后双指针查找
传统解法,对数组分别排序后,进行双指针遍历。
/**
* @param {number[]} nums1
* @param {number[]} nums2
* @return {number[]}
*/
var intersection = function(nums1, nums2) {
nums1 = nums1.sort((a,b) => a-b);
nums2 = nums2.sort((a,b) => a-b);
let returnArray = new Set();
let i=0;
let j=0;
while(i<nums1.length&&j<nums2.length){
if(nums1[i]<nums2[j]){
i++;
}else if(nums1[i]>nums2[j]){
j++;
}else{
returnArray.add(nums1[i]);
i++;
j++;
}
}
return [... returnArray]
};
合并区间_简单
给出一个区间的集合,请合并所有重叠的区间.
示例 1: 输入: [[1,3],[2,6],[8,10],[15,18]] 输出:[[1,6],[8,10],[15,18]] 解释: 区间[1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].
示例 2: 输入:[[1,4],[4,5]] 输出: [[1,5]] 解释: 区间 [1,4] 和 [4,5] 可被视为重叠区间。
/**
* @param {number[][]} intervals
* @return {number[][]}
*/
var merge = function(intervals) {
if(intervals.length===0||intervals.length===1){
return intervals;
}
let returnArray = [];
intervals = intervals.sort((a,b) => a[0]-b[0]);
returnArray.push(intervals.reduce((pre,cur) =>{
if(pre[1]>=cur[0]){
if(pre[1]<=cur[1]){
pre[1]=cur[1];
}
return pre;
}else{
returnArray.push(pre);
return cur;
}
}));
return returnArray;
};
将数组分成和相等的三个部分_简单
给定一个整数数组 A,只有我们可以将其划分为三个和相等的非空部分时才返回 true,否则返回 false。
形式上,如果我们可以找出索引i+1 < j 且满足 (A[0] + A[1] + … + A[i] == A[i+1] + A[i+2] + … +A[j-1] == A[j] + A[j-1] + … + A[A.length - 1]) 就可以将数组三等分。
示例 1:输出:[0,2,1,-6,6,-7,9,1,2,0,1] 输出:true 解释:0 + 2 + 1 = -6 + 6 - 7 + 9 + 1= 2 + 0 + 1
示例 2: 输入:[0,2,1,-6,6,7,9,-1,2,0,1] 输出:false 示例 3: 输入[3,3,6,5,-2,2,5,1,-9,4] 输出:true 解释:3 + 3 = 6 = 5 - 2 + 2 + 5 + 1 -
9 + 4
提示: 3 <= A.length <= 50000
-10000 <= A[i] <= 10000
/**
* @param {number[]} A
* @return {boolean}
*/
var canThreePartsEqualSum = function (A) {
if (A.forEach(item => item === 0)) return true
let sum = A.sum()
let avg = sum / 3
let ins = 0, count = 0
for (const i of A) {
ins = ins + i
if (ins === avg) {
count++
ins = 0
}
}
return count === 3
};
Array.prototype.sum = function () {
return this.reduce((pre, cur) => pre + cur)
}
两数之和_简单
给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。
你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。 示例: 给定 nums = [2, 7, 11, 15],
target = 9 因为 nums[0] + nums[1] = 2 + 7 = 9 所以返回 [0, 1]
解法如下,可以两次遍历暴力求解;同样也可以根据数组中值与索引的关系建立Map()
字典。
1. 暴力求解
最好的办法莫过于暴力梭哈,两次遍历数组,满足条件时返回结果。
时间复杂度:O(n)=n²
/**
* @param {number[]} nums
* @param {number} target
* @return {number[]}
*/
var twoSum = function(nums, target) {
for(var i=0;i<nums.length;i++){
for(var j=i+1;j<nums.length;j++){
if(nums[i]+nums[j]===target){
return [i,j]
}
}
}
};
2. HashMap求解
根据数组中数值和索引的关系建立Map()
这一数据结构,可以省去一次遍历,通过检查Map
中待匹配两数的索引是否存在即可求解。
时间复杂度O(n)=n
/**
* @param {number[]} nums
* @param {number} target
* @return {number[]}
*/
var twoSum = function(nums, target) {
var resultArray = [];
var hashSet = new Map();
for(var i=0;i<nums.length;i++){
var tempValue = target - nums[i];
var tempIndex = hashSet.get(tempValue);
if(hashSet.has(tempValue) && tempIndex != i){
resultArray.push(tempIndex);
resultArray.push(i);
return resultArray;
}else{
hashSet.set(nums[i],i);
}
}
return resultArray;
};
买股票的最佳时机_简单
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
如果你最多只允许完成一笔交易(即买入和卖出一支股票),设计一个算法来计算你所能获取的最大利润。 注意你不能在买入股票前卖出股票。
示例1:输入: [7,1,5,3,6,4] 输出: 5
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 =6)的时候卖出,最大利润 = 6-1 = 5 。注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格。
示例 2: 输入: [7,6,4,3,1] 输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。
可使用对数组两轮遍历得到每种情况的买卖数值,进而求最大值。更好的办法在于动态规划,遍历数组得到最小值,并比较得出后续数组元素与最小值的差值利润的最大值。
1. 暴力求解
两次遍历数组,将每次买卖情况的利润均存入另一数组中,最后取得数组中最大值。
时间复杂度O(n)=n²
/**
* @param {number[]} prices
* @return {number}
*/
var maxProfit = function(prices) {
var maxArray = [0];
var length = prices.length;
for(var i=0;i<length;i++){
for(var j=i+1;j<length;j++){
if(prices[j]>prices[i]){
maxArray.push(prices[j]-prices[i])
}
}
}
return Math.max(...maxArray)
};
2. 动态规划
单次遍历数组,找到最小值。然后比较后续数组与最小值的差值的最大值。
/**
* @param {number[]} prices
* @return {number}
*/
//curMin表示当前最小值
//max表示每次买卖利润中的最大值
var maxProfit = function(prices) {
var curMin = prices[0];
var max = 0;
for(const i of prices){
if(curMin > i){
curMin = i;
continue;
}
max = Math.max(max,i-curMin);
}
return max;
};
最大子序和_简单
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
示例: 输入:[-2,1,-3,4,-1,2,1,-5,4], 输出: 6 解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
1. 动态规划
与上题相似,遍历一次数组,若当前累计值为正值可继续保留,否则将当前遍历值设为累计值。最后比较得出所有遍历过程中的最大值。
/**
* @param {number[]} nums
* @return {number}
*/
//sum表示当前遍历累计值
//ins表示遍历过程中最大值
var maxSubArray = function(nums) {
var sum=0;
var ins=nums[0];
for(const num of nums){
if(sum < 0){
sum = num;
}else{
sum = sum + num;
}
ins = Math.max(sum,ins);
}
return ins;
};
合并两个有序数组_简单
给定两个有序整数数组 nums1 和 nums2,将 nums2 合并到 nums1 中,使得 num1 成为一个有序数组。
说明: 初始化 nums1 和 nums2 的元素数量分别为 m 和 n。 你可以假设 nums1 有足够的空间(空间大小大于或等于 m + n)来保存nums2 中的元素。
示例: 输入: nums1 = [1,2,3,0,0,0], m = 3 nums2 = [2,5,6], n =3
输出: [1,2,2,3,5,6]
关键在于要对数组本身进行操作,而不是生成一个副本数组。因此直接在数组上进行slice
和concat
等方法均无效
1. 重新赋值。
将数组去除无效值并重新排序后,循环数组,进行重新赋值。
时间复杂度O(n)=n
/**
* @param {number[]} nums1
* @param {number} m
* @param {number[]} nums2
* @param {number} n
* @return {void} Do not return anything, modify nums1 in-place instead.
*/
var merge = function(nums1, m, nums2, n) {
var tempArray = nums1.splice(0,m).concat(nums2)
tempArray.sort((a,b)=>a-b);
for(var i=0;i<tempArray.length;i++){
nums1[i]=tempArray[i]
}
};
2. 直接操作原数组
直接向原数组中多余位赋值,进而重新排序。
时间复杂度:O(n)=n²
/**
* @param {number[]} nums1
* @param {number} m
* @param {number[]} nums2
* @param {number} n
* @return {void} Do not return anything, modify nums1 in-place instead.
*/
var me
var merge = function(nums1, m, nums2, n) {
for(var i=0;i<n;i++){
nums1[m+i]=nums2[i];
}
nums1.sort((a,b)=>a-b);
};
盛水最多的容器_中等
给定 n 个非负整数 a1,a2,…,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i的两个端点分别为 (i, ai) 和 (i, 0)。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。
说明:你不能倾斜容器,且 n 的值至少为2。
图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。
示例:输入: [1,8,6,2,5,4,8,3,7] 输出: 49
本题可暴力求解,两次遍历数组;同样也可以使用双指针只遍历一次,得到最大值。
- 暴力求解
通过两次遍历数组中元素,得到每次所盛水的数值,进而比较出其中最大值。
时间复杂度:O(n)=n²
/**
* @param {number[]} height
* @return {number}
*/
var maxArea = function(height) {
function calAre(a,b){
var temp = Math.abs(a-b);
return Math.min(temp*height[a],temp*height[b]);
}
var len = height.length;
var ins = calAre(0,1);
for(var i=0;i<len;i++){
for(var j=i+1;j<len;j++){
if(calAre(i,j) > ins){
ins = calAre(i,j);
}
}
}
return ins;
};
2. 双指针求解
可简化为单次遍历,左侧指针从左向右遍历,右侧指针从右向左遍历。进而得到最大值。
时间复杂度:O(n)=n²
/**
* @param {number[]} height
* @return {number}
*/
var maxArea = function(height) {
var left = 0;
var right = height.length-1;
var ins = 0;
var maxArea = calAre(left,right);
while(left < right){
if(height[left] < height[right]){
ins = calAre(left,right);
left++;
}else{
ins = calAre(left,right);
right--
}
maxArea = Math.max(maxArea,ins);
}
return maxArea;
function calAre(i,j){
var temp = j-i;
return Math.min(temp*height[i],temp*height[j]);
}
};
会议室_中等
给定一个会议时间安排的数组,每个会议时间都会包括开始和结束的时间 [[s1,e1],[s2,e2],…] (si < ei),为避免会议冲突,同时要考虑充分利用会议室资源,请你计算至少需要多少间会议室,才能满足这些会议安排。
示例 1:输入: [[0, 30],[5, 10],[15, 20]] 输出: 2
示例 2: 输入: [[7,10],[2,4]] 输出: 1
1. 双重遍历
现将数组排序,再排序后对数组两次遍历,第二次遍历中首元素遍历至当前元素,若两个比较元素可共享会议室,即splice
前一元素。同时将当前数组的第一位元素置为0。
时间复杂度:O(n)=n²
/**
* @param {number[][]} intervals
* @return {number}
*/
var minMeetingRooms = function(intervals) {
if (!intervals.length) return 0
if (intervals.length==1) return 1;
intervals = intervals.sort((a,b)=>a[0]-b[0]);
for(var i=1;i<intervals.length;i++){
const tempNow = intervals[i];
for(j=0;j<i;j++){
const tempLast = intervals[j]
if(tempNow[0]>=tempLast[1]){
intervals.splice(j,1)
tempNow[0]=0
i--;
}
}
}
return intervals.length
};
接雨水_困难
给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6
个单位的雨水(蓝色部分表示雨水)。 示例: 输入: [0,1,0,2,1,0,1,3,2,1,2,1] 输出: 6
本题主要在于分析所求位置雨水量与左右最大高度的关系。所求位置雨水量是左侧高度最大值和右侧高度最大值中较小者与本身高度的差值。为此两次循环或是双指针均可求解。
- 暴力求解
根据分析,我们能够看出,每个数组元素的接水量等于其左侧高度最大值与右侧高度最大值中的最小值与该数组元素高度的差值。
/**
* @param {number[]} height
* @return {number}
*/
var trap = function(height) {
var ins = 0;
for(var i=0;i<height.length;i++){
var lMax = 0;//左侧高度最大值
var rMax = 0;//右侧高度最大值
for(var j=i;j>=0;j--){
lMax = Math.max(lMax,height[j]);
}
for(var k=i;k<height.length;k++){
rMax = Math.max(rMax,height[k])
}
ins += Math.min(lMax,rMax) - height[i];
}
return ins;
};
2. 双指针
使用双指针将两次遍历数组转化为一次遍历。
/**
* @param {number[]} height
* @return {number}
*/
var trap = function(height) {
var left=0;
var right=height.length-1;
var lMax = 0;
var rMax = 0;
var ins = 0;
while(left < right){
lMax = Math.max(lMax,height[left]);
rMax = Math.max(rMax,height[right]);
if(lMax < height[right]){
ins += lMax - height[left];
left++;
}else{
ins += rMax - height[right];
right--;
}
}
return ins;
};
三数之和
- 双指针,先排除特殊情况
给定一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0
?找出所有满足条件且不重复的三元组。 注意:答案中不可以包含重复的三元组。 示例: 给定数组 nums = [-1, 0, 1, 2,
-1, -4], 满足要求的三元组集合为: [ [-1, 0, 1], [-1, -1, 2] ]
/**
* @param {number[]} nums
* @return {number[][]}
*/
// 3.07
var threeSum = function (nums) {
if (nums === null || nums.length < 3) return []
let res = []
const len = nums.length - 1
nums.sort((a, b) => a - b)
for (let i = 0; i < len + 1; i++) {
if (nums[0] > 0 || nums[len] < 0) break
if (i > 0 && nums[i] === nums[i - 1]) continue
let left = i + 1, right = len
while (left < right) {
let sum = nums[left] + nums[i] + nums[right]
if (sum === 0) {
res.push([nums[left], nums[i], nums[right]])
while (left < right && nums[left] === nums[left+1]) left++
while (left < right && nums[right] === nums[right-1]) right--
left++
right--
}
else if (sum < 0) left++
else if (sum > 0) right--
}
}
return res
}