代码随想录算法训练营31期第二天|**977.有序数组的平方 **209.长度最小的子数组 59.螺旋矩阵II
今天的题目难度有所增加 只有有序数组的平方一题自己独立做了出来 其余都看了视频 需要多看多新
有序数组的平方
思路
这个还是不难的 关键还是要能想到最大的值无非就是出现在数组的两个边界 要不就是负数很负 要不就是正数很正 不难想到要用双指针的方法 一个指向左边界 一个指向右边界 然后比较二者平方的大小 将大的留在新数组的最右侧(因为题目要求以递增的顺序)然后将大的一侧的边界挪动一位 继续比较
代码
class Solution {
public int[] sortedSquares(int[] nums) {
int []result = new int[nums.length];
int left =0;
int right = nums.length -1;
int size = nums.length-1; //给result数组用的 从大到小
while(left<=right){//left是可以等于right的 指向同一个元素
int leftSquare = nums[left]*nums[left];
int rightSquare = nums[right]*nums[right];
if(leftSquare<rightSquare){
result[size] = rightSquare;
right--;
size--;
}
else{
result[size] = leftSquare;
left++;
size--;
}
}
return result;
}
}
时间复杂度O(n) 空间复杂度O(n)
长度最小的子数组
思路
这道题用到的一个关键是滑动窗口 其实滑动窗口的实际实现还是双指针 只不过不像原先我们用的是双指针指的元素 滑动窗口利用的是二者指针之间的值 关键还是在于我们要明确什么时候双指针要移动 该怎么移动
此题用的是同向的双指针 将末尾指针每次移动一个单位 直到窗口中的和达到了target 可以这么理解:初始你有0个苹果 一个机器随机的送出苹果 而你的目标是达到10个苹果 经过了几轮后 你达到了这个目标 但是有可能最后一次给你了100个苹果 那么前几轮对你来说都是没用的 因此我们需要一轮轮的丢弃前几轮的苹果 看至少需要几轮能达到目标
代码
class Solution {
// 滑动窗口
public int minSubArrayLen(int target, int[] nums) {
int left = 0;
int sum = 0;
int result = Integer.MAX_VALUE;
for (int right = 0; right < nums.length; right++) {
sum += nums[right];
while (sum >= target) {//不断相加达到target
result = Math.min(result, right - left + 1); //二者之间取最小值
sum -= nums[left];//每次丢弃一轮的🌿
left++;
}
}
return result == Integer.MAX_VALUE ? 0 : result; //如果一直没有达到target 则返回0
}
}
这里为什么要用while:就例如我上面的例子 我们需要一轮轮的丢弃 如果只是用了一个if 那么我们只丢弃了一轮 找不到最小的子数组(例如 1,1,1,1,100 target=100)
时间复杂度O(n) 空间复杂度O(1)
Tips
这道题有使用条件就是数组中不能有负值 如果出现负值 我们窗口的值就不能保证简单的缩小即减小 扩张即扩大
螺旋矩阵
这道题相对来说复杂不少 需要明确边界的判断 也需要明确每一次loop哪些发生了变化
思路
求解本题依然是要坚持循环不变量原则 每一条边坚持一个区间 不要一会左闭右开 一会左闭右闭 这样边界一会就搞晕了
模拟顺时针画矩阵的过程:
- 填充上行从左到右
- 填充右列从上到下
- 填充下行从右到左
- 填充左列从下到上
由外向内一圈一圈这么画下去。
我的想法是对于一个n=4的二维数组 遍历一个loop后相当于我们还剩一个n=2的二维数组需要再走一圈(这也是为什么loop=n/2) 而对于n为奇数的情况呢 我们最终都会剩下一个最中心的块 单独判断一下即可
同时我们可以设置每一个loop的起点 比如第一个loop的起点是(0,0)第二次就变成(1,1)第三次变成(2,2)以此类推
代码
class Solution {
public int[][] generateMatrix(int n) {
int [][]result = new int[n][n];
int value=1;
int start= 0; //起点的位置 每次转一圈之后起点都会变化 例如(0,0)->(1,1)
int offset=1; //每次移动的距离 初始是1
int loop=0; //代表转了几圈
int i,j;
while(loop<n/2){
for(j =start;j<n-offset;j++){
result[start][j]=value;
value++;
}
for (i =start;i<n-offset;i++){
result[i][j] = value;
value++;
}
for (;j>start;j--){
result[i][j]=value;
value++;
}
for (;i>start;i--){
result[i][j]=value;
value++;
}
start++;
offset++;
loop++;
}
if(n % 2 != 0){//判断n为奇数的情况
result[n/2][n/2]=n*n;
}
return result;
}
}
四个循环代表四条边 代码中其实有不少小细节:
- 对于一条边 由于我采用的是左闭右开的区间 这一条边的边界点我是不处理的 交给下一个循环 而同时我们的循环参数就正好移动到了这个边界的一个横坐标/纵坐标 供我们下一个循环使用 因为下一个循环总有一个值是保持不变的
- 有关于这里的offset :因为是左闭右开 右边取不到 所以我们不能简单的i<n 而是要减去一个偏移量 第一次loop减1即可 第二次呢?可以想象这次我们相当于最右边又被剔除掉一个元素(因为已经赋值过了) 那第二次就该减去2了 相当于往里面缩了一格
- 后两个循环采用的是i>start 这是因为我们后两条边正好要到的位置就是起点的横坐标/纵坐标 并且同样适用于当我们的起点变化
总结
图片取自代码随想录