Day2 数组: 滑动窗口,螺旋矩阵

暴力解法:

时间复杂度: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个下标 ij

循环终止的条件:只要 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)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值