📝个人主页:@Sherry的成长之路
🏠学习社区:Sherry的成长之路(个人社区)
📖专栏链接:练题
🎯长路漫漫浩浩,万事皆有期待
文章目录
- 有序数组的平方
- 暴力求解
- 双指针法
- 长度最小子数组
- 暴力求解
- 滑动窗口
- 其他问题
- 螺旋矩阵II
- 总结:
有序数组的平方
有序数组的平方
暴力求解
这一题的暴力解法思路就是先将每一个数按顺序平方,存储到一个数组中,处理完全部后再进行排序。时间复杂度应该是O(n*logn)。n是处理每个数的平方的过程,logn是快排的时间。
代码如下
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;
}
};
双指针法
是对于双指针的一种巧妙运用,具体思路为:由于数组传入时已经有序,所以在正负数同时出现时,大数应该都为两边分布,而小数则集中在中间(若两数相等则随意取一个放入),分别设立两个指针指向数组第一个元素和最后一个元素,比较二者平方值,将较大值插入数组,且将对应指针向前或向后移动,直到两个指针遍历完真个数组。
代码如下
class Solution {
public:
vector<int> sortedSquares(vector<int>& nums) {
vector<int> ret(nums.size(),0);//要告诉系统开多大空间
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]))
{
ret[k--]=(nums[right]*nums[right]);
right--;
}
else
{
ret[k--]=(nums[left]*nums[left]);
left++;
}
}
return ret;
}
};
易错
:注意最后一个元素为nums.size()-1,不然越界访问
长度最小子数组
长度最小子数组
这道题对于很多新手来说,都很头疼,一开始做的时候没有思路
暴力求解
这道题暴力破解对于此题是通不过的,数据过多,会显示超过时间。
class Solution {
public:
int minSubArrayLen(int target, 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 >= target) { // 一旦发现子序列和超过了s,更新result
subLength = j - i + 1; // 取子序列的长度
result = result < subLength ? result : subLength;
break; // 因为我们是找符合条件最短的子序列,所以一旦符合条件就break
}
}
}
// 如果result没有被赋值的话,就返回0,说明没有符合条件的子序列
return result == INT32_MAX ? 0 : result;
}
};
时间复杂度:O(n^2)
空间复杂度:O(1)
滑动窗口
所以采用的是双指针的一种思路,用双指针来控制一个区间,这种思路被称为滑动窗口!
滑动窗口:用一层for循环完成原本两层循环进行的操作。循环中变量 j 控制的是滑动窗口的结束位置,而起始位置,会由于数据的判断成功而动态的发生改变。一开始两个都指向第一个位置,j向后移动找到目标值target后,起始指针 i 向后移动,寻找在数组元素>=target的同时,长度最小的子数组。
要点:要明确循环中 j 指向哪个位置,起始位置还是终点位置,其二要知道起始位置什么条件会动态变化
代码如下
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
int result = INT32_MAX;
int sum = 0; // 滑动窗口数值之和
int i = 0; // 滑动窗口起始位置
int subLength = 0; // 滑动窗口的长度
for (int j = 0; j < nums.size(); j++)
{
sum += nums[j];
// 注意这里使用while,每次更新 i(起始位置),并不断比较子序列是否符合条件
while (sum >= target)
{
subLength = (j - i + 1); // 取子序列的长度
result = result < subLength ? result : subLength;
sum -= nums[i++]; // 这里体现出滑动窗口的精髓之处,不断变更i(子序列的起始位置)
}
}
// 如果result没有被赋值的话,就返回0,说明没有符合条件的子序列
return result == INT32_MAX ? 0 : result;
}
};//滑动窗口的妙解
时间复杂度:O(n)
空间复杂度:O(1)
时间复杂度 主要是看每一个元素被操作的次数,每个元素在滑动窗后进来操作一次,出去操作一次,每个元素都是被操作两次,所以时间复杂度是 2 × n 也就是O(n)。
一开始知道这个思路,感觉并没有和双for暴力解决有什么太大的区别,后来发现确实很巧妙,子数组的和被判>=target后,起始位置不断缩小能够帮助我们找到最小长度的子数组,而当缩小后,子数组和小于target时,终点位置还能够向后遍历,实现了动态的窗口滑动,这也是为什么起始位置滑动代码块用while循环,而不是用if来只走一次移动的原因所在。
其他问题
● 加入滑动窗口中有负数怎么办?
如果有负数的话感觉也不能用滑动窗口了,因为有负数的话无论你收缩还是扩张窗口,你里面的值的总和都可能增加或减少,就不像之前收缩一定变小,扩张一定变大,一切就变得不可控了。如果要 cover 所有的情况,那每次 left 都要缩到 right,那就退化为暴力了。
● 双指针和滑动窗口有什么区别,感觉双指针也是不断缩小的窗口。这道题,用两头取值的双指针,结果错了?
因为两头指针走完相当于最多只把整个数组遍历一遍,会漏掉很多情况。滑动窗口实际上是双层遍历的优化版本,而双指针其实只有一层遍历,只不过是从头尾开始遍历的。
螺旋矩阵II
59. 螺旋矩阵 II - 力扣(LeetCode)
刚开始看到时候很懵,不知道如何下手,没有思路,这道题实际上就是模拟矩阵,创建一个二维数组,用循环来走矩阵的边,通过下标的调整来进入内层循环,实现螺旋矩阵的操作。
代码如下
class Solution {
public:
vector<vector<int>> generateMatrix(int n) {
vector<vector<int>> ret (n,vector<int>(n,0)) ;// 使用vector定义一个二维数组
int startx=0;
int starty=0;// 定义每循环一个圈的起始位置
int i=0;
int j=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;// 需要控制每一条边遍历的长度,每次循环右边界收缩一位
while(loop--)
{
j=starty;
i=startx;
// 下面开始的四个for就是模拟转了一圈
// 模拟填充上行从左到右(左闭右开)
for(j=starty;j<n-offset;j++)
{
ret[startx][j]=count++;
}
// 模拟填充右列从上到下(左闭右开)
for(i=startx;i<n-offset;i++)
{
ret[i][j]=count++;
}
// 模拟填充下行从右到左(左闭右开)
for(;j>starty;j--)
{
ret[i][j]=count++;
}
// 模拟填充左列从下到上(左闭右开)
for(;i>startx;i--)
{
ret[i][j]=count++;
}
// 第二圈开始的时候,起始位置要各自加1, 例如:第一圈起始位置是(0, 0),第二圈起始位置是(1, 1)
startx++;
starty++;
// offset 控制每一圈里每一条边遍历的长度
offset++;
}
// 如果n为奇数的话,需要单独给矩阵最中间的位置赋值
if(n%2)
{
ret[mid][mid]=n*n;
return ret;
}
};
注意
:第一是要明确,模拟遍历的各条边代码实现时,应该有规律,而不能一条边一个规律,这样很容易写崩,上述代码就是采用左闭右开的规律,即每条边遍历第一个元素到倒数第二个为止,这样跳出循环后,变量指向的就是下一条边的起点,很方便。
第二是要注意循环判断条件,也就是除了要写n减去最后一个不能取的数字以外,还要加上起始位置的下标(第一圈模拟可能看不出作用,但是对后面内圈模拟作用很大)加上起始下标才能不出错。
第三是offset的改变,每进入一圈,各边减少两个数字的遍历,以第一条边为例,第二圈少了最左边的一个数字,和最右边的数字。
第四是n%2的细节,如果圈数是奇数,则必有一个元素没有被加入进去,那么在后面判断一下单独加入即可,关于循环为什么是n/2,可以通过举例找规律,例如n=3,就是遍历一次,在后面加入中间元素即可。
关于offset不太好理解:offset的意义在于 结束一圈后 起始位置向后移 结束位置向前移。可以画和n=4或者n=5的矩阵,会比较好理解。offset就是由于要去更向内的一圈,内圈元素更少的地方循环,所以循环的次数变少了
总结:
今天的三道题感觉自己收获了不少,特别是滑动窗口的使用。和解决螺旋矩阵时对一些变量的定义和利用。接下来,我们继续进行算法练习·。希望我的文章和讲解能对大家的学习提供一些帮助。
当然,本文仍有许多不足之处,欢迎各位小伙伴们随时私信交流、批评指正!我们下期见~