目录
977.有序数组的平方
前言:
本题分别用暴力解法和双指针法进行实现,其中在暴力解法中有先平方后排序和先排序(按平方后的大小)后平方两种思路。
方式一:暴力解法一(先排序):
class Solution {
public:
vector<int> sortedSquares(vector<int>& nums) {
for (int i = 0; i < nums.size() - 1; i++){
for (int j = i + 1; j < nums.size(); j++){
if (nums[i] * nums[i] > nums[j] * nums[j]){
swap(nums[i], nums[j]);
}
}
}
for (int i = 0; i < nums.size(); i++){
nums[i] = nums[i] * nums[i];
}
return nums;
}
};
该方法大致思路是先将原数组按照平方后的的大小进行排序,在对排序后的数组进行平方。在通过130/137个案例后出现超时案例。
方式二:暴力解法二(先平方):
class Solution {
public:
vector<int> sortedSquares(vector<int>& nums) {
for (int i = 0; i < nums.size(); i++){
nums[i] = nums[i] * nums[i];
}
sort(nums.begin(), nums.end());
return nums;
}
};
该方法直接先对数组元素进行平方,再调用库函数 sort 对平方后的数组进行平方,时间复杂度0(n^2),能够通过。
注意点:
虽然暴力解法能够通过题解,但是时间复杂度上不满足o(n)的进阶要求。
方式三:双指针法:
class Solution {
public:
vector<int> sortedSquares(vector<int>& nums) {
int len = nums.size() - 1;
vector<int> result(len + 1,0);
for (int i = 0, j = len; i <= j;){
if(nums[i] * nums[i] > nums[j] * nums[j]){
result[len--] = nums[i] * nums[i];
i++;
}
else{
result[len--] = nums[j] * nums[j];
j--;
}
}
return result;
}
};
因为存在负数的可能,因此数组平方后的特点是两头大,中间小,用双指针分别指向数组的两头,分别比较两端元素平方后的大小,将较大值平方后存放进入一个新数组中,新数组与原数组的容量大小相同,由于是从小到大排序,要将值从数组最后往前添加,之后再依次移动指针,直到两指针重合。
209.长度最小的子数组
前言:
本题依旧先利用暴力解法进行实现,再利用滑动窗口的方式进行实现。
方式一:暴力解法(比较):
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
int len = nums.size() + 1, sum = 0;
for (int i = 0; i < nums.size(); i++){
sum = nums[i];
if (sum >= target){
len = 1;
break;
}
for (int j = i + 1; j < nums.size(); j++){
sum = sum + nums[j];
if (sum >= target && len > j - i + 1) {
len = j - i + 1;
}
}
}
return len == nums.size() + 1 ? 0 : len;
}
};
暴力解法,两层 for 循环,第一次依次遍历数组元素,第二层 for 循环进行具体的求和操作,值得注意的是我在本题中定义的是 j 从 i + 1 开始,则 sum = nums[i] 的情况要先在第一个 for 循环中进行讨论,与老师题解所给算法相比略显不足:
class Solution {
public:
int minSubArrayLen(int s, vector<int>& nums) {
int result = INT32_MAX; // 最终的结果
int sum = 0; // 子序列的数值之和
int subLength = 0; // 子序列的长度
for (int i = 0; i < nums.size(); i++) { // 设置子序列起点为i
sum = 0;
for (int j = i; j < nums.size(); j++) { // 设置子序列终止位置为j
sum += nums[j];
if (sum >= s) { // 一旦发现子序列和超过了s,更新result
subLength = j - i + 1; // 取子序列的长度
result = result < subLength ? result : subLength;
break; // 因为我们是找符合条件最短的子序列,所以一旦符合条件就break
}
}
}
// 如果result没有被赋值的话,就返回0,说明没有符合条件的子序列
return result == INT32_MAX ? 0 : result;
}
};
代码随想录中的 j 从 i 值开始取其,实现了 sum 取值的统一,减少了代码量,但是仍然超时。
注意点:
INT_MAX 和 INT_MIN 是 C++ 的两个宏,代表了整型变量能够存储的最大正整数和最小负整数,分别为 2147483647 和 -2147483648,这两个宏在头文件 <limits.h> 中定义。
方式二:滑动窗口:
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
int sum = 0, result = nums.size() + 1, len = 0;
for (int i = 0, j = 0; j < nums.size(); j++){
sum += nums[j];
while (sum >= target){ //边界值判断
sum -= nums[i];
len = j - i + 1;
result = (result > len ? len : result); //将新获取的 len 和目前最短的 len(即 result)比较
i++; //先获取窗口长度后加1
}
}
return result == nums.size() + 1 ? 0 : result;
}
};
滑动窗口是一个新鲜的概念,所谓滑动窗口,就是不断的调节子序列的起始位置和终止位置,从而得出我们要想的结果。
本题中我们让 j 作为窗口的终止位置先出发逐一遍历数组元素,并将对应的值加入 sum 中,直到 sum 的值超过我们的目标值 target,此时开始移动起始位置 i 进行窗口的收缩,将 i 所对应的数组元素从 sum 中剔除出去,直到 sum 再次小于 target 时,此时记录下窗口的长度,再继续移动 j,循环上一个过程,直到遍历到 j 遍历到最后一个元素。
注意点:
- while 和 if 的选择:while 在条件满足的情况下循环执行,而 if 只在条件满足情况下执行一次,本题在更改窗口大小时要一直调节起始位置才能满足最短子数组的条件;
- 为了简化流程和便于代码实现,在每一次更改窗口大小后都进行一次窗口长度的记录和最短长度的判断,而非在一次 while 循环结束后再进行判断;
- 子序列起始位置的移动和窗口长度的记录顺序要注意,先记录下窗口长度 len,再对子序列起始位置进行移动(即 i++)。
59.螺旋矩阵II
前言:
本题是循环不变量原则的具体应用,需要仔细考虑各种边界条件。
具体实现:
class Solution {
public:
vector<vector<int>> generateMatrix(int n) {
int num = 1, k = n / 2, offset = 1; // k为循环圈数
int startx = 0, starty = 0;
int i,j;
int mid = n / 2;
vector<vector<int>> result(n, vector<int>(n, 0)); //用 vector 创建二维数组
while (k--){
for (j = starty ; j < n - offset; j++){
result[startx][j] = num++;
}
for (i = startx; i < n - offset; i++){
result[i][j] = num++;
}
for ( ; j > starty; j--){
result[i][j] = num++;
}
for ( ; i > startx; i--){
result[i][j] = num++;
}
startx++;
starty++;
offset++;
}
if (n % 2 == 1) result[mid][mid] = num; //n奇数额外处理
return result;
}
};
本算法在处理边界问题时都只处理到当前边(循环)的前一个,并且用 offset 变量记录当前的圈数,k为总计要执行的圈数,每圈循环结束后都要更新 x、y的起始值,即startx、starty。在所有循环结束后还要为奇数矩阵的中心额外赋值处理。
注意点:
- 用 vector 容器创建并初始化二维数组;
- 矩阵内部循环时边界值的考虑尤为重要,要在外圈的基础上多减 offset, 并且 x、y的初始值也已变化。
今天题目的难度显著高于昨天的,确实费了不少时间,不过确实学到了很多有用的知识和技巧,加油,坚持就是胜利!