第二天(ง •_•)ง(ง •_•)ง(ง •_•)ง,编程语言:c++
目录
977.有序数组的平方:
文档讲解:代码随想录有序数组的平方
视频讲解:手撕有序数组的平方
题目:
初看: 第一想法是把数组内每个元素平方后,在进行分别排序,时间复杂度为O(n)+排序算法时间复杂度。(复习不同排序算法时间复杂度:插入排序O(n^2),稳定性:稳定;希尔排序最佳情况O(n^1.3),稳定性:不稳定;冒泡排序O(n^2),稳定性:稳定;快速排序O(nlogn),稳定性:不稳定;简单选择排序O(n^2),稳定性:不稳定;堆排序,建堆O(n)、排序O(nlogn),稳定性:不稳定)
学习:看完文档讲解和视频讲解后。理解到数组是有序的,可以采用双指针的方式,一个指向头,一个指向尾,能使得时间复杂度降低为O(n)。
暴力解法代码(平方后排序):
//时间复杂度O(n+nlogn)
//空间复杂度O(1)
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)
//空间复杂度O(1) 注意一般返回值不算做空间复杂度内
class Solution {
public:
vector<int> sortedSquares(vector<int>& nums) {
vector<int> ans(nums.size(),0);
//存在正负差
int left = 0; //左指针
int right = nums.size() - 1; //右指针
int k = ans.size() - 1;
while (left <= right) { //区间左闭右开
int leftmax = nums[left]*nums[left];
int rightmax = nums[right]*nums[right];
if (leftmax >= rightmax) {
ans[k--] = leftmax;
left++;
}
else {
ans[k--] = rightmax;
right--;
}
}
return ans;
}
};
收获:
- 数组题目是否使用双指针,我认为主要看两点:1.数组是否需要交换元素的位置;2.数组是否需要遍历。当满足这两个条件的时候就可以考虑是否能够采用双指针的方式。
- 双指针方法一般有:左右指针,快慢指针两种,不同的在于左右指针总共遍历了下标一次,快慢指针实际最多需要遍历每个下标两次。根据实际需求选择不同的方法。
209. 长度最小的子数组
文档讲解:代码随想录长度最小的子数组
视频讲解:手撕长度最小的子数组
题目:
初看:第一想法通过从头遍历数组,使用两个for循环。内层循环寻找合适的子数组,当子数组元素大于等于目标值时,更新答案长度(取最小值),然后跳出内层循环,外层循环i++,接着寻找新的子数组。
学习:可以采用双指针构成一个滑动窗口的方式来寻找合适的子数组。使用两个指针分别指向子数组区间的左右边界。当区间内元素和大于等于目标值时,更新答案长度(取最小值),之后左边边界向内靠拢left++,继续寻找合适的子数组。此处要注意,右边新进入的数组值可能很大,左边边界可以不断向内靠拢,直到区间内数组和小于目标值为止,因此每次答案长度的更新也可以由此为依据进行。
暴力解法代码:
//时间复杂度O(n^2)
//空间复杂度O(1)
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;
}
};
滑动窗口(双指针)代码:
//时间复杂度O(n)
//空间复杂度O(1)
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
int ans = INT32_MAX;
int sum = 0;
for (int left = 0, right = 0; right < nums.size(); right++) {
sum += nums[right];
// 如果此时窗口内的元素和大于目标值,则修改ans的值
if (sum >= target){
//若新进入的元素太大,则左边界可一直往右移动,直到达到满足的最小窗口
while (sum >= target) {
sum -= nums[left++];
}
ans = min(ans, right - left + 2);
}
}
//若ans没有被赋值则返回0
return ans < INT32_MAX ? ans : 0;
}
};
收获:
- 双指针的额外用法滑动窗口,通过两个指针,实现观测指针内数据的效果,通过当前子序列和大小的情况,不断调节子序列的起始位置。
- c++内的部分极值表示方法:INT_MIN = -2147483648;INT_MAX = 2147483647;FLT_MAX = 3.40282e+038;FLT_MIN = 1.17549e-038;DBL_MAX = 1.79769e+308;DBL_MIN = 2.22507e-308;SHRT_MAX = 32767;SHRT_MIN = -32768。
59.螺旋矩阵
文档讲解:代码随想录螺旋矩阵
视频讲解:手撕螺旋数组
题目:
初看:第一想法是寻找数学规律按照一行一行进行赋值处理,但发现不同的n值差别很大,pass。第二想法按照箭头顺序依次进行赋值处理,实际写下来,循环条件难以确定。
学习:循环条件的确定因严格遵守循环不变量原则,确定好循环体是什么,考虑到本题存在四条线路,顺序分别为从左到右,从上到下,从右到左,从下到上,接着重复此循环顺序。若n为奇数,则会导致中间值多一个,多一次从左到右,但由于只多一个值因此可以单独进行处理,不用进入循环。则循环次数loop = n/2 (每次向内缩减2个单位),每次循环进行从左到右,从上到下,从右到左,从下到上四条路线赋值,最后n为奇数时,单独为中间值赋值。
代码:
//时间复杂度O(n^2)
//空间复杂度O(1)
class Solution {
public:
vector<vector<int>> generateMatrix(int n) {
vector<vector<int>>ans(n,vector<int>(n,0));
int loop = n/2; //循环次数
int line0= 0; //行
int linen = n - 1;
int col0 = 0; //列
int coln = n - 1;
int num = 1; //赋值
while(loop--) {
//从左到右边界
//行不变,列变化
for (int i = col0; i < coln; i++) {
ans[line0][i] = num;
num++;
}
//从上到下边界
//列不变,行变化
for (int j = line0; j < linen; j++) {
ans[j][coln] = num;
num++;
}
//从右到左边界
//行不变,列变化
for (int i = coln; i > col0; i--) {
ans[linen][i] = num;
num++;
}
//从下到上边界
//列不变,行变化
for (int j = linen; j > line0; j--) {
ans[j][col0] = num;
num++;
}
//一遍循环后缩进1格
line0++;
linen--;
col0++;
coln--;
}
//单独处理奇数情况下的中间元素
if (n % 2) {
ans[n/2][n/2] = num;
}
return ans;
}
};
收获:
- 本题所采用的是模拟的方法,关键在于找到循环体,确定循环不变量,牢牢注意每次循环要进行的步骤。
数组总结
文档讲解:数组理论基础
数组是存放在连续内存空间上的相同类型数据的集合。
因为数组中的元素在内存空间的地址是连续的,所以在进行数组中间元素的删除或者增添的时候,就需要移动其他的元素地址。
数组的经典方法:
- 二分法:例如力扣上的704.二分查找,通过从中间元素开始往左右查找,避免一次遍历所有元素。二分查找法的时间复杂度为O(logn)。
- 双指针法:通过一个快指针和一个慢指针在一个for循环下,完成两个for循环的工作。也存在使用左右指针的方式。快慢指针从同一个地点遍历方向相同,左右指针从一头一尾往中间进行遍历。
- 滑动窗口:通过两个指针建立一个滑动窗口,滑动窗口的精妙之处在于根据当前子序列和大小的情况,不断调节子序列的起始位置。从而将O(n^2)的暴力解法降为O(n)。
- 模拟行为:同样在数组内很常见,要确定好模拟的需求,确定循环体,保持循环不变量原则。
总结:今天的题目难度比昨天大一些,巩固了双指针的用法,学习到了新的滑动窗口和模拟行为的解题思路。