Leetcode长度最小的子数组209/螺旋矩阵59

文章讲述了作者在解决LeetCode中的两道问题,209题长度最小子数组和59题螺旋矩阵生成,通过错误反思,详细分析了算法思路、双指针技巧和滑动窗口的应用,以及在实现过程中遇到的常见错误和解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

Leetcode209/59

一、209题(长度最小的子数组)

题目描述:给定一个含有 n 个正整数的数组和一个正整数 target 。

找出该数组中满足其总和大于等于 target 的长度最小的 连续
子数组
[numsl, numsl+1, …, numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。

示例 1:
输入:target = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3] 是该条件下的长度最小的子数组。
题解(mine):做了好久好久好久,接下来首先归纳一下做这道题花这么久的原因(╯▔皿▔)╯

错误反思:

  • 看错题,要求是大于等于,看成了等于
  • 由于使用的是[left,right)区间,对于for循环里的条件究竟是right<nums.length还是right<=nums.length弄了好久。一般说来,由于right处是开区间,所以可以取到nums.length,但由于代码中有这一句"sum = sum + nums[right++]",如果right值取到nums.length,则会出现nums[nums.length],index越界错误。区别于二分法。
  • ①长度为1的没有特殊处理②忽略了"12345"这种直到结束循环才出现sum大于target的情况,导致无法为ans赋值(这两个错误都是循环体的问题,感觉只有碰到错误具体实例才能发现)

题解1

mine(和大佬对比差距有些大😟再次练习时直接参考解法2学习,解法1只用于记录和反思自己的初次思路)

class Solution {
    public int minSubArrayLen(int target, int[] nums) {
        int left = 0;
        int ans = 0;
        if(nums.length==1){
            if(nums[0]>=target)
            return 1;
            else
            return 0;
        }
        int sum = nums[0];
        int right;
        for(right = 1;right<nums.length;){
            if(sum>=target){
                if(ans==0){
                    ans = right - left;
                }
                else{
                    ans = ans<right-left?ans:right-left;
                }
                sum = sum - nums[left++];
            }
            else{
                sum = sum + nums[right++];
            }
        }
        //会出现到最后一个元素才大于target,但由于退出没法更新ans的情况
        if(ans==0&&sum>=target){
            ans = right-left;
        }
        //退出时,right = nums.length。只能移动left指针
        if(sum<target || left ==nums.length-1){
            return ans;
        }
        while(sum>=target){
            sum = sum - nums[left++];
        }
        //right-left+1
        return ans<(right-left+1)?ans:(right-left+1);
    }
}

写到错误反思突然意识到,第三点的1和2其实都是一个错误,即“没有进入循环体的为ans赋值的代码块里",比如只有一个元素[6],target为5,但由于right=1等于num.length=1,故没机会判断"sum>=target"从而为ans赋值。又比如[1,2,3,4,5]直到right=5时sum才大于target,但此时直接退出,也没机会判断"sum>=target"从而为ans赋值。这样说来二者的情况类似,所以不用分别特殊为①和②处理,上面代码中的如下一段就统一解决了。

if(ans==0&&sum>=target){
            ans = right-left;
        }

修改后的代码如下所示:

class Solution {
    public int minSubArrayLen(int target, int[] nums) {
        int left = 0;
        int ans = 0;
        // if(nums.length==1){
        //     if(nums[0]>=target)
        //     return 1;
        //     else
        //     return 0;
        // }
        int sum = nums[0];
        int right;
        for(right = 1;right<nums.length;){
            if(sum>=target){
                if(ans==0){
                    ans = right - left;
                }
                else{
                    ans = ans<right-left?ans:right-left;
                }
                sum = sum - nums[left++];
            }
            else{
                sum = sum + nums[right++];
            }
        }
        //会出现到最后一个元素才大于target,但由于退出没法更新ans的情况
        if(ans==0&&sum>=target){
            ans = right-left;
        }
        //退出时,right = nums.length。只能移动left指针
        if(sum<target || left ==nums.length-1){
            return ans;
        }
        while(sum>=target){
            sum = sum - nums[left++];
        }
        //right-left+1
        return ans<(right-left+1)?ans:(right-left+1);
    }
}

算法思路:
双指针,[left,right),当前sum等于该区间内所有元素的和。首先sum=nums[0],left指向0,right指向1。如果sum大于等于target,则存下较小ans值。如果sum小于target,则要加上当前right所指元素,并且把right向后移。
关键点在于,"如果sum大于等于target,则存下较小ans值"之后如何移动,究竟是right++呢还是left++。当然是left++,因为right所指元素还没处理过,是开区间,如果直接加加,之后就处理不到了。既然是left++,那么就应该是sum=sum-nums[left++]

题解2

解法:重点在于如何移动起始位置,达到动态更新窗口大小。滑动窗口的精妙之处在于根据当前子序列和大小的情况,不断调节子序列的起始位置即while控制语句

class Solution {

    // 滑动窗口
    public int minSubArrayLen(int target, int[] nums) {
        int left = 0;
        int sum = 0;
        int result = Integer.MAX_VALUE;
        //这段for循环很妙,完美阐释了:终止指针遍历,起始指针的移动和判别有关
        for (int right = 0; right < nums.length; right++) {
            sum += nums[right];
            while (sum >= target) {
                result = Math.min(result, right - left + 1);
                sum -= nums[left++];
            }
        }
        //有可能遍历完整个数组都不出现sum>=target
        return result == Integer.MAX_VALUE ? 0 : result;
    }
}

算法思想:

  • 滑动窗口:[start,end],起始指针和终止指针,构成了一个窗口,每次判断窗口内值是否符合要求,符合要求则记录。终止指针在整个数组上遍历,起始指针则按需求更新位置。
  • 该算法中的如下段代码块很好的体现了算法的思想(每个终止指针都会对应更新起始指针位置),当终止指针停在right处时,起始指针可以连续向右移动缩小窗口,直到不再满足所需要求。我的代码中一次只移动一个指针的一个位置
while (sum >= s) {
                result = Math.min(result, right - left + 1);
                sum -= nums[left++];
            }

二、59题(螺旋矩阵Ⅱ)

题目描述:给你一个正整数n ,生成一个包含1到n2所有元素,且元素按顺时针顺序螺旋排列的n x n正方形矩阵matrix。

class Solution {
    public int[][] generateMatrix(int n) {
        int[][] matrix = new int[n][n];
        //目前所有格子里都是0,只要格子不为0就表示走过了
        //思路是:一直朝一个方向走,走到头或者碰到走过了的元素就掉头
        int direction = 0;//{用0,1,2,3}表示向右,下,左,上,掉头就是加1模4
        int i = 0,j = 0;
        int num = 1;
        while(num<=n*n){
            if(i<n&&i>=0&&j<n&&j>=0&&matrix[i][j]==0){
                matrix[i][j] = num++;   
                if(direction == 0){
                    j++;
                }
                else if(direction == 1){
                    i++;
                } 
                else if(direction==2){
                    j--;
                }   
                else{
                    i--;
                }
            }
            else{
                //当前走到的格子已经有数字的话,反方向退一格并且换方向走一格
                if(direction == 0){
                    j--;
                    direction = 1;
                    i++;
                }
                else if(direction == 1){
                    i--;
                    direction = 2;
                    j--;
                } 
                else if(direction==2){
                    j++;
                    direction = 3;
                    i--;
                }   
                else{
                    i++;
                    direction = 0;
                    j++;
                }
                //此处不要加入matrix[i][j]=num++;
            }
        }
        return matrix;
    }
}

算法思路

  • 一开始没什么思路,动手画了几个图之后灵光一现。即每次都是在到边界和碰到已存在元素的格子时转弯,且转弯固定,原方向是向右则向下,向下则向左,向左则向上,向上则向右。
  • 根据上述,不难发现,画螺旋矩阵的关键点在于控制方向。而方向如何改变有几个关键点:①原方向上继续移动一格的前提条件是:前方没有元素,且前方没有出界②当出现无法在原方向上继续移动一格的情况时:转弯
  • 我的算法:需要用(i,j)记录当前位置,num记录当前需要填入方格的数字,当前的方向direction。①如果(i,j)是一个符合要求的位置,填入数字,并且按原方向移动一格②否则,调整方向:原方向回退一格,换方向并走一格。

写的过程中出现的易错点:一道折磨人的题(;′⌒)`

  • “if(i<n&&i>=0&&j<n&&j>=0&&matrix[i][j]==0)”:必须把i和j的判断放前面,不然matrix[i][j]放前面的话会出现越界错误,因为先判断的matrix[i][j]是否为0,没有判断i和j是否合法。且注意i和j不仅要小于n还要大于0,正方形四边都有界,和之前的从左往右的数组不同。
  • 与之对应的else语句块是用来调整方向的,但不可以在里面增加赋值功能,因为if语句块是用来赋值的。如果在else语句块加入了赋值语句"matrix[i][j]=num++",即调整位置后赋值(看似逻辑上很正确),如果后面什么也不写,那么将进入下一次循环,此时matrix[i][j]上已经有元素了,就会再次进入else块调整方向,造成错误。所以else只负责调整位置,赋值交给if语句。

看了下别人的算法,跟我的不太一样,绕来绕去不想看了😴,我这个方法挺好的,先就这样吧

三、54题(螺旋矩阵)

题目描述题目描述去网上搜。我的建议是好好审题,有了59题这题真的很容易理解错题意,麻了,有时间再做吧,好讨厌这种绕来绕去的题目ε=ε=ε=┏(゜ロ゜;)┛

class Solution {
    public List<Integer> spiralOrder(int[][] matrix) {
        // 获得行数和列数
        int m = matrix.length;
        int n = matrix[0].length;
        List<Integer> order = new ArrayList<Integer>();//order.add(matrix[x][y]);
        int i = 1;
        int x=0,y=0,direction=0;//{0,1,2,3}右下左上
        int[][] visit = new int[m][n];//是否访问过当前格子
        while(i<=m*n){
            //判断位置(x,y)是否合法,合法则加入List容器,不合法则更改位置
            //合法:不越界且没访问过
            if(x>=0&&x<m&&y>=0&&y<n&&visit[x][y]==0){
                order.add(matrix[x][y]);
                i++;
                visit[x][y]=1;
                //原方向继续走一格
                if(direction == 0){
                    y++;
                }else if(direction == 1){
                    x++;
                }else if(direction == 2){
                    y--;
                }else{
                    x--;
                }
            }
            else{
                //改变位置:原方向回退一格,换方向走一格
                if(direction == 0){
                    y--;
                    direction = 1;
                    x++;
                }else if(direction==1){
                    x--;
                    direction = 2;
                    y--;
                }else if(direction ==2){
                    y++;
                    direction =3;
                    x--;
                }else{
                    x++;
                    direction = 0;
                    y++;
                }
            }
        }
        return order;
    }
}

算法说明:

  • 和上一题基本相同,算法思路已在代码中标注清晰,就不多做说明了。以下归纳一些知识点:
  • ①二维数组(int[][] matrix)获得行和列数的方法:
    int m = matrix.length;
    int n = matrix[0].length;
  • ②List容器:
    List<Integer.> order = new ArrayList<Integer.>();
    order.add(matrix[x][y]);

备注

这两题都比较快想到了解决方法,但实现起来出错很多。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值