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

一.题目链接–977.有序数组的平方

在这里插入图片描述

力扣题目链接:977.有序数组的平方

代码随想录链接: 977.有序数组的平方

二.解题思路

非递减顺序,即分为非递减顺序两方面,非递减就是可能逐渐增加或者不变,甚至一增一减这种无序;顺序则代表前面的要么增加,要么不变。根据样例可看出左边可以是负数或者零,并且左边一个数永远比它的右边的数字小。

2.1暴力解法

只需要在原数组上令每个数组的元素都自己平方再赋值给自己,然后使用排序函数即可实现。

2.2双指针解法

重点是如果一个数列是非递减顺序,原数组中的元素平方最大值一定产生在原数组的最左边或者最右边
那么我们可以通过进行所有数组优先比较最左边和最右边的值,然后先把大的放进新的数组中,再逐个比较。

三.遇到问题和注意事项

写双指针算法时候,忘记了创建新数组的新指针,不能用来定位新数组的位置。

四.实现代码

4.1 暴力解法

// 方法一:暴力解法
    public int[] sortedSquares(int[] nums) {
        int len = nums.length;
        for(int i=0; i<len; i++){
        	//每个元素平方再赋值给自己
            nums[i] = nums[i] * nums[i];
        }
        Arrays.sort(nums);
        return nums;
    }

4.2双指针法

    //方法二:双指针法
    public int[] sortedSquares(int[] nums) {
        int left = 0;
        int right = nums.length - 1;
        //定义一个新数组用来存放,注意大小一致的
        int[] result = new int[nums.length];
        //定义新数组的坐标,用来定位存放的位置
        int write = nums.length - 1;
        while(left <= right){
            if(nums[left] * nums[left] <= nums[right] * nums[right]){
                //下面代码也可以简略写成result[write--] = nums[right] * nums[right];  right--;
                //若最右边值比最左边值大,则把最右边赋值给新数组最右边,然后注意移动指标
                result[write] = nums[right] * nums[right];
                right--;
                write--;
            }else if(nums[left] * nums[left] > nums[right] * nums[right]){
                 //下面代码也可以简略写成 result[write--] = nums[left] * nums[left];  left++;
                result[write] = nums[left] * nums[left];
                left++;
                write--;
            }
        }
    return result;
    }

五.随想录知识点

一.题目链接-- 209.长度最小的子数组

在这里插入图片描述
力扣题目链接:209.长度最小的子数组

代码随想录链接:209.长度最小的子数组

二.解题思路

这道题目暴力解法当然是 两个for循环,然后不断的寻找符合条件的子序列,时间复杂度很明显是O(n^2)。 但题目要求是O(n),所以不行。

想到数组问题可以用双指针,这里特殊的双指针,叫做滑动窗口
在这里插入图片描述
本题中实现滑动窗口,主要确定如下三点:
1.窗口内是什么?
2.如何移动窗口的起始位置?
3.如何移动窗口的结束位置?

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

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

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

解题的关键在于 窗口的起始位置如何移动,这个类似于队列,先判断此时队列总和是否超出target.如果超出,就依次减掉最开始的那个,然后循坏判断,知道不超出,再进行窗口终点的移动。

下面是别人的图示:
我们把数组中的元素不停的入队,直到总和大于等于 s 为止,接着记录下队列中元素的个数,然后再不停的出队,直到队列中元素的和小于 s 为止(如果不小于 s,也要记录下队列中元素的个数,这个个数其实就是不小于 s 的连续子数组长度,我们要记录最小的即可)。接着再把数组中的元素添加到队列中……重复上面的操作,直到数组中的元素全部使用完为止。 这里以 [2,3,1,2,4,3] 举例画个图来看下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述在这里插入图片描述

三.遇到问题和注意事项

3.1 Integer.MAX_VALUE 的含义

在这里插入图片描述

3.2 Java中length、length()、size()区别

(博客链接)
1.*带括号()*的均是java中的方法method,length()就是String类的一个method所以有括号

2.不带括号的是类的属性field,比方说penson.name,length就是数组类的一个属性所以不需要括号

3.size()是针对泛型集合LIst说的,因为数组是静态的在初始化的时候JVM就会为起分配相应的内存大小,但是List是动态的,其没有length的属性。所以java专门提供了size()方法用来计算集合的长度大小。为了以示区别所以叫做size()!(个人猜测,哈哈不对当我没说。)
如果看到泛型或者集合有多少个元素,记得调用size()方法来查看!

3.3 三元运算符

三元运算符的格式
数据类型 变量名称 = 条件判断 ? 表达式A : 表达式B;
例:a = a< b? a : b;
当a<b则 a= a; 否则 a= b

四.实现代码

// 双指针之滑动窗口(类似队列相加)
public int minSubArrayLen(int target, int[] nums) {
   int res = Integer.MAX_VALUE;
   //滑动窗口的左边界起始位置
   int left = 0;
   //滑动窗口内的和
   int sum = 0;
   //滑动窗口子序列的长度(需要比较的结果)(如果想类似双指针的话也可以不定义,在下面第19行直接使用结果)
   int winLen = 0;
   //数组用nums.length  集合和泛型用nums.size();
   int len = nums.length;
   for(int right = 0; right < len; right++){
       sum += nums[right];
       //用while是因为它是一个一直在变的,所以不用if只判断一次
       while(sum >= target){
           //滑动窗口子序列的长度  用右边界-左边界还要加1,因为指针从零开始需要加一表示真实长度
           winLen = right - left + 1;
           //不定义窗口长度也可以用函数res = Math.min(result, right - left + 1);
           res = res < winLen ? res : winLen;
           sum -= nums[left++]; // 这里体现出滑动窗口的精髓之处,不断变更i(子序列的起始位置)
       }
   }
   //最后需要注意是否整个数组加起来都小于target
   return res == Integer.MAX_VALUE ? 0 : res;
}


五.随想录知识点

● 滑动窗口:本质是满足了单调性,即左右指针只会往一个方向走且不会回头。收缩的本质即去掉不再需要的元素。也就是做题我们可以先固定移动右指针,判断条件是否可以收缩左指针算范围。大家可以好好理解一下。
● 加入滑动窗口中有负数怎么办?
如果有负数的话感觉也不能用滑动窗口了,因为有负数的话无论你收缩还是扩张窗口,你里面的值的总和都可能增加或减少,就不像之前收缩一定变小,扩张一定变大,一切就变得不可控了。如果要 cover 所有的情况,那每次 left 都要缩到 right,那就退化为暴力了哈哈。
● 在滑动窗口类型题目里有没有去DEBUG的什么小技巧呢?
一般是怀疑哪里有问题就打印哪里 像今天的滑动窗口 就可以把窗口首尾的下标变化过程打印出来 能很清楚的看到窗口是怎样移动的
● 双指针和滑动窗口有什么区别,感觉双指针也是不断缩小的窗口。这道题,我想用两头取值的双指针,结果错了?
因为两头指针走完相当于最多只把整个数组遍历一遍,会漏掉很多情况。滑动窗口实际上是双层遍历的优化版本,而双指针其实只有一层遍历,只不过是从头尾开始遍历的。
滑动窗口的原理是右边先开始走,然后直到窗口内值的总和大于target,此时就开始缩圈,缩圈是为了找到最小值,只要此时总和还大于target,我就一直缩小,缩小到小于target为止在这过程中不断更新最小的长度值,然后右边继续走,如此反复,直到右边碰到边界。这样就保证了可以考虑到最小的情况

一.题目链接–59.螺旋矩阵II

在这里插入图片描述

二.解题思路

2.1挨个填充 不需要考虑中间值(推荐)

在这里插入图片描述
这种方式不需要考虑对于当n为奇数的时候 还需要再填充上中心点的值

2.2代码随想录解法 左开右闭 (没仔细看这种 更喜欢上面那个)

在这里插入图片描述

三.遇到问题和注意事项

四.实现代码

4.1挨个填充不需要考虑中间值(推荐)

class Solution {
    public int[][] generateMatrix(int n) {
      int left = 0, right = n-1, top = 0, bottom = n-1;
      int[][] res = new int[n][n];
      int count = 1, target = n*n;

      //for循环中变量定义成i或j的细节:按照通常的思维,i代表行,j代表列
      //这样,就可以很容易区分出来变化的量应该放在[][]的第一个还是第二个
      //对于变量的边界怎么定义:
            //从左向右填充:填充的列肯定在[left,right]区间
            //从上向下填充:填充的行肯定在[top,bottom]区间
            //从右向左填充:填充的列肯定在[right,left]区间
            //从下向上填充:填充的行肯定在[bootom,top]区间
      while(count <= target){
        //从左向右填充 相当于缩小上边界
        for(int j = left; j <= right; j++){
          res[top][j] = count++;
        }
        top++;

        //从上到下填充 相当于缩小右边界
        for(int i = top; i <= bottom; i++){
          res[i][right] = count++;
        }
        right--;

        //从右到左填充 相当于缩小下边界
        for(int j = right; j >= left; j--){
          res[bottom][j] = count++;
        }
        bottom--;

        //从下到上填充 相当于缩小左边界
        for(int i = bottom; i >= top; i--){
          res[i][left] = count++;
        }
        left++;
      }
      return res;
    }
}

4.2 代码随想录解法 左开右闭

class Solution {
    public int[][] generateMatrix(int n) {
        int loop = 0;  // 控制循环次数
        int[][] res = new int[n][n];
        int start = 0;  // 每次循环的开始点(start, start)
        int count = 1;  // 定义填充数字
        int i, j;

        while (loop++ < n / 2) { // 判断边界后,loop从1开始
            // 模拟上侧从左到右
            for (j = start; j < n - loop; j++) {
                res[start][j] = count++;
            }

            // 模拟右侧从上到下
            for (i = start; i < n - loop; i++) {
                res[i][j] = count++;
            }

            // 模拟下侧从右到左
            for (; j >= loop; j--) {
                res[i][j] = count++;
            }

            // 模拟左侧从下到上
            for (; i >= loop; i--) {
                res[i][j] = count++;
            }
            start++;
        }

        if (n % 2 == 1) {
            res[start][start] = count;
        }

        return res;
    }
}

五.随想录知识点

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值