Day2 记录代码随想录
第一题 力扣977 有序数组的平方
给你一个按 非递减顺序 排序的整数数组 nums,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。
题目链接:力扣题目链接
解题思路:除了暴力求解之外,考虑题目已有条件为有序数组,平方以后最小值一定在中间部分,越往两边越小,可考虑使用双指针。题解如下。
暴力求解
本题有两个需求,一是求平方,二是递减排序。依次实现功能即可。代码如下:
//有序数组平方(暴力求解)
class Solution {
public:
vector<int> sortedSquares(vector<int>& nums) {
int i = 0;
for (i = 0; i < nums.size() ; i++) {
nums[i] = nums[i]*nums[i];
}
sort(nums.begin(),nums.end()); //记住A.begin()和A.end()的用法
return nums;
}
};
此解法的时间复杂度为O(n + nlogn)。
sort函数是STL(standard template library)库中的一种排序函数,使用方式为sort(a.begin(),a.end()),函数会对参数迭代器范围内的元素进行排序,默认为升序排序,如果要降序排序,可给入第三个参数greater<int>(),写成sort(a.begin(), a.end(), greater<int>())。如果需要其他类型的排序法则,可以依自己需要,定义一个bool函数,将函数名(假设为cmp())给第三个参数。sort会将范围内的元素依次传给cmp函数进行排序,通过这样可以实现平均分排序、求余排序甚至结构体排序等各种功能。
双指针求解
对于一个非递减的有序数组,在可能存在负数的情况下,对其求平方,得到的新数组必然存在这种规律:有一个最小值,且位于数组中间位置或者最左边;从最小值往左或右都为有序数组。
可以定义两个指针,left 和right ,分别指向原数组的最左边和最右边,同时定义一个同样长度的新数组,并定义新数组指针为 length,初始值设为最大值,即nums.size()-1。由于最大值一定是在新数组的右边,在原数组的两侧,那么分别比对left 元素的平方和 right 元素的平方即可,然后将较大的值存放到新数组的最右边,此时不要忘记将 新数组的指针 length-1 哦!
(ps:突然想到,如果要求生成的新数组为降序怎么做呢?只要将length初始值设为0,且每次循环将length+1就行。)
每次给值以后,要将对应的指针(left或者right)调整到下一个位置。
这里跳出循环的条件:当每个值都存到新数组时就完成了,即 i < num.size()
代码如下:
//有序数组平方(双指针)
class Solution {
public:
vector<int> sortedSquares(vector<int>& nums) {
int left = 0, right = 0, length = 0;
right = nums.size() - 1;
length = nums.size() - 1;//出错一次,数组下标未按大小减一
vector<int> result(nums.size(),0);//出错一次,如果容器需要不按顺序插值,则定义时需要初始化大小和值。
for(int i = 0; i<nums.size(); i++) {
if( (nums[left] * nums[left]) <= (nums[right] * nums[right]) ) {
result[length--] = nums[right] * nums[right];
right--;
}
else {
result[length--] = nums[left] * nums[left];
left++; //出错一次,从left插值以后,忘记调整left数组位置。
}
}
return result;
}
};
代码时间复杂度为O(n)。
以上代码更精简的写法为下面这种:
// 有序数组平方(双指针)
class Solution {
public:
vector<int> sortedSquares(vector<int>& nums) {
int length = nums.size() - 1;
vector<int> result(nums.size(), 0);
for (int i = 0, j = length; i <= j;) { //i=j时是有意义的
if (nums[i] * nums[i] <= nums[j] * nums[j]) {
result[length--] = nums[j] * nums[j--];
} else
result[length--] = nums[i] * nums[i++];
}
return result;
}
};
将左右指针定义为 i , j ,循环条件中不给出 i , j 的变化,而是在步骤中给出,哪边大哪边往里挪。跳出循环的条件为:当 i 和 j 相遇时则全部计算完成。需要着重考虑的是 i = j 是有意义的,需要循环一次。因为比到最后面总会剩下一个值,这个值就是最小值,需要放到新数组的最左边。
第二题 力扣209 长度最小的子数组
给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的 连续 子数组,并返回其长度。如果不存在符合条件的子数组,返回 0。
题目链接:力扣题目链接
解题思路:因为需要找数组中的子数组,且顺序是不能打乱的,考虑采用滑动窗口。
当然了,对于我这种菜鸡来说,第一时间想的肯定是暴力求解,于是我先写了一个暴力求解的代码:
// 长度最小子数组(暴力求解)_超时 O(n^2)
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
int result = 0;
int length = INT32_MAX; // 32位int的最大值
for (int i = 0; i < nums.size(); i++) {
result = 0;
for (int j = i; j < nums.size(); j++) {
result += nums[j];
if (result >= target) {
length = length < (j - i + 1) ? length : (j - i + 1);
}
}
}
return length < INT32_MAX ? length : 0;
}
};
果不其然,成功超时了。题目要求O(n)。
于是采用滑动窗口法。
如图所示,同样也是一种双指针法,定义两个指针,分别表示窗口的左右边界,初始值都为0,先挪动右边界,同时累加滑入窗口的值,扫描和与target的关系,当sum>=target时,就记录此时的窗口大小,然后将左边界右移,同时减去滑出窗口的值,判断是否满足条件sum>=target,只要满足条件,就继续右移左边界,直到不满足条件为止,同时对窗口大小length取小。
代码如下:
// 长度最小子数组(滑动窗口)
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
int sum = 0; //窗口和
int length = INT32_MAX; // 32位int的最大值
int i = 0;
for ( int j = 0 ; j < nums.size() ; j++) {
sum += nums[j];
while( sum >= target ) {
length = length < ( j - i + 1 ) ? length : ( j - i + 1 );
sum -= nums[i++];
}
}
return length < INT32_MAX ? length : 0;
}
};
第三题 力扣59 螺旋矩阵II
给定一个正整数 n,生成一个包含 1 到 n^2 所有元素,且元素按顺时针顺序螺旋排列的正方形矩阵。
示例:
输入: 3 输出: [ [ 1, 2, 3 ], [ 8, 9, 4 ], [ 7, 6, 5 ] ]
题目链接:力扣题目链接
解题思路:这道题是一个模拟过程,之前在二分法中,有讲到循环不变量法则,就是在循环的过程中,循环的边界要保持统一。
在这道题里,可以将每个外围圈看做一次循环,每次循环可以分解为四步,图解如下:
那么,每次循环只需要重复的做这四步就可以,分别是从左到右,从上到下,从右到左,从下到上。
从第一次循环开始看,第一次第一步,需要设置一个起始点:start_x = 0 , start_y = 0 ,然后从左往右填值,此时 i 不用变,j 递增,value++ , 结束条件为 n - 当前循环次数,即为 n - offset ,
循环结束后,此时各变量值为 i = 0 , j = n , value = n 。
然后第二步,从上往下,此时 j = n保持不变 ,i = 0 递增 , value 递增 ,结束条件为大于等于 n - 当前循环次数,即循环条件为 i < n - offset 。
第三步和第四步类似,只是改成 i 或 j 递减。
一轮循环结束以后,需要重置下一次循环的起始点,按图解来看,只需要坐标各加一就行了。循环圈数offset也需要加一,保证下一次循环的第一步和第二步能在圈内停止。
还有一个问题就是要确定循环次数了,观察规律,不难发现,当n等于偶数时,正好会有 n/2 个圈,而当 n为奇数时,会有 n/2个圈,还剩下一个中心点( n/2 , n/2 ),只需要在最后将这个点赋值为n^2就可以啦。
至此就可以实现了,代码如下:
//模拟行为
class Solution {
public:
vector<vector<int>> generateMatrix(int n) {
vector<vector<int>> result(n, vector<int>(n, 0));
int loop = n / 2; // 循环圈数
int center = n / 2; //中心位置
int value = 1; //元素值
int i = 0 , j = 0; //写入下标
int start_x = 0 , start_y = 0; //循环起始位置
int offset = 1; //缩进每条边的长度
while( loop-- ) {
i = start_x;
j = start_y;
//写左到右
for ( j = start_y ; j < n - offset ; j++ ) {
result[i][j] = value++;
}
//写上到下
for ( i = start_x ; i < n - offset ; i++ ) {
result[i][j] = value++;
}
//写右到左
for ( ; j > start_y ; j--) {
result[i][j] = value++;
}
//写下到上
for ( ; i > start_x ; i--) {
result[i][j] = value++;
}
//更新下次循环初始位置和边缩进长度
start_x++;
start_y++;
offset++;
}
//写中心值
if ( n % 2 != 0)
result[center][center] = value++;
return result;
}
};
数组总结
两天的刷题学习,学到的方法有二分法、双指针、滑动窗口和模拟过程深刻理解了代码的编写过程与功能实现的思考过程,总得来说收获颇丰,下图是代码随想录的数组部分总结图,贴过来记录一下hhh。
ps:文章中有部分图文和理论来自代码随想录。