代码随想录算法训练营第2天| 977.有序数组的平方、209.长度最小的子数组、59.螺旋矩阵II。
学习文档链接: 代码随想录
一、977.有序数组的平方
链接: 有序数组的平方
该题两种思路:
(1)暴力排序:每个数平方之后,排个序,时间复杂度是 O(n + nlogn)。
(2)双指针:数组其实是有序的,只是负数可能会导致平方很大,考虑使用双指针实现,左指针指向起始位置,右指针指向末尾。定义一个新数组result,其大小与原数组相同,让k指向数组的末尾,判断左右指针指向位置数的平方大小,将较大的先插入新数组即可。双指针的时间复杂度为O(n),相对于暴力排序的解法O(n + nlog n)还是提升不少的。
(1)暴力排序代码:
//新建数组然后使用push_back()插入。
class Solution {
public:
vector<int> sortedSquares(vector<int>& nums) {
vector<int> result;
for(int a : nums){
result.push_back(a*a);
}
sort(result.begin(),result.end());
return result;
}
};
//直接更改原数组,使用for(int a : nums)遍历时,若要更改数组元素,加引用才行,若不加引用,改不了原数组。
//使用for(int i = 0;i<nums.size();i++)遍历,结果与该方法一致。
class Solution {
public:
vector<int> sortedSquares(vector<int>& nums) {
for(int &a : nums){
a*=a;
}
sort(nums.begin(),nums.end());
return nums;
}
};
(2)双指针代码:
class Solution {
public:
vector<int> sortedSquares(vector<int>& nums) {
vector<int> result(nums.size(), 0); //初始化之后就可以使用数组形式添加数据
//vector<int> result; 如果不初始化,那么后续只能使用push_back()添加数据,使用下面方式会直接报如下错误。
int left = 0;
int right = nums.size() - 1;
int k = nums.size()-1;
while(left<=right){
if(nums[left]*nums[left]<=nums[right]*nums[right]){
result[k--] =nums[right]*nums[right];
right--;
}
else
{
result[k--] =nums[left]*nums[left];
left++;
}
}
return result;
}
};
不初始化数组的报错信息:
二、209.长度最小的子数组
链接: 长度最小的子数组
两种思路:
(1)暴力遍历,一个for循环滑动窗口的起始位置,一个for循环为滑动窗口的终止位置,用两个for循环 完成了一个不断搜索区间的过程。时间复杂度为O(n^2),由于题目中1 <= nums.length <= 10^5,所有在力扣里面会超时。
(2)滑动窗口,就是不断的调节子序列的起始位置和终止位置,从而得出我们要想的结果,本质也就是双指针。那么滑动窗口如何用一个for循环来完成这个操作呢。
首先要思考如果用一个for循环,那么应该表示滑动窗口的起始位置,还是终止位置。如果只用一个for循环来表示 滑动窗口的起始位置,那么如何遍历剩下的终止位置?此时难免再次陷入暴力解法的怪圈。所以 只用一个for循环,那么这个循环的索引,一定是表示滑动窗口的终止位置。
(1)暴力遍历法
时间复杂度为o(n^2),详细代码如下:
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;
}
};
(2)滑动窗口
可以发现滑动窗口的精妙之处在于根据当前子序列和大小的情况,不断调节子序列的起始位置。从而将O(n^2)暴力解法降为O(n)。
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
int left = 0;
int subLength = 0;
int result = INT32_MAX;
int sum=0;
for(int right = 0;right<nums.size();right++){
sum += nums[right];
while(sum>=target){
subLength = right-left+1;
result = min(result,subLength); //要在while里面执行min()。
sum -= nums[left];
left++;
}
}
if(result ==INT32_MAX)
return 0;
else
return result;
}
};
三、59.螺旋矩阵II
链接: 螺旋矩阵II
思路:
这道题目可以说在面试中出现频率较高的题目,本题并不涉及到什么算法,就是模拟过程,但却十分考察对代码的掌控能力。
求解本题依然是要坚持循环不变量原则。
模拟顺时针画矩阵的过程:
填充上行从左到右
填充右列从上到下
填充下行从右到左
填充左列从下到上
由外向内一圈一圈这么画下去。
关键点:这里一圈下来,我们要画每四条边,这四条边怎么画,每画一条边都要坚持一致的左闭右开,或者左开右闭的原则,这样这一圈才能按照统一的规则画下来。
详细代码如下:
class Solution {
public:
vector<vector<int>> generateMatrix(int n) {
vector<vector<int> > Matrix(n,vector<int>(n,0));
int startx = 0, starty = 0; // 定义每循环一个圈的起始位置
int num = 1;
int i,j;
int offset = 1; // 需要控制每一条边遍历的长度,每次循环右边界收缩一位
int loop =n/2;
int mid =n/2;
while(loop--){
i = startx;
j = starty;
//左闭右开
for (; j < n - offset; j++) {
Matrix[i][j] = num++;
}
for (i; i < n - offset; i++) {
Matrix[i][j] = num++;
}
for (; j > starty; j--) {
Matrix[i][j] = num++;
}
for (; i > startx; i--) {
Matrix[i][j] = num++;
}
startx++;
starty++;
offset += 1;
}
if (n % 2) {
Matrix[mid][mid] = num;
}
return Matrix;
}
};