Day02打卡.有序数组的平方 and 长度最小的子数组 and 螺旋矩阵

有序数组的平方

  • 给你一个按 非递减顺序 排序的整数数组 nums,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。

示例 1:

  • 输入:nums = [-4,-1,0,3,10]

  • 输出:[0,1,9,16,100]

  • 解释:平方后,数组变为 [16,1,0,9,100],排序后,数组变为 [0,1,9,16,100]

示例 2:

  • 输入:nums = [-7,-3,2,3,11]

  • 输出:[4,9,9,49,121]

  • 对应leetcode977题

  • 本题我最先想到的是暴力解法,但是具体实现有些遗忘,翻过笔记后成功AC

暴力解法

  • 每个数先进行平方,然后利用快速排序

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,0,nums.size()-1); // 快速排序
        return nums;
​
    }
    void sort(vector<int> &nums,int low,int high){
      if(low<high){ //表的长度大于1
            int pivotloc = partition(nums,low,high);
          //将nums数组一分为二,pivotloc为枢轴元素排好的位置
            sort(nums,low,pivotloc-1);//对低子表递归排序
            sort(nums,pivotloc+1,high);//对高子表递归排序
​
      }
​
    }
    int partition(vector<int> &nums,int low,int high){
        int pivotkey = nums[low]; //设表中第一个元素为枢轴,对表进行划分
        while(low<high){
            while(low<high&&nums[high]>=pivotkey){
                --high;
            }
            nums[low]=nums[high];//将比枢轴小的元素移动到左端
             while(low<high&&nums[low]<=pivotkey){
                ++low;
            }
            nums[high]=nums[low];//将比枢轴大的元素移动到右端
        }
        nums[low]=pivotkey;//枢轴元素放到最终位置
        return low;
​
    }
};
  • 时间复杂度为O(n + nlogn)

双指针法

  • 数组是有序的, 但是负数平方之后可能成为最大数了

  • 所以数组平方的最大值就在数组的两端,不是最左边就是最右边,不可能是中间,且大小由中间向两端趋近

  • 此时可以考虑双指针法了,i指向起始位置,j指向终止位置。

  • 定义一个新数组result,和nums数组一样的大小,让k指向result数组终止位置。

  • 如果nums[i] * nums[i] < nums[j] * nums[j] 那么result[k--] = nums[j] * nums[j];

  • 如果nums[i] * nums[i] >= nums[j] * nums[j] 那么result[k--] = nums[i] * nums[i];

class Solution {
public:
    vector<int> sortedSquares(vector<int>& nums) {
      vector<int> result(nums.size(),0) ;
        int k =result.size()-1;
        for(int i = 0,j=nums.size()-1;i<=j;){
            if(nums[i]*nums[i]<nums[j]*nums[j]){
                result[k--]=nums[j]*nums[j];
                j--;
            }
            else{  //已经包含>=的情况
                result[k--]=nums[i]*nums[i];
                i++;
            }
​
        }
        return result;
​
    }
   
};
  • 时间复杂度为O(n),相对于暴力排序的解法O(n + nlog n)还是提升不少的

长度最小的子数组

给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的 连续 子数组,并返回其长度。如果不存在符合条件的子数组,返回 0。

示例:

  • 输入:s = 7, nums = [2,3,1,2,4,3]

  • 输出:2

  • 解释:子数组 [4,3] 是该条件下的长度最小的子数组。

提示:

  • 1 <= target <= 10^9

  • 1 <= nums.length <= 10^5

  • 1 <= nums[i] <= 10^5

  • 力扣209题

暴力解法

  • 暴力解法是 两个for循环,然后不断的寻找符合条件的子序列,时间复杂度很明显是O(n^2)。

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;  //内循环找完更新sum为0;从i++位置重新寻找
            for(int j=i;j<nums.size();j++){ 
                sum=sum+nums[j]; //求和
                if(sum>=target){ //一旦发现子序列和超过了target,更新result
                    subLength=j-i+1; //取子序列的长度
                    result = result < subLength ? result : subLength; 
                    break;//因为找的是符合条件的最短子序列,一旦符合就跳出内循环,从i++位置重新寻找
                }
            }
        }
 // 如果result没有被赋值的话,就返回0,说明没有符合条件的子序列
          return result == INT32_MAX ? 0 : result;
    }
};
  • 时间复杂度:O(n^2)

  • 空间复杂度:O(1)

  • 但是力扣更新了数据,运行超时

下面是我当时写了的,不过发现还是暴力

class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        int i=0,j=0,sum=0;
        int result = INT32_MAX; // 最终的结果
        int subLength = 0; // 子序列的长度
        while(i<=j&&j<nums.size()){
            int temp=sum+nums[j];
            if(temp>=target){
                subLength=j-i+1; //取子序列的长度
                result = result < subLength ? result : subLength; 
                i++;
                j=i;
                sum=0;
            }
            else{
                sum=sum+nums[j];
                
                if(j==nums.size()-1){
                    i++;
                    j=i-1;
                    sum=0;
                    
                }
                j++;
                
            }
        }
       return result == INT32_MAX ? 0 : result;
    }
};

滑动窗口法

  • 所谓滑动窗口,就是不断的调节子序列的起始位置和终止位置,从而得出我们要想的结果

  • 只用一个for循环,那么这个循环的索引,一定是表示滑动窗口的终止位置。

  • 窗口的移动:如果j在移动过程中发现sum>=target,那么首先记录当前窗口长度,然后将窗口缩短(i--),并减去nums[i]的值,减去之后的sum就是当前缩短后的窗口的sum值,并再次判断是否大于target,若大于,则继续记录长度并与前一个长度进行比较,取最小的,然后更新sum值,i++进行移动缩短,若是小于的话,则移动j指针,扩大窗口长度,进行判断,依次类推,直到遍历完成

  • 在本题中实现滑动窗口,主要确定如下三点:

    • 窗口内是什么?

    • 如何移动窗口的起始位置?

    • 如何移动窗口的结束位置?

    窗口就是 满足其和 ≥ s 的长度最小的 连续 子数组。

    窗口的起始位置如何移动:如果当前窗口的值大于等于s了,窗口就要向前移动了(也就是该缩小了)。

    窗口的结束位置如何移动:窗口的结束位置就是遍历数组的指针,也就是for循环里的索引。

  • 可以发现滑动窗口的精妙之处在于根据当前子序列和大小的情况,不断调节子序列的起始位置。从而将O(n^2)暴力解法降为O(n)。

class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        int i = 0, j = 0, sum = 0;// sum:滑动窗口数值之和  i: 滑动窗口起始位置
        int result = INT32_MAX; // 最终的结果,INT32_MAX是最大正整数
        int subLength = 0;     // 滑动窗口的长度
        for (j = 0; j < nums.size(); j++) {
            sum = sum + nums[j];
 // 注意这里使用while,每次更新 i(起始位置),并不断比较子序列是否符合条件
            while (sum >= target) {
                subLength = j - i + 1; // 取子序列的长度
                result = result < subLength ? result : subLength;
                sum = sum - nums[i++];// 这里体现出滑动窗口的精髓之处,不断变更i(子序列的起始位置)
               
            }
        }
// 如果result没有被赋值的话,就返回0,说明没有符合条件的子序列
        return result == INT32_MAX ? 0 : result;
    }
};
  • 时间复杂度:O(n)

    • 不要以为for里放一个while就以为是O(n^2)啊, 主要是看每一个元素被操作的次数,每个元素在滑动窗后进来操作一次,出去操作一次,每个元素都是被操作两次,所以时间复杂度是 2 × n 也就是O(n)。

  • 空间复杂度:O(1)

螺旋矩阵

  • 给定一个正整数 n,生成一个包含 1 到 n^2 所有元素,且元素按顺时针顺序螺旋排列的正方形矩阵。

示例:

输入: 3 输出: [ [ 1, 2, 3 ], [ 8, 9, 4 ], [ 7, 6, 5 ] ]

  • 力扣59题

模拟法

而求解本题依然是要坚持循环不变量原则。

模拟顺时针画矩阵的过程:

  • 填充上行从左到右

  • 填充右列从上到下

  • 填充下行从右到左

  • 填充左列从下到上

由外向内一圈一圈这么画下去。

  • 可以发现这里的边界条件非常多,在一个循环中,如此多的边界条件,如果不按照固定规则来遍历,那就是一进循环深似海,从此offer是路人

  • 这里一圈下来,我们要画每四条边,这四条边怎么画,每画一条边都要坚持一致的左闭右开,或者左开右闭的原则,这样这一圈才能按照统一的规则画下来。

  • 按照左闭右开规则进行模拟:首先给定一系列初始值,然后循环n/2次(理由:依据对称可以发现),先填入第一行的值,但是第一行最后一个元素不填,因为是左闭右开,然后是填入最后一列,同理,最后一列的最后一个元素不填,再然后是最后一行,从右往左进行填写,第一列的最后一个元素不填,最后就是从下往上填写第一列,第一个元素不填,第一圈结束,然后更新startx,starty,填写内层的值,以此类推,如果n是奇数,循环体内不会进行填写,最后进行单独判断填写

  • 所以一定要按照规则进行填入,但是我了解到思路后,还是没能一次性写对!

class Solution {
public:
    vector<vector<int>> generateMatrix(int n) {
        int startx=0,starty=0; //定义每循环一个圈的起始位置
        int i=0,j=0;
        int offset=1;//需要控制每一条边遍历的长度,每次循环右边界收缩一位
        int count=1;//用来给矩阵中每一个空格赋值
        int loop=n/2;   //每个圈循环几次,例如n为奇数3,那么loop = 1 只是循环一圈,矩阵中间的值需要单独处理
        int mid =n/2;// 矩阵中间的位置,例如:n为3, 中间的位置就是(1,1),n为5,中间位置为(2, 2)
        vector<vector<int>> nums(n,vector<int>(n,0));// 使用vector定义一个二维数组
        while(loop--){
            i=startx;  //i控制行,startx就是行的初试值
            j=starty;  //j控制列,starty就是列的初始值
​
            // 下面开始的四个for就是模拟转了一圈
​
            // 模拟填充上行从左到右(左闭右开)
            for(j;j<n-offset;j++){
                nums[i][j]=count++;
            }
            // 模拟填充右列从上到下(左闭右开)
            for(i;i<n-offset;i++){
                nums[i][j]=count++;
            }
            // 模拟填充下行从右到左(左闭右开)
            for(;j>starty;j--){
                nums[i][j]=count++;
            }
             // 模拟填充左列从下到上(左闭右开)
            for(;i>startx;i--){
                nums[i][j]=count++;
            }
        // 第二圈开始的时候,起始位置要各自加1, 例如:第一圈起始位置是(0, 0),第二圈起始位置是(1, 1)
            startx++;
            starty++;
​
         // offset 控制每一圈里每一条边遍历的长度
            offset+=1;
        }
        // 如果n为奇数的话,需要单独给矩阵最中间的位置赋值
        if(n%2!=0){
            nums[mid][mid]=count;//因为count在上述循环中自增过了,所以直接赋值即可
        }
​
        return nums;
    }
};
  • 时间复杂度 O(n^2): 模拟遍历二维矩阵的时间

  • 空间复杂度 O(1)

第二种解法




class Solution {
public:
    vector<vector<int>> generateMatrix(int n) {
       
        vector<vector<int>> nums(n,vector<int>(n,0));// 使用vector定义一个二维数组
        int dx[]={0,1,0,-1},dy[]={1,0,-1,0};
        for(int x =0,y=0,d=0,k=1;k<=n*n;k++){
            nums[x][y]=k;
            int a = x+dx[d],b=y+dy[d];
            if(a<0||a>=n||b<0||b>=n||nums[a][b]){
                d=(d+1)%4;
                a=x+dx[d],b=y+dy[d];
            }
            x=a,y=b;
        }

        return nums;
    }
};

  • 33
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值