代码随想录算法训练营第二天| 977. 有序数组的平方 209. 长度最小的子数组 59. 螺旋矩阵II
977. 有序数组的平方
题目
给你一个按 非递减顺序 排序的整数数组 nums
,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。
示例 1:
输入:nums = [-4,-1,0,3,10]
输出:[0,1,9,16,100]
解释:平方后,数组变为 [16,1,0,9,100]
排序后,数组变为 [0,1,9,16,100]
示例 2:
输入:nums = [-7,-3,2,3,11]
输出:[4,9,9,49,121]
思路1
首先,如果是暴力的方式,可以先将数组中的元素进行平方,然后再排序
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;
}
};
- 时间复杂度:O(n+nlogn)=O(nlogn)
- 空间复杂度:O(1)
思路2
还可以采用双指针的方式,如下图所示:
因为数组里面有负数,平方之后的负数可能会大于正数,所以结果集的最大值应该在输入数组的两端,具体步骤如下:
首先初始化i和j,分别指向输入数组的第一个元素和最后一个元素
然后创建结果集,并将k指向结果集的最后一个元素
因为结果集的size和输入数组的size是一样的,所以可以通过size控制循环的次数
在循环体内部比较i、j所指向元素的平方的大小,或者比较其绝对值的大小
如果nums[i]*nums[i] < nums[j]*nums[j]
那么result[k] = nums[j]*nums[j]
然后将j--
如果nums[i]*nums[i] >= nums[j]*nums[j]
那么result[k] = nums[i]*nums[i]
然后将i++
最终返回result
具体的代码如下:
class Solution {
public:
vector<int> sortedSquares(vector<int>& nums) {
// 初始化i、j
int i = 0;
int j = nums.size()-1;
// 创建结果集
vector<int> result(nums.size(),0);
// 遍历输入数组
for(int k = nums.size()-1; k>=0;k--)
{
// 大小判断
if(nums[i]*nums[i] < nums[j]*nums[j])
{
result[k] = nums[j]*nums[j];
// 移动右指针
j--;
}else{
result[k] = nums[i]*nums[i];
// 移动左指针
i++;
}
}
return result;
}
};
- 时间复杂度:O(n)
- 空间复杂度:O(1)
209. 长度最小的子数组
题目
给定一个含有n
个正整数的数组和一个正整数target
。
找出该数组中满足其总和大于等于target
的长度最小的连续子数组[nums_l, nums_l+1, ..., nums_r-1, nums_r]
,并返回其长度。如果不存在符合条件的子数组,返回0。
示例 1:
输入:target = 7, nums = [2,3,1,2,4,3]
输出:2示例 2:
输入:n = 1
输出:[[1]]
解释:子数组[4,3] 是该条件下的长度最小的子数组。
示例 2:
输入:target = 4, nums = [1,4,4]
输出:1
示例 3:
输入:target = 11, nums = [1,1,1,1,1,1,1,1]
输出:0
思路1
如果对该题目进行暴力求解的话,可以采用两层循环遍历所有的子数组,遍历的过程如下图所示,通过i控制遍历子数组的起点,通过j控制遍历子数组的终点。
具体代码如下:
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
// 定义结果
int result = INT32_MAX;
// 第一层循环确定遍历的起点
for(int i = 0 ; i < nums.size(); i++){
// 中间变量用于存储区间的和
int sum = 0;
// 第二层循环确定遍历的终点
for(int j = i; j < nums.size(); j++){
// 计算区间内的和
sum = sum + nums[j];
// 判断和是否满足范围
if(sum>=target)
{
// 计算区间长度
int subLength = j-i+1;
result = result < subLength ? result : subLength;
break;
}
}
}
return result == INT32_MAX ? 0 : result;
}
};
- 时间复杂度:O(n^2)
- 空间复杂度:O(1)
思路2
除了利用暴力的解法,还可以用滑动窗口的方法,刚刚的暴力算法中利用两个循环来控制子数组区间的起始位置和终止位置,在滑动窗口法中,只需要利用一个循环来控制子数组的终止位置,起始位置根据条件自动移动即可,具体的示意图如下:
在滑动窗口中,需要确定三点:
- 窗口内的是什么?
- 如何移动窗口的起始位置
- 如何移动窗口的结束位置
明确了这三点,我们便可以容易地理解了该方法的精髓
- 窗口内是满足和≥target的子数组
- 当窗口的值≥s时,起始位置移动
- 当窗口的值<s时,结束位置移动,可以通过数组的长度来确定终止位置需要移动的次数
具体形式如下:
注意:这里的while循环进入的条件体现了滑动窗口法的第一要素,窗口内的是什么
具体代码如下:
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
int sum = 0;
int result = INT32_MAX;
int subLength = 0;
int i = 0;
// 移动终止位置指针
for(int j = 0; j<nums.size();j++){
// 计算和
sum = sum + nums[j];
// 出现满足要求子数组
while(sum>=target){
// 计算长度
subLength = j - i + 1;
// 记录最小长度
result = result < subLength ? result : subLength;
// 更新sum
sum = sum - nums[i];
// 移动起始位置
i++;
}
}
return result < INT32_MAX ? result : 0;
}
};
总结上述过程
通过for循环移动区间的终止位置指针
在循环体内部计算sum
对于满足要求的sum,计算长度并进行记录,需要注意的是要在移动起始位置指针之前,先更新sum
对于不满足要求的sum移动终止位置指针
返回结果
- 时间复杂度:O(n)
- 空间复杂度:O(1)
59. 螺旋矩阵 II
题目
给你一个正整数n
,生成一个包含1
到n^2
所有元素,且元素按顺时针顺序螺旋排列的nxn
正方形矩阵matrix
。
示例 1:
输入:n = 3
输出:[[1,2,3],[8,9,4],[7,6,5]]
示例 2:
输入:n = 1
输出:[[1]]
思路
这个问题看似简单,写着写着就懵了
下面总结一下自己的错误
- 没有考虑每一层旋转的起始位置点,应该定义变量存放每层的起始位置,不然转到第二圈之后不好索引
- 没有考虑循环过程中的坐标的记录,具体问题是当第一行转完之后,没有办法在从上向下旋转的时候进行索引
最终的代码如下:
class Solution {
public:
vector<vector<int>> generateMatrix(int n) {
// 存放结果
vector<vector<int>> res(n,vector<int>(n,0));
// 每次循环的起始点坐标
int startx=0, starty = 0;
// 循环的次数,一共转几圈
int loop = n/2;
// 每条边遍历的长度 通过offset计算
int offset = 1;
// 计数器
int count=1;
// 矩阵中间位置
int mid = n/2;
// 待填充点的坐标
int i,j = 0;
for(;loop>0;loop--){
i = startx;
j = starty;
// 从左到右转圈
for(j;j<n-offset;j++){
res[i][j] = count;
count++;
}
// 从上到下转圈
for(i;i<n-offset;i++){
res[i][j] = count;
count++;
}
// 从右向左转圈
// 注意这里要和起始点坐标进行比较而不是0
for(j;j>starty;j--){
res[i][j] = count;
count++;
}
// 从下向上转圈
for(i;i>startx;i--){
res[i][j] = count;
count++;
}
// 完成一次转圈
// 更新起始点坐标
startx = startx+1;
starty = starty+1;
// 更新offset
offset = offset+1;
}
// 处理奇数情况
if(n%2!=0){
res[mid][mid] = count;
}
return res;
}
};
这里还犯了一个错误,在从右向左转圈和从下向上转圈的时候没有考虑和起始点比较的情况。
- 时间复杂度 O(n^2): 模拟遍历二维矩阵的时间
- 空间复杂度 O(1)
🤗 总结归纳
本文讨论了三个算法问题:977.有序数组的平方,209.长度最小的子数组,和59.螺旋矩阵II。对于问题977,提出了两种解决方案,一种是暴力解法,先将数组中的元素进行平方,然后再排序;另一种是双指针法。对于问题209,也提出了两种解决方案,一种是暴力解法,通过两层循环遍历所有的子数组;另一种是滑动窗口法,只需要利用一个循环来控制子数组的终止位置,起始位置根据条件自动移动。对于问题59,给出了一种解决方案,通过模拟螺旋路径填充矩阵。