代码随想录算法训练营第二天|977.有序数组的平方 ,209.长度最小的子数组 ,59.螺旋矩阵II

本文详细介绍了如何使用双指针法解决有序数组平方、长度最小的子数组以及螺旋矩阵II这三个算法问题。针对每个问题,首先给出了直观但效率较低的解决方案,然后分析了错误思路,最后提出了优化后的O(n)时间复杂度算法,通过滑动窗口和循环不变量原则实现高效求解。这些方法对于理解和提升算法能力非常有帮助。
摘要由CSDN通过智能技术生成

今日学习的文章和视频链接

977.有序数组的平方文章链接: [link]
977.有序数组的平方视频讲解链接: [link]
209.长度最小的子数组文章链接: [link]
209.长度最小的子数组视频讲解链接: [link]
59.螺旋矩阵II文章链接: [link]
59.螺旋矩阵II文章链接: [link]

977.有序数组的平方

给你一个按 非递减顺序 排序的整数数组 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]

看到题目第一想法

最直观的想法,每个数平方之后,排个序。

class Solution {
    public int[] sortedSquares(int[] nums) {
    int n=nums.length-1;
    int temp;
    int[] result=new int[nums.length];
    for (int i=0;i<n+1;i++){
        result[i]=( nums[i]* nums[i]);
    }        
    for(int slow=0;slow<nums.length;slow++){
        for(int fast=slow ;fast<nums.length;fast++){
            if (result[fast]<result[slow]){
                temp=result[slow];
                result[slow]=result[fast];
                result[fast]=temp;
            }
        }
    }
    return result;}
}

学习之后

发现数组其实是有序的, 只不过负数平方之后可能成为最大数了。
那么数组平方的最大值就在数组的两端,不是最左边就是最右边,不可能是中间。即数组整体形势:大小大。
此时可以考虑双指针法了,i指向起始位置,j指向终止位置。
定义一个新数组result,和A数组一样的大小,让k指向result数组终止位置,倒着(由大到小)放入结果result数组当中。
循环用 while比for好,因为两个指针自增(i指向的元素平方更大,i++)或自减(j指向的元素平方更大,j–)分情况讨论,用for循环不便于书写。
如果A[i] * A[i] < A[j] * A[j] 那么result[k–] = A[j] * A[j]; 。
如果A[i] * A[i] >= A[j] * A[j] 那么result[k–] = A[i] * A[i]; 。
如动画所示:

在这里插入图片描述
此时的时间复杂度为O(n)。

class Solution {
    public int[] sortedSquares(int[] nums) {
    int m=nums.length-1;
    int[] result=new int[nums.length];
    int left=0;
    int right= m;
    while (left<=right){//应该取等否则丢失一个数据
        if ((nums[left]*nums[left])>(nums[right]*nums[right])){
            result[m]=(nums[left]*nums[left]);//倒序录入
            m--;
            left++;
        }else{
            result[m]=(nums[right]*nums[right]);
            m--;
            right--;
        }
    }
    return result;}
}

209.长度最小的子数组

给定一个含有 n 个正整数的数组和一个正整数 s ,
找出该数组中满足其和 ≥ s 的长度最小的连续子数组,并返回其长度。
如果不存在符合条件的子数组,返回 0。
示例:
输入:s = 7, nums = [2,3,1,2,4,3]
输出:2 
解释:子数组 [4,3] 是该条件下的长度最小的子数组。

看到题目第一想法

一开始理解错了 连续子数组是说以原数组顺序的子数组,我理解成数字来源于原数组但是数字大小连续。纠正错误后一开始的想法就是暴力解法: 两个for循环,然后不断的寻找符合条件的子序列,时间复杂度很明显是O(n^2)。


class Solution {
    public int minSubArrayLen(int target, int[] nums) {
      int sum=0;
      int sumlengthfinal=0;//最终长度,分别找每个位置连续子数组,后筛选最小
      int sumlength=0;
      for (int i=0;i<nums.length;i++){
          for(int j=i;j<nums.length;j++){
              sum=sum+nums[j];
              sumlength+=1;
              if(sum>=target){
                  if((sumlengthfinal==0)||(sumlength<sumlengthfinal)){
                      sumlengthfinal=sumlength;
                    break;}
                }
            }
        sum=0;
        sumlength=0;
        }
        return sumlengthfinal;}
    }

• 时间复杂度:O(n^2)
• 空间复杂度:O(1)
结果暴力解法超时了。

学习过后

滑动窗口

滑动窗口,本质是双指针,就是不断的调节子序列的起始位置和终止位置,从而得出我们要想的结果。
在暴力解法中,是一个for循环滑动窗口的起始位置,一个for循环为滑动窗口的终止位置,用两个for循环完成了一个不断搜索区间的过程。
只用一个for循环,那么这个循环的索引,一定是表示滑动窗口的终止位置,否则依旧是暴力解法了。
那么问题来了, 滑动窗口的起始位置如何移动呢?
这里还是以题目中的示例来举例,s=7, 数组是 2,3,1,2,4,3,来看一下查找的过程:

在这里插入图片描述
窗口就是 满足其和 ≥ s 的长度最小的 连续子数组。
窗口的起始位置如何移动:如果当前窗口的值大于s了,窗口就要向前移动了(也就是该缩小了)。
窗口的结束位置如何移动:窗口的结束位置就是遍历数组的指针,也就是for循环里的索引。
解题的关键在于 窗口的起始位置如何移动,如图所示:
在这里插入图片描述
可以发现滑动窗口的精妙之处在于根据当前子序列和大小的情况,不断调节子序列的起始位置,即学会如何收缩窗口。从而将O(n^2)暴力解法降为O(n)。

 //伪代码
    i=0//起始位置  j是终止位置
    result=max;//只有取最大值后面才能取min比较
    for(j=0;j<nums.length;j++){
    sum+=nums[j];
    while(sum>=target){//用while不用if 因为比如1,1,1,1,1,100,... target=100;应持续移动起点i,终点j不动,
    //while连续移动,if移动一次
        sumlength=j-i+1;
        result=min(result,sumlength);
        sum=sum-nums[i];
        i++
    }
    }
    return (result==max)?0:result;

class Solution {
    public int minSubArrayLen(int target, int[] nums) {
     int finallength=nums.length+1;
     int i=0;//起点
     int sum=0;
     int length;
     for (int j=0;j<nums.length;j++){
         sum+=nums[j];
         while(sum>=target){
             length=j-i+1;
             finallength=Math.min(length,finallength);
             sum-=nums[i++];
         }
     }
    return (finallength==nums.length+1)?0:finallength;}
    }

• 时间复杂度:O(n)
• 空间复杂度:O(1)
一开始错误地认为时间复杂度为 O(n^2)
,以为for里放一个while就以为是O(n^2)啊, 其实主要是看每一个元素被操作的次数,每个元素在滑动窗后进来操作一次,出去操作一次,每个元素都是被操作两次,所以时间复杂度是 2 × n 也就是O(n)。
补充知识:
在这里插入图片描述

59.螺旋矩阵II

给定一个正整数 n,生成一个包含 1 到 n^2 所有元素,	且元素按顺时针顺序螺旋排列的
正方形矩阵。
示例:
输入: 3 输出: [ [ 1, 2, 3 ], [ 8, 9, 4 ], [ 7, 6, 5 ] ]

在这里插入图片描述

看到题目第一想法

无非找到四个角的临近条件以四个边为单位分四种情况,触碰临界条件,改变相应行列上下界,限定其变化范围。

错误方法

//边界条件分析错误
class Solution {
    public int[][] generateMatrix(int n) {
        int[][] result= new int[n][n];
        int i=0;//行
        int j=0;//列
        int m=1;//放第几个数
        int imax=n-1;
        int imin=0;
        int jmax=n-1;
        int jmin=0;
        while(m<=n){//左闭右开
             result[i][j]=m;
             m++;

             if ((i==imin)&&(j==jmin)){
                 j++;
                 if(m!=1){
                 imax-=1;}}
             if ((i==imin)&&(j<jmax)){
                 j++;}
             if ((i==imin)&&(j==jmax)){
                 i++;
                 if(m!=n){
                 jmin+=1;}}

            if ((i<imax)&&(j==jmax)){
                 i++;}
            if ((i==imax)&&(j==jmax)){
                 j--;
                 imin+=1;}
            if ((i==imax)&&(j>jmin)){
                 j--;}
            if ((i==imax)&&(j==jmin)){
                 i--;
                 jmax-=1;}
            if ((i>imin)&&(j==jmin)){
                 i--;}                           
        }
    return result;}
}
}

本题依然是要坚持循环不变量原则,其难点主要在于起始位置的判断固定与循环不变量–区间的划分。
模拟顺时针画矩阵的过程:
• 填充上行从左到右
• 填充右列从上到下
• 填充下行从右到左
• 填充左列从下到上
由外向内一圈一圈这么画下去。
可以发现这里的边界条件非常多,在一个循环中,如此多的边界条件,必须按照固定规则来遍历。
这里一圈下来,我们要画每四条边,这四条边怎么画,每画一条边都要坚持一致的左闭右开,或者左开右闭的原则,这样这一圈才能按照统一的规则画下来。
本题采用左闭右开。
在这里插入图片描述
然后是循环圈数的问题,假设给定数字为n,那么nn的矩阵需要转n/2圈。
例如当n=3需要3/2=1,没整除是因为是奇数,落单的中间块需要手动赋值,其地址为[1,1]=3
3
当n=5需要5/2=2圈,中间为[2,2]=55
当n=6需要6/2=3圈。
因此对于奇数偶数都有n/2圈,奇数额外手动赋值[n/2,n/2]=n
n。

class Solution {
    public int[][] generateMatrix(int n) {
        int startx=0;
        int starty=0;
        int offset=1;//偏移量 每转一圈偏移+1 保证左闭右开
        int m=1;
        int[][] nums=new int[n][n];
        int loop=n/2;//圈数
        int i,j;
        while(loop>0){
            for(j=starty;j<n-offset;j++){
                nums[startx][j]=m++;
            }
            for(i=startx;i<n-offset;i++){//上一个循环结束j=n-offset 以4为例 地址为0~3 4-1正好为3最后一列
                nums[i][j]=m++;
            }
            for(;j>starty;j--){//可以不初始化
                nums[i][j]=m++;
            }
            for(;i>startx;i--){
                nums[i][j]=m++;
            }
            startx++;
            starty++;
            offset++;
            loop--;
        }
        if (n%2==1){//奇数手动赋值中间最后一个
            nums[n/2][n/2]=n*n;
        }
    return nums;}
       
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值