977. 有序数组的平方
题目链接:Leecode977. 有序数组的平方
文章讲解:代码随想录—977.有序数组的平方
第一思路:刚看到本体的第一想法就是文章中的暴力算法,先对数组进行平方运算,再对平方后的数组排序
暴力算法
class Solution {
public:
vector<int> sortedSquares(vector<int>& nums) {
for (int i = 0; i < nums.size(); i ++ ) nums[i] *= nums[i];
sort(nums.begin(), nums.end());
return nums;
}
};
时间复杂度:O(n + nlog n)
空间复杂度:O(1)
双指针算法
思路:
原数组是按递增顺序排列的,在数组平方运算后能让原本递增顺序改变的,只可能是因为数组中存在负数。
如果数组既存在正数、又存在负数,那数组左半部分(负数部分)经平方运算后会呈递减排列,右半部分(正数部分)会呈递增排列。也就是说,平方后数组的最大值只会出现在数组两端,并向中间递减。
如果只有正数,数组右端存在最大值;如果只有负数,数组左端存在最大值。综上,满足平方后数组的最大值只会出现在数组两端这一条件。
因此,本题解决方案就是运用双指针从两边出发对数值进行比较,将比较后的更大值依次更新到新建数组中。
class Solution {
public:
vector<int> sortedSquares(vector<int>& nums) {
vector<int> res(nums.size(), 0);
int len = nums.size();
int i = 0, j = len - 1, k = j;
while (i <= j) {
if (nums[i] * nums[i] < nums[j] * nums[j]) {
res[k --] = nums[j] * nums[j];
j --;
}
else {
res[k --] = nums[i] * nums[i];
i ++;
}
}
return res;
}
};
时间复杂度:O(n)
空间复杂度:O(1)
209. 长度最小的子数组
题目链接:Leecode209. 长度最小的子数组
文章讲解:代码随想录—209. 长度最小的子数组
第一思路:接触过滑动窗口的解法,可以想到用该法解题
暴力算法
简单粗暴两层 for 循环,时间复杂度可想而知 O(n ^ 2)
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
int res = INT32_MAX;
int sum = 0;
int len = 0;
for (int i = 0; i < nums.size(); i ++) {
sum = 0;
for (int j = i; j < nums.size(); j ++) {
sum += nums[j];
if (sum >= target) {
len = j - i + 1;
res = res < len ? res : len;
break;
}
}
}
return res == INT32_MAX ? 0 : res;
}
};
时间复杂度:O(n ^ 2)
空间复杂度:O(1)
滑动窗口算法
思路:
很容易想到,本题中需要不断更改子序列的起始位置和终止位置,就像一个窗口在数列中滑动。
初始先从第一个元素开始,向子序列中添加元素,直到子序列中的元素和 >= target,记录子序列长度。
第一个满足条件的窗口成立后,起始位置不变的情况下,后面继续添加元素只会在满足条件的情况下增加窗口长度,因此应更改窗口起始位置后再滑动窗口,直到找到符合条件的最小长度值。
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
int sum = 0;
int res = INT32_MAX;
int i = 0;
int len = 0;
for (int j = i; j < nums.size(); j ++) {
sum += nums[j];
while (sum >= target) {
len = j - i + 1;
res = res < len ? res : len;
sum -= nums[i ++];
}
}
return res == INT32_MAX ? 0 : res;
}
};
时间复杂度:O(n)
空间复杂度:O(1)
59. 螺旋矩阵 II
题目链接:Leecode59. 螺旋矩阵 II
文章讲解:代码随想录—59. 螺旋矩阵 II
思路:
找好边界,左闭右开,按着数字填充顺序填写
记号循环圈数,如果矩阵边长是奇数,需要单独填充中间值
class Solution {
public:
vector<vector<int>> generateMatrix(int n) {
vector<vector<int>> res(n, vector<int>(n, 0));
int x = 0, y = 0; //记录每一圈的起始位置
int count = 1; //标记数值
int offset = 1; //每一行或每一列行走的终点偏移量
int loop = n / 2; //记录圈数
int mid = n / 2; //记录中间位置
while (loop --) {
int i = x, j = y;
for ( ; j < n - offset; j ++) res[i][j] = count ++;
for ( ; i < n - offset; i ++) res[i][j] = count ++;
for ( ; j > y; j --) res[i][j] = count ++;
for ( ; i > x; i --) res[i][j] = count ++;
x ++;
y ++; //更新下一圈起始位置
offset ++;
}
if (n % 2) res[mid][mid] = count;
return res;
}
};
时间复杂度:O(n ^ 2)
空间复杂度:O(1)
总结
二分法:目前看来使用条件是有序数组,没有重复元素时二分法更简单,时间复杂度一般为O(log n)。重点是要把握好边界!
双指针:目的是能在一个循环内完成两个循环的工作,时间复杂度为O(n)。目前练习的题目中包含了快慢双指针和左右双指针,灵活应对。
滑动窗口:涉及到子序列在数组中滑动以满足条件,时间复杂度为O(n)。