目录
1 今日学习的文章
有序数组的平方
看到题目的第一想法
第一时间想到用暴力的方法来解决,先把数据平移再对新数组进行排序
考虑到题目是非递减数组和负数,同时考虑到双指针,我的思路是找到第一个非负元素和负数最小的两个作为快指针进行大小比较,慢指针指向新数组要填充的,也就是慢指针递增,但是找到第一个非负元素总是会出问题?
看到代码随想录之后的想法
代码随想录提出的办法是 :负数最大的和正数最大的平方,总会是最大的,从最大的开始比,然后在新数组中从大到小填充(也就是从后往前进行填充)
自己实现过程中遇到的困难
1:for循环判断时,i<j和i<=j有点不太确定,解决办法,可以考虑最后一次的情况,当最后一个指针指向i==j时,可以确定这个下标对应的值是需要继续计算的。
2.审题要多想一步:比如说 负数的平方越小的越大,正数的平方越大的越大,就可以考虑从数组两端开始对新数组由大到小来填充(我的思路是由小到大比较麻烦)
class Solution {
public int[] sortedSquares(int[] nums) {
//非递减顺序如何满足?本身就是非递减了 如果为正 则可以直接判断,如果为负数则要看平方
//双指针,慢指针指向将要填入的,快指针指向目标
//两个快指针,正数最大的平方最大,负数最小的平方最大
int[] newArray = new int[nums.length];
int k=nums.length-1;
for(int i=0,j=nums.length-1;i<=j;){
if(nums[i]*nums[i]<nums[j]*nums[j]){
newArray[k--] = nums[j]*nums[j];
j--;
}else{
newArray[k--] = nums[i]*nums[i];
i++;
}
}
return newArray;
}
}
长度最小的子数组
所谓滑动窗口,就是不断的调节子序列的起始位置和终止位置,从而得出我们要想的结果。
本质是满足了单调性,即左右指针只会往一个方向走且不会回头。收缩的本质即去掉不再需要的元素
题目建议: 本题关键在于理解滑动窗口,这个滑动窗口看文字讲解 还挺难理解的,建议大家先看视频讲解。 拓展题目可以先不做。
题目链接:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
文章讲解:代码随想录
看到题目的第一想法
使用双向双指针(代码存在问题,思路时左右两个指针来判断,估计会遗漏相关指针)
class Solution {
public int minSubArrayLen(int target, int[] nums) {
//总和大于等于target且长度最小的连续子数组
//相向双指针 左边一个,右边一个,设置一个最大值
int sum = 0;
for(int i=0;i<nums.length;i++){
sum+=nums[i];
}
if(sum<target){
return 0;
}
int leftIndex = 0;
int rightIndex = nums.length-1;
int size = nums.length;
//先判断哪边的数值小,用sum-数值小的,看是否满足sum>target,如果满足则数值小的指针进行移动,继续循环
while(leftIndex<=rightIndex){
//处理left
if(nums[leftIndex]<=nums[rightIndex]){
sum-=nums[leftIndex++];
if(sum<target){
return size;
}
size--;
}else{
sum-=nums[rightIndex--];
if(sum<target){
return size;
}
size--;
}
}
return -1;
}
}
看到代码随想录之后的想法
代码随想录提出的办法是 :滑动窗口(有点像双指针),用两个指针画出一个窗口来
1 需要确定for循环中的变量定位的指针是窗口起始位置的还是终止位置的,如果定位的指针为起始位置的其实和暴力方法没什么区别,所以应该选终止的
2 选择后面的指针的话,前面的指针应该什么时候移动?
当窗口中的数值满足条件(>target)时,前面的指针就可以开始移动了
3 在本题中实现滑动窗口,主要确定如下三点:窗口内是什么?如何移动窗口的起始位置?如何移动窗口的结束位置?
窗口就是 满足其和 ≥ s 的长度最小的 连续 子数组。
窗口的起始位置如何移动:如果当前窗口的值大于s了,窗口就要向前移动了(也就是该缩小了)。
窗口的结束位置如何移动:窗口的结束位置就是遍历数组的指针,也就是for循环里的索引
自己实现过程中遇到的困难
1:result的初始值的设定:需要考虑一次while循环都无法进入的情况(数组中所有元素相加都无法满足) 把result设置的足够大,return时需要做相关判断,如果还为初始值则返回0
2:for循环中需要使用while循环来起始位置的指针移动次数(我设置的if发现走不通)
3: 子序列的长度为fastIndex-slowIndex+1(当fastIndex=slowIndex时长度为1,所以要+1)
class Solution {
public int minSubArrayLen(int target, int[] nums) {
//滑动窗口 还是双指针的思路来判断,fastIndex先逐个往后走,达到>=target且最小时则slowIndex往后走
//使用count记录数组大小 fastIndex移动一位count++,slowIndex移动一位count--
int slowIndex = 0;
int sum=0;
int result = Integer.MAX_VALUE;
int length;
for(int fastIndex=0;fastIndex<nums.length;fastIndex++){
sum+=nums[fastIndex];
while(sum>=target){
//得到数组的长度
length = fastIndex-slowIndex+1;
//若数组的长度比result要短则取数组
result = result<length?result:length;
sum-=nums[slowIndex++];
}
}
return result==Integer.MAX_VALUE?0:result;
}
}
长度最小的子数组
所谓滑动窗口,就是不断的调节子序列的起始位置和终止位置,从而得出我们要想的结果。
本质是满足了单调性,即左右指针只会往一个方向走且不会回头。收缩的本质即去掉不再需要的元素
看到题目的第一想法
二维数组不太会,因为平时接触的是java很少用到数组,这道题属于完全没有思路的题目
看到代码随想录之后的想法
1 代码随想录给出的思路是画边,按照顺序挨个画边,有几个难点
难点1:需要确定画的圈数,按照循环来画,循环时怎么设置的
可以理解为每次划一圈,每条边就少两个数于是圈数就为n/2
难点2:每一圈的每一条边画的长度不同,要怎么界定
1 每条边按照左闭右开规则
2 每画完一圈,将offset+1,使用n-offset 来确定下一圈要画的长度(n-loop也可以)
难点3:奇数偶数要怎么确定
当n为偶数时直接执行,当n为奇数时需要额外处理最后一个元素
自己实现过程中遇到的困难
1 卡哥视频中提到的offset,startX,startY,这些其实在全局中值都是一样的,过于纠结这几个变量脑子会乱
2 循环的次数问题,虽然知道要执行n/2次,但是一时间不知道怎么让循环执行n/2次。。。其实很简单就是设置个loop变量让loop++<n/2就行了
3 设置多个变量可能会出现loop,offset,startX,startY 数据有问题导致数组溢出。。暂时没发现原因
class Solution {
public int[][] generateMatrix(int n) {
// 画边 从左往右,从上往下,从右往左,从下往上
// 需要定义 每次需要走几步(第一次是n-1,第二次是n-2)设置为n-offset,
// 总共需要走多少圈:每转一圈一条边就要少两个格子,所以圈数为n/2
// 当n为奇数是需要额外填充最中间的元素为n^2
int loop = 0;
int[][] array = new int[n][n];
int start=0;
int offset = 0;
int count=1;
int i,j;
//while循环中只要>0则执行 用loop=n/2 loop-->0是会报错
while(loop++<n/2){
//用n-loop 用n-offset会报错。
//画第一条边a[i][j]--->a[i][n-offset] 动的是y轴 所以startY
for(j=start;j<n-loop;j++){
array[start][j] = count++;
}
//画第二条边 a[i][n-offset]--->a[n-offset][n-offset] 动的是x轴所以startX
for(i=start;i<n-loop;i++){
array[i][j] = count++;
}
//画第三条边 a[n-offset][n-offset]--->a[n-offset][startY]
for(;j>start;j--){
array[i][j] = count++;
}
//画第四条边
for(;i>start;i--){
array[i][j]=count++;
}
start++;
}
if(n%2!=0){
//处理最中间的 可用count 可用n*n
array[n/2][n/2]=n*n;
}
return array;
}
}
可以参考每日精华的一些总结
https://www.yuque.com/chengxuyuancarl/wnx1np/ktwax2#35ee2f7d
- 滑动窗口:本质是满足了单调性,即左右指针只会往一个方向走且不会回头。收缩的本质即去掉不再需要的元素。也就是做题我们可以先固定移动右指针,判断条件是否可以收缩左指针算范围。大家可以好好理解一下。
- 加入滑动窗口中有负数怎么办?
如果有负数的话感觉也不能用滑动窗口了,因为有负数的话无论你收缩还是扩张窗口,你里面的值的总和都可能增加或减少,就不像之前收缩一定变小,扩张一定变大,一切就变得不可控了。如果要 cover 所有的情况,那每次 left 都要缩到 right,那就退化为暴力了哈哈。 - 在滑动窗口类型题目里有没有去DEBUG的什么小技巧呢?
一般是怀疑哪里有问题就打印哪里 像今天的滑动窗口 就可以把窗口首尾的下标变化过程打印出来 能很清楚的看到窗口是怎样移动的 - 双指针和滑动窗口有什么区别,感觉双指针也是不断缩小的窗口。这道题,我想用两头取值的双指针,结果错了?
因为两头指针走完相当于最多只把整个数组遍历一遍,会漏掉很多情况。滑动窗口实际上是双层遍历的优化版本,而双指针其实只有一层遍历,只不过是从头尾开始遍历的。
滑动窗口的原理是右边先开始走,然后直到窗口内值的总和大于target,此时就开始缩圈,缩圈是为了找到最小值,只要此时总和还大于target,我就一直缩小,缩小到小于target为止在这过程中不断更新最小的长度值,然后右边继续走,如此反复,直到右边碰到边界。这样就保证了可以考虑到最小的情况
运行时的错误代码(不知道错在哪里希望有人指正)
class Solution {
public int[][] generateMatrix(int n) {
// 画边 从左往右,从上往下,从右往左,从下往上
// 需要定义 每次需要走几步(第一次是n-1,第二次是n-2)设置为n-offset,
// 总共需要走多少圈:每转一圈一条边就要少两个格子,所以圈数为n/2
// 当n为奇数是需要额外填充最中间的元素为n^2
int loop = n/2;
int[][] array = new int[n][n];
int start=0;
int offset = 0;
int count=0;
int i,j;
//while循环中只要>0则执行 用loop=n/2 loop-->0是错误的
while(loop-->0){
//画第一条边a[i][j]--->a[i][n-offset] 动的是y轴 所以startY
for(j=start;j<n-offset;j++){
array[start][j] = count++;
}
//画第二条边 a[i][n-offset]--->a[n-offset][n-offset] 动的是x轴所以startX
for(i=start;i<n-offset;i++){
array[i][j] = count++;
}
//画第三条边 a[n-offset][n-offset]--->a[n-offset][startY]
for(;j>start;j--){
array[i][j] = count++;
}
//画第四条边
for(;i>start;i--){
array[i][j]=count++;
}
start++;
offset++;
}
if(n%2!=0){
//处理最中间的
array[n/2][n/2]=n*n;
}
return array;
}
}