977.有序数组的平方(双指针)
/**
* @param {number[]} nums
* @return {number[]}
*/
var sortedSquares = function (nums) {
let len = nums.length;
const result = new Array(len);// 先创建结果数组,这可以提升速度,防止空间不够内存重新分配
let left, right;
for (let i = 0, k = len - 1, j = k; i <= j;) {
left = nums[i] * nums[i];
right = nums[j] * nums[j];
if (left < right) {
result[k--] = right;
j--;
} else {
result[k--] = left;
i++;
}
}
return result;
};
-
let len = nums.length;:初始化变量len为数组nums的长度。 -
const result = new Array(len);:创建一个长度为len的新数组result。 -
let left, right;:定义了两个指针left和right。 -
for (let i = 0, k = len - 1, j = k; i <= j; ) {...}:从数组两端开始,向中间遍历。 -
left = nums[i] * nums[i];和right = nums[j] * nums[j];:计算当前左侧和右侧元素的平方。 -
根据大小将平方值放入
result数组,并更新索引:- 如果右边的平方值较大(
left < right),说明右侧的平方值应该放在result数组的末尾(k),即将right放入result[k]。 - 如果左边的平方值较大,说明左侧的平方值应该放在
result数组的末尾(k),即将left放入result[k]。
- 如果右边的平方值较大(
-
return result;:遍历结束后,返回排序后的平方值数组。
这个函数的主要目的是对一个给定的数字数组进行平方操作,并将结果按升序排序。它通过从数组两端向中间遍历的方式,比较每个元素的平方大小,并将其放入结果数组中,最终得到一个平方值升序排列的新数组。
209.长度最小的子数组 (动态窗口)
/**
* @param {number} target
* @param {number[]} nums
* @return {number}
*/
var minSubArrayLen = function (target, nums) {
const len = nums.length;
let res = Infinity;
let sum = 0;
let left = 0;
for (let right = 0; right < len; right++) {
sum += nums[right];
while (sum >= target) {
res = Math.min(res, right - left + 1);
sum -= nums[left];
left++;
}
}
return res === Infinity ? 0 : res;
}
在一个给定的整数数组 nums 中,找到一个子数组,使得这个子数组的和大于或等于给定的目标值 target,并且这个子数组的长度是所有满足条件的子数组中最短的。
在本题中实现滑动窗口,主要确定如下三点:
- 窗口内是什么?
- 如何移动窗口的起始位置?
- 如何移动窗口的结束位置?
窗口就是 满足其和 ≥ s 的长度最小的 连续 子数组。
窗口的起始位置如何移动:如果当前窗口的值大于等于s了,窗口就要向前移动了(也就是该缩小了)。
窗口的结束位置如何移动:窗口的结束位置就是遍历数组的指针,也就是for循环里的索引。
解题的关键在于 窗口的起始位置如何移动,如图所示:
可以发现滑动窗口的精妙之处在于根据当前子序列和大小的情况,不断调节子序列的起始位置。从而将O(n^2)暴力解法降为O(n)。
下面详细解释代码思路:
- 首先,获取数组
nums的长度len,并初始化三个变量:res为无穷大,sum为0,left为 0。res用于保存满足条件的最短子数组长度,初始化为无穷大是因为还没有找到任何满足条件的子数组。sum用于保存当前子数组的和,left用于指示当前子数组的左边界。 - 然后开始一个
for循环,循环变量right从0开始,逐步增加到len-1。这个for循环的目的是逐个尝试每个元素作为子数组的右边界。 - 在循环体内部,首先将当前元素
nums[right]添加到sum中。这样,sum就包含了从nums[0]到nums[right]的所有元素的和。 - 接着检查
sum是否大于或等于target。如果sum大于或等于target,这意味着找到了一个子数组,其和达到或超过了目标值。此时需要更新res。Math.min(res, right-left+1)是用来比较当前找到的子数组长度与之前找到的最短子数组长度,并取较小者。这就是更新res的思路。这样做是为了确保res始终保存着满足条件的最短子数组长度。 - 更新
res之后,在这个while循环中,通过sum -= nums[left]减少sum的值,同时通过left++向右移动子数组的左边界。这意味着我们尝试缩小子数组的范围,看看是否能够找到更短的子数组。 - 最后,当
for循环结束时,res中将保存满足条件的最短子数组长度。如果没有找到满足条件的子数组,res将保持为无穷大。所以函数的最后返回值是res===Infinity?0:res。如果res是无穷大,返回0表示没有找到子数组,否则返回res表示找到的最短子数组长度。
59.螺旋矩阵II
而求解本题依然是要坚持循环不变量原则。
模拟顺时针画矩阵的过程:
- 填充上行从左到右
- 填充右列从上到下
- 填充下行从右到左
- 填充左列从下到上
由外向内一圈一圈这么画下去。
可以发现这里的边界条件非常多,在一个循环中,如此多的边界条件,如果不按照固定规则来遍历,那就是一进循环深似海,从此offer是路人。
这里一圈下来,我们要画每四条边,这四条边怎么画,每画一条边都要坚持一致的左闭右开,或者左开右闭的原则,这样这一圈才能按照统一的规则画下来。
那么我按照左闭右开的原则,来画一圈,大家看一下:
这里每一种颜色,代表一条边,我们遍历的长度,可以看出每一个拐角处的处理规则,拐角处让给新的一条边来继续画。
这也是坚持了每条边左闭右开的原则。
就是因为在画每一条边的时候,一会左开右闭,一会左闭右闭,一会又来左闭右开,岂能不乱。
var generateMatrix = function (n) {
let startX = startY = 0; // 起始位置
let loop = mid = Math.floor(n / 2); // 旋转圈数 和 中间位置
let offset = 1; // 控制每一层填充元素个数
let num = 1; // 更新填充数字
let i, j;
const arr = new Array(n).fill(0).map(item => new Array(n));
while (loop--) {
for (j = startY; j < n - offset; j++) {
arr[startX][j] = num++;
}
for (i = startX; i < n - offset; i++) {
arr[i][j] = num++;
}
for (; j > startY; j--) {
arr[i][j] = num++;
}
for (; i > startX; i--) {
arr[i][startY] = num++;
}
// 更新起始位置
startX++;
startY++;
// 更新offset
offset++;
}
// 如果n为奇数的话,需要单独给矩阵最中间的位置赋值
if (n % 2 === 1) {
arr[mid][mid] = num;
}
return arr;
};
- 首先,函数初始化了一些变量:
startX,startY作为起始点,loop和mid表示循环次数和中间位置,offset控制每一层的填充数量,num是填充的数字,i和j是循环使用的索引,arr是最终要返回的矩阵。 - 利用嵌套的
for循环来创建螺旋模式。每一次循环都是一次完整的螺旋线,从外层逐步向里层。 - 在
while循环中,使用for循环完成四条边的数字填充,接着更新startX,startY和offset以便于下次循环填充下一层。 - 当
while循环结束后,如果n是奇数,则给中间位置赋予最后一个数字。 - 最终,函数返回填充完毕的矩阵
arr。
简而言之,这段JavaScript代码旨在构建一个n x n的矩阵,并以特定的螺旋顺序填充数字。