记录一下数组刷题目录和题解
具体题目和题解参考代码随想录和leetcode官方
数组的基础知识(常用API)
具体使用要看MDN文档
方法名 | 方法描述 |
---|---|
concat | 连接2个或更多数组,并返回结果 |
every | 对数组中的每一项运行给定函数,如果该函数对每一项都返回 true,则返回true, 否则返回false |
filter | 对数组中的每一项运行给定函数,返回该函数会返回 true的项组成的数组 |
forEach | 对数组中的每一项运行给定函数。这个方法没有返回值 |
join | 将所有的数组元素连接成一个字符串 |
indexOf | 返回第一个与给定参数相等的数组元素的索引,没有找到则返回-1 |
lastIndexOf | 连接2个或更多数组,并返回结果 |
map | 对数组中的每一项运行给定函数,返回每次函数调用的结果组成的数组 |
reverse | 颠倒数组中元素的顺序,原先第一个元素现在变成最后一个,同样原先的最后一个元素变成了现在的第一个 |
slice | 传入索引值,将数组里对应索引范围内的元素作为新数组返回 |
some | 对数组中的每一项运行给定函数,如果任一项返回 true,则结果为true, 并且迭代结束 |
sort | 按照字母顺序对数组排序,支持传入指定排序方法的函数作为参数 |
toString | 将数组作为字符串返回 |
valueOf | 和 toString类似,将数组作为字符串返回 |
reduce | reduce() 方法对数组中的每个元素按序执行一个由您提供的 reducer 函数,每一次运行 reducer 会将先前元素的计算结果作为参数传入,最后将其结果汇总为单个返回值。 |
reduce 案例
求数组所有值的和
let total = [ 0, 1, 2, 3 ].reduce(
( previousValue, currentValue ) => previousValue + currentValue,
0
)
累加对象数组里的值
let initialValue = 0
let sum = [{x: 1}, {x: 2}, {x: 3}].reduce(
(previousValue, currentValue) => previousValue + currentValue.x
, initialValue
)
console.log(sum) // logs 6
将二维数组转化为一维
let flattened = [[0, 1], [2, 3], [4, 5]].reduce(
( previousValue, currentValue ) => previousValue.concat(currentValue),
[]
)
计算数组中每个元素出现的次数
let names = ['Alice', 'Bob', 'Tiff', 'Bruce', 'Alice']
let countedNames = names.reduce(function (allNames, name) {
if (name in allNames) {
allNames[name]++
}
else {
allNames[name] = 1
}
return allNames
}, {})
// countedNames is:
// { 'Alice': 2, 'Bob': 1, 'Tiff': 1, 'Bruce': 1 }
刷题
二分查找
地址:https://leetcode-cn.com/problems/binary-search/
二分查找涉及的很多的边界条件,逻辑比较简单,但就是写不好。例如到底是 while(left < right)
还是 while(left <= right)
,到底是right = middle
呢,还是要right = middle - 1
呢
写二分法,区间的定义一般为两种,左闭右闭即[left, right],或者左闭右开即[left, right)。
我就主要掌握一种,左闭右闭。
代码:
var search = function(nums, target) {
// 定义target在左闭右闭的区间里,[left, right]
let left = 0;
let right = nums.length - 1;
// 当left==right,区间[left, right]依然有效,所以用 <=
while(left <= right) {
// 防止溢出 等同于(left + right)/2
let mid = left + Math.floor((right - left) / 2);
if(nums[mid] > target) {
// target 在左区间,所以[left, middle - 1]
right = mid - 1;
} else if(nums[mid] < target) {
// target 在右区间,所以[middle + 1, right]
left = mid + 1;
} else {
// 数组中找到目标值,直接返回下标
return mid;
}
}
return -1;
};
移除元素
地址:https://leetcode-cn.com/problems/remove-element/
数组的移除元素有两种方法:
- 暴力法:两层for循环,一个for循环遍历数组元素 ,第二个for循环更新数组。
- 双指针法:通过一个前指针和后指针在一个for循环下完成两个for循环的工作。
暴力法的时间复杂达到O(n*n) 就不展示了,详细看看双指针法。
删除过程图示:
// 简化版双指针法 O(n)
var removeElement = function(nums, val) {
let left = 0;
for(let right = 0; right < nums.length; right++) {
if(nums[right] !== val) {
nums[left] = nums[right];
left++;
}
}
return left;
};
有序数组的平方
地址:https://leetcode-cn.com/problems/squares-of-a-sorted-array/
这题也是两个方法解决
- 暴力法:每个数平方之后,排个序
- 双指针法
详细讲解双指针法:
数组其实是有序的, 只不过负数平方之后可能成为最大数了。
那么数组平方的最大值就在数组的两端,不是最左边就是最右边,不可能是中间。
此时可以考虑双指针法了,i指向起始位置,j指向终止位置。
定义一个新数组result,和A数组一样的大小,让k指向result数组终止位置。
如果A[i] * A[i] < A[j] * A[j]
那么result[k--] = A[j] * A[j];
。
如果A[i] * A[i] >= A[j] * A[j]
那么result[k--] = A[i] * A[i];
。
如动画所示:
代码:
var sortedSquares = function(nums) {
let n = nums.length;
let newArr = new Array(n).fill(0);
let i = 0;
let j = n - 1;
let k = n - 1;
while(i <= j) {
if(nums[i] * nums[i] < nums[j] * nums[j]) {
newArr[k--] = nums[j] * nums[j];
j--;
} else {
newArr[k--] = nums[i] * nums[i];
i++;
}
}
return newArr
};
长度最小的子数组
地址:https://leetcode-cn.com/problems/minimum-size-subarray-sum/
代码随想录上讲解的滑动窗口讲的非常好。
地址:https://www.programmercarl.com/0209.%E9%95%BF%E5%BA%A6%E6%9C%80%E5%B0%8F%E7%9A%84%E5%AD%90%E6%95%B0%E7%BB%84.html#_209-%E9%95%BF%E5%BA%A6%E6%9C%80%E5%B0%8F%E7%9A%84%E5%AD%90%E6%95%B0%E7%BB%84
接下来就开始介绍数组操作中另一个重要的方法:滑动窗口。
所谓滑动窗口,就是不断的调节子序列的起始位置和终止位置,从而得出我们要想的结果。
这里还是以题目中的示例来举例,s=7, 数组是 2,3,1,2,4,3,来看一下查找的过程:
最后找到 4,3 是最短距离。
其实从动画中可以发现滑动窗口也可以理解为双指针法的一种!只不过这种解法更像是一个窗口的移动,所以叫做滑动窗口更适合一些。
在本题中实现滑动窗口,主要确定如下三点:
- 窗口内是什么?
- 如何移动窗口的起始位置?
- 如何移动窗口的结束位置?
窗口就是 满足其和 ≥ s 的长度最小的 连续 子数组。
窗口的起始位置如何移动:如果当前窗口的值大于s了,窗口就要向前移动了(也就是该缩小了)。
窗口的结束位置如何移动:窗口的结束位置就是遍历数组的指针,窗口的起始位置设置为数组的起始位置就可以了。
代码
var minSubArrayLen = function(target, nums) {
// 长度为 len, res 为子数组的长度
let len = nums.length;
// 如果所有子数组相加都没有超过 target,就返回 0
// 所以就设置 res 的初始长度为 len + 1
let res = len + 1;
// 滑动窗口的 两个指针 left right
let left = 0;
// 累加的和
let sum = 0;
// 开始窗口滑动
for(let right = 0; right < len; right++) {
sum += nums[right];
// 注意这里使用while,每次更新 left(起始位置),并不断比较子序列是否符合条件
while(sum >= target) {
// 取更短的序列
res = res < right - left + 1? res : right - left + 1;
// 这里体现出滑动窗口的精髓之处,不断变更left(子序列的起始位置)
sum -= nums[left++];
}
}
// 如果res超过len,就返回0,说明没有符合条件的子序列
return res > len ? 0 : res;
};
螺旋矩阵
地址:https://leetcode-cn.com/problems/spiral-matrix-ii/
画个图就能理解了。
模拟顺时针画矩阵的过程:
- 填充上行从左到右
- 填充右列从上到下
- 填充下行从右到左
- 填充左列从下到上
由外向内一圈一圈这么画下去。
代码:
var generateMatrix = function(n) {
// js 创建二维数组的方式
let matrix = new Array(n).fill(0).map(() => new Array(n).fill(0));
// 定义每循环一圈的起始位置
let startx = 0;
let starty = 0;
// 每个圈循环几次,例如n为奇数3,那么loop = 1只是循环一圈,矩阵中间的值需要单独处理
let loop = Math.floor(n / 2);
// 矩阵中间的位置,例如 n 为 3,中间的位置为(1,1) n 为 5,中间的位置为(2,2)
let mid = Math.floor(n / 2);
// 用来给矩阵每一个空格赋值
let count = 1;
// 每一圈循环,需要控制每一条边遍历的长度
let offset = 1;
let i,j;
while(loop--) {
i = startx;
j = starty;
// 下面开始的四个for就是循环模拟转了一圈
// 模拟填充上行从左到右(左闭右开)
for(j = starty; j < starty + n - offset; j++) {
matrix[i][j] = count++;
}
// 模拟填充右列从上到下(左闭右开)
for(i = startx; i < startx + n - offset; i++) {
matrix[i][j] = count++;
}
// 模拟填充下行从右到左(左闭右开)
for(; j > starty; j--) {
matrix[i][j] = count++;
}
// 模拟填充左列从下到上(左闭右开)
for(; i > startx; i--) {
matrix[i][j] = count++;
}
// 第二圈开始的时候,起始位置要各自加1, 例如:第一圈起始位置是(0, 0),第二圈起始位置是(1, 1)
startx++;
starty++;
// offset 控制每一圈里每一条边遍历的长度
offset += 2;
}
// 如果n为奇数的话,需要单独给矩阵最中间的位置赋值
if(n % 2) {
matrix[mid][mid] = count;
}
return matrix
};
三数之和
地址:https://leetcode.cn/problems/3sum/
思路:两个循环,固定第一个指针,后面两个指针慢慢往中间夹
/**
* @param {number[]} nums
* @return {number[][]}
*/
var threeSum = function (nums) {
nums.sort((a, b) => a - b);
// console.log(nums);
let res = [];
// 定住第一个指针
for(let i = 0; i < nums.length; i++) {
// 先判重
while(nums[i - 1] == nums[i]) {
i++;
}
// 确定两个指针
let p = i + 1;
let q = nums.length - 1;
while(p < q) {
let sum = nums[i] + nums[p] + nums[q];
if(sum === 0) {
res.push([nums[i], nums[p], nums[q]])
p++;
while(nums[p - 1] == nums[p]) {
p++;
}
q--;
while(nums[q] === nums[q + 1]) {
q--;
}
} else if (sum > 0) {
q--;
while(nums[q] == nums[q + 1]) {
q--;
}
} else {
p++;
while(nums[p - 1] == nums[p]) {
p++;
}
}
}
}
return res;
};