第二天| 第一章 数组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++;
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fGNeBs7x-1691675735587)(image-20230810215337551.png)]
-