螺旋矩阵
59. 螺旋矩阵 II
根据第一次循环去写样例,但要记得其中的条件要设置成之后也要使用的循环变量
【用0作为边界条件判断,忽略了循环量】
代码如下,已经详细注释了每一步的目的,可以看出while循环里判断的情况是很多的,代码里处理的原则也是统一的左闭右开。
/*
* @lc app=leetcode.cn id=59 lang=cpp
*
* [59] 螺旋矩阵 II
*/
// @lc code=start
class Solution {
public:
vector<vector<int>> generateMatrix(int n) {
//判断有几行几列
vector<vector<int>> res(n, vector<int>(n,0));
//起始位置
int startX = 0;
int startY = 0;
//循环次数
int loop = n / 2;
//每次循环边界-1
int offset = 1;
//赋值
int count = 1;
//中间位置
int mid = n / 2;
//旋转的各个边界如何判定
while(loop--){
//向右移动
//坚持循环不变量原则 区间取左闭右开
int i = 0;
int j = 0;
for(j = startY; j < n - offset; j++){
res[startX][j] = count;
count++;
}
//向下移动
for(i = startX; i < n - offset; i++){
res[i][j] = count;
count++;
}
//向左移动
// for(; j > 0; j--){
// res[i][j] = count;
// count++;
// }
//需要比初始值大,而不是0(只考虑了第一次循环)
for(; j > startY; j--){
res[i][j] = count;
count++;
}
//向上移动
for(; i > startX; i--){
res[i][j] = count;
count++;
}
//起始位置的添加要在下一次循环开始之前
startX++;
startY++;
//每一次循环后,再下一次循环开始前需要修改的变量
//都需要在这一次循环结束前进行修改
offset++;
}
// //起始位置
// startX++;
// startY++;
//奇数偶数判断中间值
if(n % 2 != 0){
res[mid][mid] = n * n;
}
return res;
}
};
// @lc code=end
// 时间复杂度 O(n^2): 模拟遍历二维矩阵的时间
// 空间复杂度 O(1)
大家还记得我们在这篇文章数组:每次遇到二分法,都是一看就会,一写就废 (opens new window)中讲解了二分法,提到如果要写出正确的二分法一定要坚持循环不变量原则。
而求解本题依然是要坚持循环不变量原则。
模拟顺时针画矩阵的过程:
- 填充上行从左到右
- 填充右列从上到下
- 填充下行从右到左
- 填充左列从下到上
由外向内一圈一圈这么画下去。
可以发现这里的边界条件非常多,在一个循环中,如此多的边界条件,如果不按照固定规则来遍历,那就是一进循环深似海,从此offer是路人。
这里一圈下来,我们要画每四条边,这四条边怎么画,每画一条边都要坚持一致的左闭右开,或者左开右闭的原则,这样这一圈才能按照统一的规则画下来。
那么我按照左闭右开的原则,来画一圈,大家看一下:
这里每一种颜色,代表一条边,我们遍历的长度,可以看出每一个拐角处的处理规则,拐角处让给新的一条边来继续画。
这也是坚持了每条边左闭右开的原则。
滑动窗口
209. 长度最小的子数组
暴力解法:两个for循环,一个for循环为滑动窗口的终止位置,用两个for循环 完成了一个不断搜索区间的过程。
那么滑动窗口如何用一个for循环来完成这个操作呢。
如果只用一个for循环来表示 滑动窗口的起始位置,那么如何遍历剩下的终止位置?
此时难免再次陷入 暴力解法的怪圈。
所以 只用一个for循环,那么这个循环的索引,一定是表示 滑动窗口的终止位置。
实现滑动窗口,主要确定如下三点:
- 窗口内是什么?
- 如何移动窗口的起始位置?
- 如何移动窗口的结束位置?
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
//滑动窗口也可以理解为双指针
//但是slow永远追不上fast
int slow = 0;
int subLength = 0;
int sum = 0;
//加入result 筛选结果
// int result = 0;因为初值要比sublength大,所以初值要赋最大值
// INT32_MAX指32位有符号整数类型int的最大值 2^32-1
int result = INT32_MAX;
for(int fast = 0; fast < nums.size(); fast++){
sum += nums[fast];
//符合条件、进入判断
while(sum >= target){
//更新subLength
subLength = fast - slow + 1;
result = result < subLength ? result : subLength;
//保存最小的窗口长度,然后移动前面指针,继续判断
sum -= nums[slow];
slow++;
}
}
//如果不加入对于result对最大值的筛选,每次返回的都是最新的值,得加入一个对于历史值的筛选
return result == INT32_MAX ? 0 : result;
}
};
窗口就是 满足其和 ≥ s 的长度最小的 连续 子数组。
窗口的起始位置如何移动:如果当前窗口的值大于s了,窗口就要向前移动了(也就是该缩小了)。
窗口的结束位置如何移动:窗口的结束位置就是遍历数组的指针,也就是for循环里的索引。
滑动窗口的精妙之处在于根据当前子序列和大小的情况,不断调节子序列的起始位置。从而将O(n^2)暴力解法降为O(n)。
977. 有序数组的平方
重构一个数组存放结果,不需要在原地移动
双指针:前后两个指针比较结果,然后存放在新空间内,新空间从后向前存放元素。
因为两边平方后比较最大值,最大值在数组的两头,方便比较
class Solution {
public:
vector<int> sortedSquares(vector<int>& nums) {
//创建跟原数组长度一致的新数组存放
vector<int> new_nums;
int size = nums.size();
new_nums.resize(size);
//建立双指针
int slow = 0;
int fast = size - 1;
int n = size - 1;
//为何取到“=”没有考虑进去 当fast = slow的时候,该元素并不能被忽略
//代码随想录:因为要处理最后两个元素
while(fast >= slow){
if(nums[slow] * nums[slow] >= nums[fast] * nums[fast]){
new_nums[n--] = nums[slow] * nums[slow];
slow++;
}else if(nums[slow] * nums[slow] < nums[fast] * nums[fast]){
new_nums[n--] = nums[fast] * nums[fast];
fast--;
}
}
return new_nums;
}
};