997.有序数组的平方
题目链接 977. 有序数组的平方 - 力扣(Leetcode)
遇到问题
再运用双指针时,没联想到新建一个答案数组,导致无法解答
题解
1. 暴力遍历解法
最简单的思想就是全部算出来,然后排序
var sorteSquares = function(nums){
for(let i = 0; i < nums.length; i++){
nums[i] *= nums[i]
}
return nums.sort((a,b) => a-b)
}
时间复杂度: O(n + nlogn)(取决于快速排序的复杂度)
空间复杂度: O(1)
2. 双指针解法
分析可知,数组平方后出现的情况是负数平方大于正数,而且是负的越多越大,对于数组中负数来说,左边的平方一定大于右边的平方;对于正数来说,右边的平方一定大于左边的平方,因此,最大的数值只能出现在两头,故只需要比较两头大小即可
var sorteSquares = function(nums){
let res = [], i = 0, j = nums.length - 1
while(i <= j){
let left = nums[i] * nums[i]
let right = nums[j] * nums[j]
if(left > right){
res.unshift(left) // 将数压入最前端,保证为非递减
i ++
}else{
res.unshift(right)
j --
}
}
return res
}
时间复杂度:O(n)
空间复杂度:O(n)(为保存答案数组所占用的空间)
当然如果不想使用Arry.unshift()
可以创建一个指针k
在每次赋值时用nums[k--]
即可
需要额外注意的是:循环条件中i <= j
应该是需要赋值等号,如果未赋值,则当遍历到两个指针重合时,会导致少压入一个元素
209.长度最小的子数组
题目链接 209. 长度最小的子数组 - 力扣(Leetcode)
遇到问题
没完全理解滑动窗口和涵盖关系,错误的把暴力解法认为是滑动窗口法,从而没做出更好的解答
题解
1.暴力解法
var minSubArrayLen = function(target, nums) {
let minLength = Infinity
for(let i = 0; i < nums.length; i++){
for(let j = i,sum = 0; j < nums.length; j++){
sum += nums[j]
if(sum >= target){
minLength = minLength > (j - i + 1) ? (j - i + 1) : minLength
break
}
}
}
return minLength == Infinity? 0: minLength
}
时间复杂度:O(n^2)
空间复杂度:O(1)
基本思路就是一个数一个数的算然后找出来最小的值,没什么可以过多说的
2.滑动窗口法
滑动窗口 的基本思想是:就是不断的调节子序列的起始位置和终止位置,从而得出我们要想的结果。
其实这也是双指针的一种,但是他多了一个固定的区间,具体到这个题目中,需要确定
- 窗口内是什么?
- 如何移动窗口的起始位置?
- 如何移动窗口的结束位置?
这里借用一下代码随想录的动态图来更好的理解这过程
var minSubArrayLen = function(target, nums) {
let minLength = Infinity, sum = 0
let i = 0 //窗口起始位置
for(let j = 0; j < nums.length; j++){
sum += nums[j]
while(sum >= target){
minLength = minLength > (j - i + 1)? (j - i + 1) : minLength
sum -= nums[i++] // 起始位置向右边移动一位
}
}
return minLength == Infinity? 0 : minLength
}
时间复杂度:O(n)
空间复杂度:O(1)
乍一看会和暴力解法很像,但是精髓之处,就是动态的计算了框选了一个长度,即循环中的while判断而不是无止尽的遍历
可以发现滑动窗口的精妙之处在于根据当前子序列和大小的情况,不断调节子序列的起始位置。从而将O(n^2)暴力解法降为O(n)
扩展
904. 水果成篮 - 力扣(Leetcode)
76. 最小覆盖子串 - 力扣(Leetcode)
59.螺旋矩阵ll
题目链接 59. 螺旋矩阵 II - 力扣(Leetcode)
遇到问题
- 只想着如何快速解答,直接上手找规律,没有进行实际模拟
- 对整个区间规划,做的不完善
题解
题目重点是如何转圈遍历,然后模拟过程,其次是与二分法相似的循环不变量原则
在本题中,循环不变量原则体现在对每一个转角的模拟遍历,是属于转角前的还是转角后的呢?
先看代码
var generateMatrix = function(n) {
let res = new Array(n).fill(0).map(() => new Array(n).fill(0))
let loop = n >> 1 // 判断转圈数
let mid = n >> 1 // 中间值
let count = 1 // 计数
let offset = 1 // 每一行减少数
let startX = startY = 0
while(loop--){
let row = startX,col = startY
// 上行从左往右遍历(左闭右开)
for(; col < n - offset; col++){
res[row][col] = count++
}
// 右边从上往下遍历(左闭右开)
for(; row < n - offset; row++){
res[row][col] = count++
}
// 下行从右往左遍历(左闭右开)
for(; col > startY; col--){
res[row][col] = count++
}
//左边从下往上(左闭右开)
for(;row > startX; row --){
res[row][col] = count++
}
startX++
startY++
offset += 1
}
// 判断是否为基数
if(n % 2 != 0){
res[mid][mid] = count
}
return res
}
时间复杂度: O(n^n)
空间复杂度:O(n^n)
这道题目没有很多技巧性质的方法,主要就是在模拟这个转圈赋值的过程,但是这个过程细节很多:
-
- Q: 如何遍历每个矩阵中的值
- A: 依照转圈的方式,分别遍历上右下左
-
- Q: 怎么样算一个循环
- A:按照上述的遍历,循环的是每圈,即每完成一次上右下左遍历后为一个周期,故因此需要提前知道有多少圈
-
-
Q: 边界值如何分配
-
A:应该按照循环不变量原则始终按照一种区间的开闭,这里最好采用左闭右开,借用图,如图:
此外做这种类型的题目时,应该不急躁一步一步模拟自己的人工运算规则
-
扩展
54. 螺旋矩阵 - 力扣(Leetcode)
剑指 Offer 29. 顺时针打印矩阵 - 力扣(Leetcode)
今日总结
- 进一步理解的深入了双指针的用法
- 学习滑动窗口如何使用
- 今日将代码随想录的数组部分学习完毕,其中主要学习到的方法
想录的数组部分学习完毕,其中主要学习到的方法- 二分法查找:需要主要区间的开闭选择,一般为左闭右开和左闭右闭
- 双指针法:将二维遍历降低到一维遍历的常用方法,在数组题目能暴力解答时,算法复杂度为
O(n^2)
时候不妨往此方向思考 - 在双指针法进一步细化的中,学习了滑动窗口的方法,动态的思考一个问题的规划与设计
- 在解题过程中不要上来就寻找最优解,应该需要首先自己模拟这个过程尝试用暴力法解决,然后再思考有无类似的思想可以运用
- 解题的细节注意,尤其是当取
=
的时候需要格外注意