前言
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]);
备注
这两题都比较快想到了解决方法,但实现起来出错很多。