977.有序数组的平方
文字链接:977.有序数组的平方
视频链接:双指针法经典题目 | LeetCode:977.有序数组的平方
状态:做出来了;但是有一个点需要强调,使用二分法貌似无法在原数组上直接操作实现平方后排序,一定要建立一个新数组来存储平方排序后的结果
基本思想
个人感觉在数组操作中涉及到比较排序这类题型,不妨考虑一下双指针的方法。
此时的双指针不是制造快慢指针了,而是显而易见得设计左右指针相互逼近。
- 左指针指向数组最左边,右指针指向数组最右边
- 创建一个新的数组来存储结果,新数组的元素从后往前排。
- num[left]和nums[right]平方后比较,然后哪个指针指向的元素放入了新数组,哪个指针就先跑。
暴力解法
暴力解法,先全部平方,再全部排序。如果使用sort函数(快排),时间复杂度为O(n+logn)
可以认为是O(nlogn)
class Solution {
public:
vector<int> sortedSquares(vector<int>& A) {
for (int i = 0; i < A.size(); i++) {
A[i] *= A[i];
}
sort(A.begin(), A.end()); // 快速排序
return A;
}
};
双指针
整体过程如上文所述
- 关于
nums[left] * nums[left]
操作,输入的整数范围被限制在[-10^4, 10^4]
之间。这意味着平方之后的结果最大不会超过10^8
,远远低于int
类型在C++中可以表示的最大值(通常是2^31 - 1
,即约21亿)。 std::abs
对于该函数,leetcode包含头文件。
class Solution {
public:
vector<int> sortedSquares(vector<int>& nums) {
int n = nums.size();
vector<int> result(n);
int left = 0, right = n - 1;
int pos = n - 1; // 从结果数组的末尾开始填充
while (left <= right) {
if (abs(nums[left]) > abs(nums[right])) {
result[pos--] = nums[left] * nums[left];
left++;
} else {
result[pos--] = nums[right] * nums[right];
right--;
}
}
return result;
}
};
209. 长度最小的子数组
文字链接:209. 长度最小的子数组
视频链接:拿下滑动窗口! | LeetCode 209 长度最小的子数组
状态:了解滑动窗口的模式,但是不知道它的代码精髓,导致代码写得乱的一。
==滑动窗口的精妙之处在于根据当前子序列和大小的情况,动态调节子序列的起始位置。==所以里面的循环操作是一种状态的检测,肯定是用
while
基本思想
滑动窗口和双指针是一回事儿吗,其实我觉得主要是解题思想的问题:对于滑动窗口,操作的范围是指针所夹的区间,而双指针则操作的是指向的两个值。
但是总而言之,滑动窗口和双指针讨论的重点是类似的:
- 如何设置两个指针,从而形成窗口
- 窗口区间如何滑动
- 窗口内的值如何进行操作
一句话解决上述三个重要问题:从数组的开头设置左右指针,用右指针遍历数组,从而形成窗口,如果窗口内形成的子数组满足其总和大于等于 target
,则记录其长度,如果大于target
,右边界不懂,左边界开始缩小范围,来减小target
,如果小于target
,左边界不动,右边界扩大窗口范围
滑动窗口
代码重点:
-
里面的循环操作是一种状态的检测,肯定是用
while
-
定义
result = INT32_MAX
,引入INT32_MAX
是为了初始化result
变量为一个很大的数,以便于后续的最小值比较和判断是否找到了满足条件的子数组。这是一种常见的编程技巧,用于解决类似的寻找最小/最大值的问题。- 表示无效值:在遍历开始之前,我们并不知道最小子数组的长度,因此
INT32_MAX
作为一个非常大的数,被用来表示一个尚未找到有效子数组的状态。这个值足够大,以至于任何有效的子数组长度都会小于它。 - 便于比较:在寻找长度最小的满足条件的子数组时,我们需要有一个比较的基准。使用
INT32_MAX
作为初始比较基准,可以保证任何一个有效的子数组长度在第一次比较时都会被认为是更小的,因此可以被记录下来。 - 返回值处理:在循环结束后,如果
result
的值仍然是INT32_MAX
,这意味着没有找到满足条件的子数组。因此,代码最后通过return result == INT32_MAX ? 0 : result;
来判断是否找到了有效的子数组。如果没有找到,就返回0
,表明不存在这样的子数组。
- 表示无效值:在遍历开始之前,我们并不知道最小子数组的长度,因此
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
int left = 0, right = 0;
int ans = INT32_MAX;
int sum = 0;
while (right < nums.size()) {
sum += nums[right];
// 注意这里使用while,每次更新 i(起始位置),并不断比较子序列是否符合条件
while (sum >= target) {
int subLength = (right - left + 1); // 拿到子序列的长度
ans = ans < subLength ? ans : subLength;
sum -= nums[left++]; // 这里体现出滑动窗口的精髓之处,不断变更i(子序列的起始位置)
}
right++;
}
// 如果result没有被赋值的话,就返回0,说明没有符合条件的子序列
return ans == INT32_MAX ? 0 : ans;
}
};
滑动窗口总结
套模板代码如下:
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
int left = 0, right = 0;
int curSum = 0, minLength = 0;
while (right < nums.size()) {
curSum = curSum + nums[right];
while (curSum >= target) {
if (right - left + 1 < minLength || minLength == 0) {
minLength = right - left + 1;
}
curSum = curSum - nums[left];
left++;
}
right++;
}
return minLength;
}
};
59. 螺旋矩阵 II
文章链接:59. 螺旋矩阵 II
视频链接:LeetCode:59.螺旋矩阵II
状态:没写出来,有好几个点都没想清楚,下面已列出
对于纯模拟题,代码性思维真的很重要,本题拿到手,确实想到了模拟和边界问题。但是有几个问题没理清,导致在写代码过程中很多错误。一定要搞清以下几个重点才能顺利写出代码。
几个重点
-
第一个大循环要如何设定。
刚开始想的是用for(count = 1; count <= n*n;count++)这样的循环。但是里面的内容应该怎么写呢?想不出来了。后来看解析,用的是圈数来循环loop = n / 2; while(loop–)。这样的模拟更合适。因为我们本质上就是在一圈一圈得跑,然后填数,然后第二圈。所以模拟圈数也比较直观。 -
因为是一圈一圈得模拟,所以要设定好每次的起始位置,循环结束直接++就能定义好下次循环的起始位置。
-
元素如何加入列表。
用for循环比较好想,但是用while的时候一下没有反应过来,其实我们开头就定义好了res,所以res的空间只有这么大,这个时候我们在外面定义一个count,然后循环里一直count++;当循环结束的时候肯定刚好填满最后一个空,此时的count一定是 n 2 n^2 n2。 -
跑第二圈如何引入。
写一个偏置量offset,等到循环跑完offset+1。
C++代码
class Solution {
public:
vector<vector<int>> generateMatrix(int n) {
vector<vector<int>> res(n, vector<int>(n, 0)); // 使用vector定义一个二维数组
int startx = 0, starty = 0; // 定义每循环一个圈的起始位置
int loop = n / 2; // 每个圈循环几次,例如n为奇数3,那么loop = 1 只是循环一圈,矩阵中间的值需要单独处理
int mid = n / 2; // 矩阵中间的位置,例如:n为3, 中间的位置就是(1,1),n为5,中间位置为(2, 2)
int count = 1; // 用来给矩阵中每一个空格赋值
int offset = 1; // 需要控制每一条边遍历的长度,每次循环右边界收缩一位
int i,j;
while (loop --) {
i = startx;
j = starty;
// 下面开始的四个for就是模拟转了一圈
// 模拟填充上行从左到右(左闭右开)
for (j = starty; j < n - offset; j++) {
res[startx][j] = count++;
}
// 模拟填充右列从上到下(左闭右开)
for (i = startx; i < n - offset; i++) {
res[i][j] = count++;
}
// 模拟填充下行从右到左(左闭右开)
for (; j > starty; j--) {
res[i][j] = count++;
}
// 模拟填充左列从下到上(左闭右开)
for (; i > startx; i--) {
res[i][j] = count++;
}
// 第二圈开始的时候,起始位置要各自加1, 例如:第一圈起始位置是(0, 0),第二圈起始位置是(1, 1)
startx++;
starty++;
// offset 控制每一圈里每一条边遍历的长度
offset += 1;
}
// 如果n为奇数的话,需要单独给矩阵最中间的位置赋值
if (n % 2) {
res[mid][mid] = count;
}
return res;
}
};