【算法练习Day2】有序数组的平方&&长度最小子数组&&螺旋矩阵II

在这里插入图片描述

​📝个人主页:@Sherry的成长之路
🏠学习社区:Sherry的成长之路(个人社区)
📖专栏链接:练题
🎯长路漫漫浩浩,万事皆有期待

有序数组的平方

有序数组的平方

暴力求解

这一题的暴力解法思路就是先将每一个数按顺序平方,存储到一个数组中,处理完全部后再进行排序。时间复杂度应该是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就是由于要去更向内的一圈,内圈元素更少的地方循环,所以循环的次数变少了

总结:

今天的三道题感觉自己收获了不少,特别是滑动窗口的使用。和解决螺旋矩阵时对一些变量的定义和利用。接下来,我们继续进行算法练习·。希望我的文章和讲解能对大家的学习提供一些帮助。

当然,本文仍有许多不足之处,欢迎各位小伙伴们随时私信交流、批评指正!我们下期见~

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Sherry的成长之路

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值