代码随想录算法训练营第二天——977.有序数组的平方 ,209.长度最小的子数组 ,59.螺旋矩阵II
知识提要:双指针、
977.有序数组的平方
题目建议: 本题关键在于理解双指针思想
文章讲解:977.有序数组的平方
视频讲解: 双指针法经典题目 | LeetCode:977.有序数组的平方
题目链接:leetcode977 squares-of-a-sorted-array
题目描述:给你一个按 非递减顺序 排序的整数数组 nums,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。
暴力法
思路:拿到这个题,第一反应:先平方,再快排。但是想了下,快排的时复是
O
(
n
log
n
)
O(n \log n)
O(nlogn),不符合题目的条件。故排除。有一说一,脑子里每次蹦出来的方法只有这种方式,感觉方法匮乏,需要再去补补基础。
然后看了下老师建议的思路,用双指针。
双指针
思路:
1.题目中是给了是非递减顺序,但是数组中有负数,负数平方以后,其最大值一定在最左边,那么最大值就只可能出现在两边了。2.用两个指针,一个指针从前面扫描,一个从后面扫描,并且新建一个空数组,用来存放结果,新建一个指针指向数组的最后一位。3.比较两数平方,将较大的数放进新的数组。
代码:
/**
* @param {number[]} nums
* @return {number[]}
*/
var sortedSquares = function (nums) {
// 使用双指针
let size = nums.length - 1
let left = 0
let right = size, k = size
var result = []
while (left <= right && k >= 0) {
if (nums[left] * nums[left] <= nums[right] * nums[right]) {
result[k--] = nums[right] * nums[right]
right--
} else {
result[k--] = nums[left] * nums[left]
left++
}
}
return result
}
执行用时: 108 ms
内存消耗: 47.4 MB
时间复杂度为O(n)
总结:刷这个题,遇到了坑有:result用let result = []
直接定义,输出的还是[],因为let 声明的范围是块作用域,函数内部结果无法在作用域外进行访问,这里我将result用var
定义为了全局变量。
209.长度最小的子数组
题目建议: 本题关键在于理解滑动窗口,这个滑动窗口看文字讲解 还挺难理解的,建议大家先看视频讲解。 拓展题目可以先不做。
文章讲解:209.长度最小的子数组
视频讲解:拿下滑动窗口! | LeetCode 209 长度最小的子数组
题目链接:leetcode209 minimum-size-subarray-sum
题目描述:给定一个含有 n 个正整数的数组和一个正整数 target 。
找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl+1, …, numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。
暴力法
思路:使用双指针,快的指针用来对连续子数组进行累加,慢的数组用来进行下一轮遍历。写完后发现本质上还是暴力法,用这个方法时间很长。
代码:
/**
* @param {number} target
* @param {number[]} nums
* @return {number}
*/
var minSubArrayLen = function (target, nums) {
//快慢双指针
let slow = 0;
let count = 0;
let min = Infinity
for (let fast = 0; fast < nums.length; fast++) {
count += nums[fast]
if (count >= target) {
min = Math.min(min, fast - slow + 1);
count = 0
fast = slow
slow++
}
}
if (min == Infinity) {
return 0
} else {
return min
}
};
执行用时: 1476 ms
内存消耗: 45.2 MB
时间复杂度:
O
(
n
2
)
O(n^2)
O(n2)
总结 :一开始写的时候,先slow++,再fast=slow
,但是提交的时候,发现出错了,在进行单步调试的时候发现,fast在for循环的时候执行了fast++,这样写就往后走了一位,在测试用例[1,4,4] target = 4
的时候就不通过。本质上还是两个for循环,时间复杂度
O
(
n
2
)
O(n^2)
O(n2)
滑动窗口法
思路:滑动窗口,就是不断的调节子序列的起始位置和终止位置,从而得出我们要想的结果。也是利用两个双指针,只不过,没有对fast指针进行重新赋值循环,而是动态调整滑动窗口的起始位置,来进行判断,这样子少了很多fast循环的操作。
代码:
var minSubArrayLen = function (target, nums) {
//滑动窗口法
let size = nums.length
let start = 0;
let count = 0;
let min = Infinity
for (let end = 0; end < size; end++) {
count += nums[end]
while (count >= target) {
min = Math.min(min, end - start + 1);
count -= nums[start]
start++
}
}
return min === Infinity ? 0 : min
};
执行用时: 76 ms
内存消耗: 45.2 MB
时间复杂度:O(n)
空间复杂度:O(1)
总结 :使用滑动窗口的办法,时间复杂度降低了好多,自己在写的时候,确实没想到可以改变窗口的起始位置,只想到了,要以每一个元素为起点进行相加。滑动窗口的精髓:count -= nums[start]
。
59.螺旋矩阵II
题目建议: 本题关键还是在转圈的逻辑,在二分搜索中提到的区间定义,在这里又用上了。
文章讲解:59.螺旋矩阵II
视频讲解:一入循环深似海 | LeetCode:59.螺旋矩阵II
题目链接:leetcode59 spiral-matrix-ii
题目描述:给你一个正整数
n
n
n ,生成一个包含 1 到
n
2
n^2
n2 所有元素,且元素按顺时针顺序螺旋排列的
n
∗
n
n * n
n∗n 正方形矩阵 matrix 。
思路:
模拟顺时针画矩阵的过程:
填充上行从左到右
填充右列从上到下
填充下行从右到左
填充左列从下到上
最关键的是:循环的边界条件,谨记循环不变量原则。
代码:
/**
* @param {number} n
* @return {number[][]}
*/
var generateMatrix = function (n) {
let startX = 0
let startY = 0; // 起始位置
let loop = Math.floor(n / 2); // 旋转圈数
let mid = n >> 1// 中间位置
let offset = 1; // 控制每一层填充元素个数
let count = 1; // 更新填充数字
let res = new Array(n).fill(0).map(() => new Array(n).fill(0));
while (loop--) {
let i = startX
let j = startY
// 上行从左到右(左闭右开)
for (; j < n - offset; j++) {
res[startX][j] = count++;
}
// 右列从上到下(左闭右开)
for (; i < n - offset; i++) {
res[i][j] = count++;
}
// 下行从右到左(左闭右开)
for (; j > startY; j--) {
res[i][j] = count++;
}
// 左列做下到上(左闭右开)
for (; i > startX; i--) {
res[i][j] = count++;
}
// 更新起始位置
startX++;
startY++;
// 更新offset
offset++;
}
// 如果n为奇数的话,需要单独给矩阵最中间的位置赋值
if (n % 2 === 1) {
res[mid][mid] = count;
}
return res;
};
执行用时: 60 ms
内存消耗: 41.1 MB
时间复杂度 O(n^2): 模拟遍历二维矩阵的时间
空间复杂度 O(1)
总结 :老实讲,看到题目和提示的思路,还是很懵逼,没有思路,就先看了视频讲解。然后才明白,但是那个边界条件是真的好容易混,明天画个步骤图出来贴上!
总结:今天做题有点慢,感觉思路还是有点不清晰,第三个题拿到的时候,一点儿思路都没有!但是好在学会了!感觉今天充实不少。然后就是基础有点不牢,需要去复习一下数据结构的有关算法了。但是今天拿到题,自己都思考了,就是自己思考的方法不太好。不完善,面对数组,总是第一想到暴力法。还是得多做题。