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

今日学习时长:3h

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

视频讲解: https://www.bilibili.com/video/BV1QB4y1D7ep

看到这个题,暴力法很好想,直接平方完排序就行。观察平方后的数组,想到了用双指针,但是还是想着27.移除元素里,在一个数组里完成操作。其实可以新开一个数组,直接把比较得到的值放到新数组里。

思路讲解

观察数组,两边大中间小。考虑用双指针(这种从两边到中间汇合的题目 双指针都挺好用的?)i从左边开始,j从右边开始。比较i和j位置上大的数放到结果数组里。

  • while条件是(i<=j),如果没有=,一共只会赋值len-1个,还有一个位置没有赋值。

如果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];

这里最好在比较的时候才平方,不要先遍历一遍数组变成平方的,这样会快一点。

在这里插入图片描述

class Solution {
    public int[] sortedSquares(int[] nums) {
        int len = nums.length;
        for(int i = 0; i < len; i ++) {
            nums[i] = nums[i] * nums[i];
        }
        int[] res = new int[len];
        int i = 0,j = len - 1;
        int k = len - 1;
        while(i <= j) {
            if(nums[i] > nums[j]) {
                res[k--] = nums[i++];
            } else if (nums[i] <= nums[j]) {
                res[k--] = nums[j--];
            }
        }
        return res;
    }
}

感想

这题比较简单,记得分析数组元素的特征,如果有两边到中间的趋势,考虑用双指针。Java中平方不能用^!!!

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

视频讲解:https://www.bilibili.com/video/BV1tZ4y1q7XE

第一思路是暴力,遍历数组,把每个位置当做起始位置往后累加找子数组,并记录下当前最小长度len。

思路讲解 滑动窗口

两层for暴力,枚举出所有子数组长度,时间复杂度O(n^2)。(超时了)

滑动窗口的方式 相当于用一层for做了两层for的事情。

在这里插入图片描述

  • 关键点:for循环里的j 代表的是滑动窗口的起始位置还是终止位置?

先假设j代表的是起始位置。依然要让终止位置往后遍历,计算s,和两层for没什么区别。

  • 所以for循环里的j 代表的一定是终止位置!!!。起始位置需要用动态移动的策略。

滑动窗口最精髓的思路在于如何调整起始位置!

滑动窗口里元素的和sum,移动终止位置的同时累加sum,sum += nums[j]

  • 如何动态调整起始位置?当终止位置移动到 sum ≥ s了之后,再去移动起始位置。同时记录下滑动窗口长度subL。

持续更新最小值result = min(result, subL)。(result初始为最大值即整个数组的长)

然后sum -= nums[i]; i++; ,移动起始位置,同时更新sum

  • ___ (sum >= target)这里循环条件应该用if还是while?

如果写if的话,可能有这种情况:j移动到sum>=s停住,此时进入if,**if只会移动一次i,j就又开始移动了!**实际上i需要连续移动来更新滑动窗口大小,因此得用while。让起始位置连续的向后移动直到sum >= target

  • 最后返回结果时,注意要考虑整个数组的和还是比target小的情况。

解决方法:

  • if(i == 0) return 0; 如果起始位置一直没有动过,就说明一次while都没进入过,i也就为0。

注意,if(res == len) return 0;不行 可能有这种情况:target = 15 nums=[1,2,3,4,5]

而如果用上面的逻辑,有进过一次while,i更新了。

  • 或者res初始赋值Integer.INT_MAXVALUE,如果到最后都没变过,说明不满足return result == Integer.MAX_VALUE ? 0 : result;

完整代码:

class Solution {
    public int minSubArrayLen(int target, int[] nums) {
        int res = Integer.MAX_VALUE;  //返回值,子数组长度初始化为最大值
        int i = 0,sum = 0,subL = 0;  //i为起始位置
        for(int j = 0; j < nums.length; j++) {   //j为终止位置
            sum = sum + nums[j]; //累加sum
            while(sum >= target) {
                subL = j - i + 1;
                res = Math.min(subL, res);
                sum = sum - nums[i++];
            }
        }
        return res == Integer.MAX_VALUE ? 0 : res;
    }
}

总结

计算数组长度要用j-i+1

滑动窗口精髓在于遍历的是终止位置,动态移动起始位置。同时更新子数组长度和结果

动态更新结果可以用res = Math.min(res, subL)

记得考虑整个数组的和仍比target小的情况!

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

视频讲解:https://www.bilibili.com/video/BV1SL4y1N7mV/

碰撞检测解法

在这里插入图片描述

class Solution {
    public int[][] generateMatrix(int n) {
        int[] dx = {-1,0,1,0};
        int[] dy = {0,1,0,-1};  //上右下左4方向偏移量
        int[][] res = new int[n][n];
        int a = 0,b = 0,x = 0,y = 0;
        int d = 1; //初始方向向右
        for(int i = 1; i <= n*n; i++) {
            res[x][y] = i;  //在当前位置填值
            a = x + dx[d];
            b = y + dy[d];
            if(a < 0 || a >= n || b < 0 || b >= n || res[a][b] > 0) {  //越界条件
                d = (d + 1) % 4;  //更新方向
                a = x + dx[d];
                b = y + dy[d];
            }
            //此时才真正赋值
            x = x + dx[d];
            y = y + dy[d];
        }
        return res;
    }
}

模拟解法

定义好循环不变量左闭右开,每条边最后一个节点留给下一条边处理

模拟顺时针画矩阵的过程:

  • 填充上行从左到右
  • 填充右列从上到下
  • 填充下行从右到左
  • 填充左列从下到上

loop = n / 2; //每个圈循环几次,例如n为奇数3,那么loop = 1 只是循环一圈,矩阵中间的值需要单独处理

在这里插入图片描述

Java代码:

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;
    }
}

数组总结

这里两道题用到了循环不变量区间的定义。以及两个双指针,双指针的目的就是在一层for里做两层for的事情。滑动窗口也很巧妙,精髓在于遍历的是终止位置,如何动态移动起始位置。

螺旋矩阵我先用的碰撞检测法,4个方向偏移量,感觉比较容易理解。

要多考虑几种情况,比如209题,一开始忘记考虑整个数组加起来还是比Target小的情况,采用赋值成INT_MAX来判断是不是这样。

这两天收获满满,希望继续坚持!

  • 24
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值