977.有序数组的平方
给你一个按 非递减顺序 排序的整数数组 nums,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。
示例 1:
- 输入:nums = [-4,-1,0,3,10]
- 输出:[0,1,9,16,100]
- 解释:平方后,数组变为 [16,1,0,9,100],排序后,数组变为 [0,1,9,16,100]
示例 2:
- 输入:nums = [-7,-3,2,3,11]
- 输出:[4,9,9,49,121]
有序数组 ——> 双指针法(左右指针)
/**
* @param {number[]} nums
* @return {number[]}
*/
// 时间复杂度O(n) 空间复杂度O(n)
// 平方后大数在两端,左右指针
// 返回每个数字的平方组成的新数组(长度和原数组一致),非递减顺序(大数在右端)
var sortedSquares = function (nums) {
let left = 0, right = nums.length - 1;
let squares = new Array(nums.length);
let k = squares.length - 1;
while (left <= right) {
// 新数组从后往前(从大到小)赋值
if (nums[left] * nums[left] <= nums[right] * nums[right]) {
squares[k] = nums[right] * nums[right];
k--; right--;
}
else {
squares[k] = nums[left] * nums[left];
k--; left++;
}
}
return squares;
};
// res[k--] = right; k--后置递减运算符,先赋值后递减
// res[--k] = right; --k前置递减运算符,先递减后赋值
209.长度最小的子数组
给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的 连续 子数组,并返回其长度。如果不存在符合条件的子数组,返回 0。
示例:
- 输入:s = 7, nums = [2,3,1,2,4,3]
- 输出:2
- 解释:子数组 [4,3] 是该条件下的长度最小的子数组。
提示:
- 1 <= target <= 10^9
- 1 <= nums.length <= 10^5
- 1 <= nums[i] <= 10^5
找某条件下长度最小/最大的子数组 ——> 滑动窗口(快慢指针)
所谓滑动窗口,就是不断的调节子序列的起始位置和终止位置,从而得出我们要想的结果。
只用一个for循环,循环的索引表示 滑动窗口的终止位置。根据当前子序列满足某条件的情况,不断调节子序列的起始位置。
/**
* @param {number} target
* @param {number[]} nums
* @return {number}
*/
// 某条件下长度最小/最大的子数组 ——> 滑动窗口(快慢指针)
var minSubArrayLen = function (target, nums) {
let result = Infinity; // 声明子数组长度并赋值正无穷,Infinity(找最小,初始为Infinity)
let sum = 0, subLength = 0; // 用于记录总和和满足条件的数组的长度
let slow = 0, fast = 0; // 快慢指针用于滑动窗口
while (fast < nums.length) { // 窗口的结束位置(快指针)小于数组长度
sum += nums[fast]; // 当前窗口中的总和
while (sum >= target) { // 总和大于等于target
subLength = (fast - slow + 1); // 记录窗口大小
result = result < subLength ? result : subLength; // 比较得到满足条件的长度最小的子数组
sum -= nums[slow++]; // 总和减去窗口起始位置的值,并向后移动窗口的起始位置(慢指针)
} // 总和小于target时结束循环
fast++; // 向后移动窗口的结束位置
} // 窗口的结束位置滑到数组末尾后结束循环
return result !== Infinity ? result : 0; // 判断是否存在符合条件的子数组,存在返回其长度,不存在返回0
};
相关题目1:904.水果成篮
你正在探访一家农场,农场从左到右种植了一排果树。这些树用一个整数数组 fruits
表示,其中 fruits[i]
是第 i
棵树上的水果 种类 。
你想要尽可能多地收集水果。然而,农场的主人设定了一些严格的规矩,你必须按照要求采摘水果:
- 你只有 两个 篮子,并且每个篮子只能装 单一类型 的水果。每个篮子能够装的水果总量没有限制。
- 你可以选择任意一棵树开始采摘,你必须从 每棵 树(包括开始采摘的树)上 恰好摘一个水果 。采摘的水果应当符合篮子中的水果类型。每采摘一次,你将会向右移动到下一棵树,并继续采摘。
- 一旦你走到某棵树前,但水果不符合篮子的水果类型,那么就必须停止采摘。
给你一个整数数组 fruits
,返回你可以收集的水果的 最大 数目。
某条件下长度最大的子数组 ——> 滑动窗口
/**
* @param {number[]} fruits
* @return {number}
*/
// 某条件下长度最大的子数组 ——> 滑动窗口
// 哈希表存储键值对 ——> 限制条件
// hashMap.set(key,value)添加/修改元素;hashMap.get(key)获取当前元素对应的值;hashMap.delete(key)删除该元素
// 时间复杂度O(n);空间复杂度O(1) Map对象中的键值对数量不会超过2
var totalFruit = function (fruits) {
let fast = 0, slow = 0; // 快慢指针用于滑动窗口
let sum = 0; // 可以采摘的果树的最大数目(找最大,初始为0)
let basket = new Map(); // 使用哈希表存储水果类型和个数,两个篮子->两种类型(不超过两个键值对)
while (fast < fruits.length) { // 快指针滑到末尾之前
// 添加当前水果到篮子中
basket.set(fruits[fast], (basket.get(fruits[fast]) || 0) + 1);
// 如果超过两种类型,则从左端移除水果直到只有两种类型
while (basket.size > 2) {
basket.set(fruits[slow], basket.get(fruits[slow]) - 1);
if (basket.get(fruits[slow]) === 0) basket.delete(fruits[slow]);
slow++;
}
sum = Math.max(sum, fast - slow + 1); // 可以采摘的果树的最大数目
fast++; // 快指针向前遍历
}
return sum;
};
相关题目2:76.最小覆盖字串
给你一个字符串 s
、一个字符串 t
。返回 s
中涵盖 t
所有字符的最小子串。如果 s
中不存在涵盖 t
所有字符的子串,则返回空字符串 ""
。
注意:
- 对于
t
中重复字符,我们寻找的子字符串中该字符数量必须不少于t
中该字符数量。 - 如果
s
中存在这样的子串,我们保证它是唯一的答案。
某条件下长度最小的字串 ——> 滑动窗口
/**
* @param {string} s
* @param {string} t
* @return {string}
*/
// 某条件下长度最小的字串 ——> 滑动窗口
// hashMap.has(key)判断哈希表中是否存在某个元素;hashMap.get(key)获取某个元素对应的值
// string.substr(startIndex,length)字符串从起始索引截取并返回某个长度的子串
// 时间复杂度O(n^2) 空间复杂度O(n)
var minWindow = function (s, t) {
let slow = 0, fast = 0; // 快慢指针 ——> 记录窗口起始位置
let need = new Map(); // 用哈希表存储t子字符串中所需字符及对应个数
for (let c of t) need.set(c, (need.get(c) || 0) + 1); // t中字符及对应个数
let window = new Map(); // 用哈希表存储当前窗口中t需要的字符及对应个数
let valid = 0; // 满足条件的字符
let start = 0, len = Infinity; // 记录字串起始索引及长度(找最小,初始为Infinity)
while (fast < s.length) { // 快指针滑到字符串尾之前
if (need.get(s[fast]) !== 0) { // 如果s[fast]是t中需要的字符
window.set(s[fast], (window.get(s[fast]) || 0) + 1); // 存储在window对象中
if (window.get(s[fast]) === need.get(s[fast])) valid++; // 如果window中某一字符数量满足t中需要,vaild++
}
fast++; // 扩大窗口
while (valid === need.size) { // 如果need中字符数量均满足(覆盖子串)
if (fast - slow < len) { // 更新长度以找到最小覆盖子串
start = slow; // 更新子串起始索引
len = fast - slow; // 更新子串长度(上面已经fast++了,所以fast-slow是字串长度,不需要再加1)
}
if (need.get(s[slow]) !== 0) { // 如果s[slow]是t中需要的字符
window.set(s[slow], window.get(s[slow]) - 1); // window中该字符个数减1
if (window.get(s[slow]) < need.get(s[slow])) valid--; // 直到window中该字符不满足t需要,valid--
}
slow++; // 缩小窗口
}
}
// 没有符合条件的子字符串,返回空字符串
// substr() 从起始索引提取字符串中指定个数的字符
return len === Infinity ? "" : s.substr(start, len);
};
59.螺旋矩阵II
给定一个正整数 n,生成一个包含 1 到 n^2 所有元素,且元素按顺时针顺序螺旋排列的正方形矩阵。
示例:
输入: 3 输出: [ [ 1, 2, 3 ], [ 8, 9, 4 ], [ 7, 6, 5 ] ]
方法一:坚持循环不变量原则(左开右闭),模拟顺时针画矩阵的过程:
- 填充上行从左到右
- 填充右列从上到下
- 填充下行从右到左
- 填充左列从下到上
/**
* @param {number} n
* @return {number[][]}
*/
var generateMatrix = function (n) {
let loop = Math.floor(n / 2); // 顺时针为1圈,需要转的圈数
let mid = Math.floor(n / 2); // 如果n为奇数,需要额外填入中心点
let offset = 1; // 四条边每条边控制一个左闭右开的区间,最后一个元素不填
let x = 0, y = 0; // 每一圈的起始点
let matrix = new Array(n).fill(0).map(() => new Array(n).fill(0)); // n*n的二维数组,默认填充0
let value = 1; // 要填充的元素
while (loop--) {
let row = x, col = y; // 每一圈的起始行和列
for (; col < n - offset; col++) { // 从左到右依次填充
matrix[row][col] = value++;
}
for (; row < n - offset; row++) { // 从上到下依次填充
matrix[row][col] = value++;
}
for (; col > y; col--) { // 从右到左依次填充
matrix[row][col] = value++;
}
for (; row > x; row--) { // 从下到上依次填充
matrix[row][col] = value++;
}
x++; y++; // 下一圈的起始点
offset++; // 更新偏移量
}
if (n % 2 !== 0) { // 如果n为奇数
matrix[mid][mid] = value; // 填充中心元素
}
return matrix;
};
方法二:动态更新边界,在每个方向填充完成后检查边界是否交叉,一旦交叉立即退出循环(更通用)
/**
* @param {number} n
* @return {number[][]}
*/
// 动态更新边界top left bottom right
// 在每个方向填充完成后检查边界是否交叉,一旦交叉立即退出循环
// 无需特殊处理中心点
var generateMatrix = function (n) {
let matrix = new Array(n).fill(0).map(() => new Array(n).fill(0)); // n*n的二维数组,默认填充0
let value = 1; // 要填充的元素
let top = 0, left = 0; // 矩阵上和左边界
let bottom = n - 1, right = n - 1; // 矩阵下和右边界
while (true) {
for (let i = left; i <= right; i++) { // 从左到右依次填充
matrix[top][i] = value++;
}
if (++top > bottom) break; // 更新上边界,超出下边界时停止循环
for (let i = top; i <= bottom; i++) { // 从上到下依次填充
matrix[i][right] = value++;
}
if (--right < left) break; // 更新右边界,超出左边界时停止循环
for (let i = right; i >= left; i--) { // 从右到左依次填充
matrix[bottom][i] = value++;
}
if (--bottom < top) break; // 更新下边界,超出上边界时停止循环
for (let i = bottom; i >= top; i--) { // 从下到上依次填充
matrix[i][left] = value++;
}
if (++left > right) break; // 更新左边界,超出右边界时停止循环
}
return matrix;
};
相关题目3:54.螺旋矩阵
给你一个 m
行 n
列的矩阵 matrix
,请按照 顺时针螺旋顺序 ,返回矩阵中的所有元素。
示例 1:
输入:matrix = [[1,2,3],[4,5,6],[7,8,9]] 输出:[1,2,3,6,9,8,7,4,5]
示例 2:
输入:matrix = [[1,2,3,4],[5,6,7,8],[9,10,11,12]] 输出:[1,2,3,4,8,12,11,10,9,5,6,7]
模拟任务过程,动态更新边界,判断边界是否交叉
/**
* @param {number[][]} matrix
* @return {number[]}
*/
// array.push()向数组末尾添加元素并返回新的长度
// 注意是 先更新边界 后判断边界是否交叉
// top bottom 是行,left right是列
var spiralOrder = function (matrix) {
let m = matrix.length; // m行
let n = matrix[0].length; // n列
let orderMatrix = new Array(); // 一维数组(长度应为m*n),按顺时针螺旋顺序记录矩阵中的元素
let top = 0, left = 0; // 矩阵的上和左边界
let bottom = m - 1, right = n - 1; // 矩阵的下和右边界
while (true) {
// 从左到右遍历矩阵
for (let i = left; i <= right; i++) {
orderMatrix.push(matrix[top][i]); // 向数组末尾添加元素
}
// 更新上边界,如果超出下边界停止循环
if (++top > bottom) break;
// 从上到下遍历矩阵
for (let i = top; i <= bottom; i++) {
orderMatrix.push(matrix[i][right]); // 记录元素
}
// 更新右边界,如果超出左边界停止循环
if (--right < left) break;
// 从右到左遍历矩阵
for (let i = right; i >= left; i--) {
orderMatrix.push(matrix[bottom][i]); // 记录元素
}
// 更新下边界,如果超出上边界停止循环
if (--bottom < top) break;
// 从下到上遍历矩阵
for (let i = bottom; i >= top; i--) {
orderMatrix.push(matrix[i][left]); // 记录元素
}
// 更新左边界,如果超出右边界停止循环
if (++left > right) break;
}
if (orderMatrix.length === m * n) return orderMatrix; // 最终返回的数组长度理应是m*n
else return [];
};