暴力解法:
时间复杂度:O(nlogn) (取决于快排)
将所有的元素都进行平方,然后再排序(可以用快排),这样就可以得到一个所有元素经过平方之后的有序数组。
代码: (核心代码模式)
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;
}
};
具体的时间复杂度:
for循环:O(n) ✖️ 快排:O(nlogn)
因此时间复杂度是 O(n + nlogn), 可以说是O(nlogn)的时间复杂度,但为了和下面双指针法算法时间复杂度有鲜明对比,记为 O(n + nlog n)。
⚠️排序使用了库函数sort
双指针解法:
时间复杂度:O(n)
数组其实是有序的,暴力解法并没有利用到这一特点。
由于负数的存在,负数平方之后可能就会成为最大数,因此数组平方后的最大值就应该在数组的两端,不是最左边就是最右边,不可能是中间。
此时可以考虑双指针法,i指向起始位置,j指向终止位置。
定义一个新的数组result
来装进行平方之后的数组元素。
定义一个索引下标,k = nums.size() - 1; 下标的作用:新的数组要从大到小来更新。
因为每次取的时候都是取了一个最大值(两头向中间取,先取最大,再取次大)。
这样的话,更新result数组也要下标由大到小来更新,最后得到的这个result数组才是一个按非递减顺序排序(元素从小到大)的集合。
通过for循环定义2个下标 i
和 j
,
循环终止的条件:只要 i <= j
就继续执行该循环
Q:若只写
i < j
会怎样?A:那我们就把i和j相等的时候指向的这个元素给落下了。
⚠️注意:for循环的条件里面不能写i++,j--
eg: for (int i = 0, j = 0; j = nums.size(); i++, j--) ❌
因为i++ 和 j--都是有条件的,取决于两头的元素谁比较大,取了那个大的元素,对应的下标再做 + +
or - -
。
动画:
代码:
class Solution {
public:
vector<int> sortedSquares(vector<int>& nums) {
vector<int> result(nums.size(), 0);
int k = nums.size() - 1;
for (int i = 0, j = nums.size() - 1; i <= j; ) {
if (pow(nums[i], 2) > pow(nums[j], 2)) {
result[k--] = pow(nums[i], 2);
i++;
}
else {
result[k--] = pow(nums[j], 2);
j--;
}
}
return result;
}
};
总结:代码很简单,但是很能体现出对这个双指针操作的细节。
209.长度最小的子数组
暴力解法:
时间复杂度:O(n^2)
最直接的想法:2层for循环进行遍历:第一层for循环控制区间的起始位置,第二层for循环控制区间的终止位置。
把数组的所有区间情况都遍历出来,然后找到 >= target
的最小区间长度。
在这个区间里面不断搜索,把所有区间情况都枚举出来,然后判断 >= target
的最小长度是多少,最后返回这个最小长度。
//brute force,time limit exceeded
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
int len=INT_MAX;
for(int i=0;i<nums.size();++i){
int sum=0;
for(int j=i;j<nums.size();++j){
sum+=nums[j];
if(sum>=target){
len=len<(j-i+1)?len:(j-i+1);
break;
}
}
}
if(len==INT_MAX){
return 0;
}
return len;
}
};
力扣更新了数据,暴力解法会超时。
滑动窗口:
所谓滑动窗口,就是不断的调节子序列的起始位置和终止位置,从而得出我们要想的结果。
用一个for循环来做2个for循环所做的事情。
思考:这个索引下标
j
表示的究竟是滑动窗口里面的终止位置还是起始位置?
可以先假设 j
表示的是起始位置,
若 j
表示的是起始位置,这个for循环一次一次把索引下标向后移动,这个终止位置要把后面所有的元素遍历一遍,才能返回所有以这个起始 i
为起始位置的集合,然后我们再去判断这个集合里面的所有元素:
如果 >= target
,去搜集所有 >= target
这些集合里面的所有的长度,再取一个最小的。 如果终止位置是一个一个向后移动的话,那么和这个暴力的解法又存在什么区别呢?
此时你会发现:如果这一个for循环里面的这个j表示的是起始位置的话,那终止位置依然要把所有位置都遍历一遍,那么它的思路就和暴力是一样的。
因此这一个for循环里面的j一定指向的是终止位置,而起始位置需要我们用动态移动的策略来移动起始位置,这样才能用一个for循环的思路来解决这道题。
🪟滑动窗口解题的关键:窗口的起始位置如何移动?
终止位置随着for循环一个一个向后移动,那什么时候移动起始位置?
如果我们的终止位置指向这里,这个集合里面的元素之和如果 >= target 的话,说明这是符合条件的一个集合,我们收集它的长度之后,起始位置就可以向后移动了。
从而缩小当前这个集合,看下一个集合是否符合我们的条件。
就是当我们发现集合里面所有的元素和 >= target,我们再去移动起始位置,这样就实现了动态调整起始位置,来去收集不同长度区间里面的和。
sum收集滑动窗口里面的和
我们需要有一个集合,通过这个集合里面的元素之和来和target做比较,
res是最终取的最小的长度。
⚠️应该写
if(sum >= target)
还是while(sum >= target)
?
如果你写的是if,看看这个样例: 输入:target = 100, nums = [1,1,1,1,1,100]
动画:
代码:
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
int len=INT_MAX;
int sum=0;
int i=0;
for(int j=0;j<nums.size();++j){
sum+=nums[j];
while(sum>=target){
len=len<(j-i+1)?len:(j-i+1);
sum-=nums[i];
++i;
}
}
if(len==INT_MAX){
return 0;
}
return len;
}
};
一些录友会疑惑为什么时间复杂度是O(n) 。
不要以为for里放一个while就以为是O(n^2)啊, 主要是看每一个元素被操作的次数,每个元素在滑动窗后进来操作一次,出去操作一次,每个元素都是被操作两次,所以时间复杂度是 2 × n 也就是O(n)
在这段代码中,外层使用了一个for循环来遍历数组nums,内层使用了一个while循环来处理滑动窗口的扩展和收缩过程。
虽然内层while循环的执行次数不是固定的,取决于满足条件的窗口个数,但是总体来说,每个元素最多会被指针i和指针j分别访问一次,因此内层while循环的执行次数总和不会超过N次。
考虑最坏情况,每个元素都符合条件,需要进行N次窗口收缩操作。在每次窗口收缩时,指针i会向右移动,但是指针j会继续向右移动直到不满足条件为止。因此,内层while循环的执行次数总和不会超过2N次。
由于内层while循环的执行次数是常数级别的,所以不会改变时间复杂度的数量级。因此,这段代码的时间复杂度仍然是O(N)。
59.螺旋矩阵
二维数组:横i竖j,横着走移动j,竖着走移动i。
用start标记这一行的开始位置,offset标记这一行的结束位置。
遵循左闭右开原则,每行填充包括左边,不包括右边。
转n/2圈,若n是奇数需要单独写条件。
1. 移动j,从starty到n-offset,i(横坐标)恒为startx;
2. 移动i,从startx到n-offset,j恒为当前值;
3. 移动j,从当前位置移动到offset(或者starty+1我想),i为当前值;
4. 移动i,从当前位置移动到offset(或者startx+1我想),j为当前值。
代码:
class Solution {
public:
vector<vector<int>> generateMatrix(int n) {
vector<vector<int>> ans(n,vector<int>(n,0));
int startx=0;
int starty=0;
int offset=1;
int i=0;
int j=0;
int num=1;
int loop=n/2;
while(loop--){
for(j=starty;j<n-offset;++j){
ans[startx][j]=num++;
}
for(i=startx;i<n-offset;++i){
ans[i][j]=num++;
}
for(;j>=offset;--j){
ans[i][j]=num++;
}
for(;i>=offset;--i){
ans[i][j]=num++;
}
++startx;
++starty;
++offset;
}
if(n%2==1||n==1){//取余,不是除法!
ans[n/2][n/2]=num;
}
return ans;
}
};
time complexity: O(N^2)
space complexity: O(N^2)