第一章 数组part02 977.有序数组的平方 ,209.长度最小的子数组 ,59.螺旋矩阵II

第二天| 第一章 数组part02 977.有序数组的平方 ,209.长度最小的子数组 ,59.螺旋矩阵II

977.有序数组的平方

  • 题目链接:https://leetcode.cn/problems/squares-of-a-sorted-array/

  • 文章讲解:https://programmercarl.com/0977.%E6%9C%89%E5%BA%8F%E6%95%B0%E7%BB%84%E7%9A%84%E5%B9%B3%E6%96%B9.html

  • 题目介绍:给你一个按非递减顺序排序的整数数组nums,返回每个数字的平方组成的新数组,要求也按非递减顺序排序。请你设计时间复杂度为O(n) 的算法解决本问题。

  • 两种解法:

    • 第一种:暴力解法

      • 给数组中的每一个元素平方之后再排序,时间复杂度就取决于排序算法,已知冒泡排序时间复杂度为O(n^2),快速排序时间复杂度O(nlogn)。
      • 以上都不满足题目O(n)的要求。
    • 第二种:双指针法

      • 这种解法的思想是:不管数组的元素是正数还是负数,最大值一定是在数组的两端,因为该数组是一个有序数组。那么就可以给定两个指针,一个指向数组的开端,一个指向数组的结尾。通过比较两端的值的大小,使得两个指针不断向中间靠拢。我们需要一个新数组来接收这些元素,因为题目要求返回新数组是从小到大进行排列的,所以新数组需要从末尾开始赋值。

      • int left = 0;

      • int right = nums.length - 1;

      • int k = nums.length - 1;

      • 代码如下:

        • class Solution {
              public int[] sortedSquares(int[] nums) {
                  int[] result = new int[nums.length];
                  int k = nums.length - 1;
                  for (int left = 0, right = nums.length - 1; left <= right;) {
                      if (nums[right] * nums[right] > nums[left] * nums[left]) {
                          result[k--] = nums[right] * nums[right];
                          right--;
                      } else {
                          result[k--] = nums[left] * nums[left];
                          left++;
                      }
                  }
                  return result;
              }
          }
          
        • 注意:这里循环的判断条件是 left <= right

        • 是因为当两个指针都指向数组中需要判断的最后一个数的时候,才能确定这个数最终放在数组的什么位置,所以判断条件是left <= right。

209.长度最小的子数组

  • 题目链接:https://leetcode.cn/problems/minimum-size-subarray-sum/

  • 文章讲解:https://programmercarl.com/0209.%E9%95%BF%E5%BA%A6%E6%9C%80%E5%B0%8F%E7%9A%84%E5%AD%90%E6%95%B0%E7%BB%84.html

  • 题目介绍:给定一个含有n个正整数的数组和一个正整数target。找出该数组中满足其和≥ target的长度最小的连续子数组[numsl, numsl+1, …, numsr-1, numsr],并返回其长度。如果不存在符合条件的子数组,返回0 。

  • 解法:

    • 第一种:暴力解法

      • 思路:

        • 首先定义一个返回值result = Integer.MAX_VALUE;
        • 使用两层for循环,外层循环控制起始位置,内层循环控制终止位置。
        • 内层循环的判断条件是如果找到了一个集合,这个集合的总和>=s,那么就终止内层循环,因为在起始位置确定的情况下,第一次找到的从起始位置到终止位置,这个一定是最短的。
      • 代码:(LeetCode无法提交,超出时间限制)

        • class Solution {
              public int minSubArrayLen(int target, int[] nums) {
                  int result = Integer.MAX_VALUE;
                  int subLength = 0;
                  int sum = 0;
                  for (int i = 0; i < nums.length; i++) {
                      sum = 0;
                      for (int j = i; j < nums.length; j++) {
                          sum += nums[j];
                          if (sum >= target) {
                              subLength = j - i + 1;
                              result = result < subLength ? result : subLength;
                              break;
                          }
                      }
                  }
                  return result == Integer.MAX_VALUE ? 0 : result;
              }
          }
          
        • 两个需要注意的地方:

          • 在执行完内层循环之后,要把sum重新赋值为0;
          • 内层循环的判断条件,只要是找到第一次满足条件的终止位置就break,因为这个就是在外层循环i确定的情况下,(即初始位置确定的情况下)的最小子数组长度。
    • 第二种:滑动窗口

      • 思路:滑动窗口是先确定终止索引的位置,再通过++让起始索引逼近终止索引,以寻找最小值。

      • 代码:

        • class Solution {
              public int minSubArrayLen(int target, int[] nums) {
                  int result = Integer.MAX_VALUE;
                  int subLength = 0;
                  int sum = 0;
                  int i = 0;
                  for (int j = 0; j < nums.length; j++) {
                      sum += nums[j];
                      while (sum >= target) {
                          subLength = j - i + 1;
                          sum -= nums[i];
                          i++;
                          result = result < subLength ? result : subLength;
                      }
                  }
                  return result == Integer.MAX_VALUE ? 0 : result;
              }
          }
          
      • 注意:

        • 和暴力解法不一样的是,这里的条件采用了while循环,意思是只要有满足的条件的情况下,一直会去找更小的值,直到条件不满足(即sum < target)
  • 总结:

    • 暴力解法是一个for循环滑动窗口的起始位置,一个for循环为滑动窗口的终止位置,用两个for循环完成了一个不断搜索区间的过程,这就相当于把整个数组中满足小于目标值的全部子数组进行了遍历、枚举。
    • 滑动窗口要求你想明白是起始索引还是终止索引需要先遍历,如果先遍历的是起始索引,那么就和暴力解法是一样的;如果先遍历的是终止索引,那么确定下终止索引的位置,就可以通过++的方式调整起始索引,找到最小值。
      • 在本题中实现滑动窗口,主要确定如下三点:
        • 窗口内是什么?
        • 如何移动窗口的起始位置?
        • 如何移动窗口的结束位置?
      • 窗口就是 满足其和 ≥ s 的长度最小的 连续 子数组。
      • 窗口的起始位置如何移动:如果当前窗口的值大于s了,窗口就要向前移动了(也就是该缩小了).
      • 窗口的结束位置如何移动:窗口的结束位置就是遍历数组的指针,也就是for循环里的索引。

59.螺旋矩阵II

  • 题目链接:https://leetcode.cn/problems/spiral-matrix-ii/

  • 文章讲解:https://programmercarl.com/0059.%E8%9E%BA%E6%97%8B%E7%9F%A9%E9%98%B5II.html

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

  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tW9TQM7H-1691675735585)(image-20230810203332228.png)]

  • 解法:

    • class Solution {
          public int[][] generateMatrix(int n) {
              int loop = 1;
              int startX = 0;
              int startY = 0;
              int offest = 1;
              int count = 1;
              int i;
              int j;
              int[][] res = new int[n][n];
              while(loop <= n / 2) {
                  for(j = startY; j < n - offest; j++) {
                      res[startX][j] = count++;
                  }
                  for(i = startX; i < n - offest; i++) {
                      res[i][j] = count++;
                  }
                  for(;j > 0; j--) {
                      res[i][j] = count++;
                  }
                  for(;i > 0; i--) {
                      res[i][j] = count++;
                  }
                  startX++;
                  startY++;
                  offest++;
                  loop++;
              }
              if (n % 2 != 0) {
                  res[startX][startY] = count;
              }
              return res;
          }
      }
      
  • 解这道题的关键是循环不变量原则。(和昨天做的二分法是一样的)

    • 我们只要把边界在每次循环的处理中保持一致就能迎刃而解。
    • 模拟顺时针画矩阵的过程:
      • 填充上行从左到右
      • 填充右列从上到下
      • 填充下行从右到左
      • 填充左列从下到上
    • 在这道题中,采用左闭右开的方式处理,如下图所示:
      • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-m2jurOqa-1691675735586)(image-20230810213311256.png)]
  • 总结本题需要注意的一些关键点:

    • (1)首先要确定转几圈可以把所有的空填满?这里画个图就明白了,最多转n/2圈,所以我们需要定义一个圈数变量

      • int loop = 0
      • while (loop < n/2)
      • n/2涉及到了n是奇数还是偶数
        • 如果(n%2 != 0)的话,意味着最后一个空需要最后赋值处理。
    • (2)我们需要定义一个初始不变的坐标(startX, startY),以及数组的横纵坐标(i, j),这个坐标是随着转圈而变化的。

      • int startX = 0;
      • int startY = 0;
      • int i = 0;
      • int j = 0;
    • (3)接着就写四个循环分别模拟四条边

      • 在第一个循环中(上行从左到右),i是不变的,一直等于startx,j会一直++,终止条件是j < n - 1。这里我们思考一下,会发现再转第二圈的时候,终止条件会变为n - 2,所以这个也是一个变量,需要控制每一条边遍历的长度,每次循环右边界收缩一位。

        • for(j = startY; j < n - offest; j++) {
              res[startX][j] = count++;
          }
          
        • int offest = 1;

        • 循环里面要给数组赋值,所以需要定义一个初始值count

          • int count = 1;
      • 在第二个循环中(右行从上到下),j是不变的,经过第一次循环之后变为n。变的是i,i要从startX一直++到n-offest。

        • for(i = startX; i < n - offest; i++) {
              res[i][j] = count++;
          }
          
      • 在第三个循环中(下行从右到左),i和j已经加到了最大值。在下行中,i是不变的,只要将j–后大于0即可,因为这里j已经有了初始值,所以不需要赋值。

        • for(;j > 0; j--) {
              res[i][j] = count++;
          }
          
      • 在第四个循环中(左行从下到上),j是不变的,并且经过上个循环之后,变为了0,因此只需要将i–后大于0即可。

        • for(;i > 0; i--) {
              res[i][j] = count++;
          }
          
    • (4)完成上面四个循环之后,初始位置要向右斜下方转移,上行结束的位置要向左斜下方转移,同理其他两行也是这样,如图所示。即整体都需要往里收缩,所以:

      • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fGNeBs7x-1691675735587)(image-20230810215337551.png)]
        • startX++;
        • startY++;
        • offest++;
        • loop++;

你可能感兴趣的:(leetcode,算法)