今日学习的文章和视频链接
977.有序数组的平方文章链接: [link]
977.有序数组的平方视频讲解链接: [link]
209.长度最小的子数组文章链接: [link]
209.长度最小的子数组视频讲解链接: [link]
59.螺旋矩阵II文章链接: [link]
59.螺旋矩阵II文章链接: [link]
977.有序数组的平方
给你一个按 非递减顺序 排序的整数数组 nums,返回 每个数字的平方组成的新数组,
要求也按非递减顺序排序。
示例 1: 输入:nums = [-4,-1,0,3,10] 输出:[0,1,9,16,100]
解释:平方后,数组变为 [16,1,0,9,100],排序后,数组变为 [0,1,9,16,100]
示例 2: 输入:nums = [-7,-3,2,3,11] 输出:[4,9,9,49,121]
看到题目第一想法
最直观的想法,每个数平方之后,排个序。
class Solution {
public int[] sortedSquares(int[] nums) {
int n=nums.length-1;
int temp;
int[] result=new int[nums.length];
for (int i=0;i<n+1;i++){
result[i]=( nums[i]* nums[i]);
}
for(int slow=0;slow<nums.length;slow++){
for(int fast=slow ;fast<nums.length;fast++){
if (result[fast]<result[slow]){
temp=result[slow];
result[slow]=result[fast];
result[fast]=temp;
}
}
}
return result;}
}
学习之后
发现数组其实是有序的, 只不过负数平方之后可能成为最大数了。
那么数组平方的最大值就在数组的两端,不是最左边就是最右边,不可能是中间。即数组整体形势:大小大。
此时可以考虑双指针法了,i指向起始位置,j指向终止位置。
定义一个新数组result,和A数组一样的大小,让k指向result数组终止位置,倒着(由大到小)放入结果result数组当中。
循环用 while比for好,因为两个指针自增(i指向的元素平方更大,i++)或自减(j指向的元素平方更大,j–)分情况讨论,用for循环不便于书写。
如果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]; 。
如动画所示:
此时的时间复杂度为O(n)。
class Solution {
public int[] sortedSquares(int[] nums) {
int m=nums.length-1;
int[] result=new int[nums.length];
int left=0;
int right= m;
while (left<=right){//应该取等否则丢失一个数据
if ((nums[left]*nums[left])>(nums[right]*nums[right])){
result[m]=(nums[left]*nums[left]);//倒序录入
m--;
left++;
}else{
result[m]=(nums[right]*nums[right]);
m--;
right--;
}
}
return result;}
}
209.长度最小的子数组
给定一个含有 n 个正整数的数组和一个正整数 s ,
找出该数组中满足其和 ≥ s 的长度最小的连续子数组,并返回其长度。
如果不存在符合条件的子数组,返回 0。
示例:
输入:s = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3] 是该条件下的长度最小的子数组。
看到题目第一想法
一开始理解错了 连续子数组是说以原数组顺序的子数组,我理解成数字来源于原数组但是数字大小连续。纠正错误后一开始的想法就是暴力解法: 两个for循环,然后不断的寻找符合条件的子序列,时间复杂度很明显是O(n^2)。
class Solution {
public int minSubArrayLen(int target, int[] nums) {
int sum=0;
int sumlengthfinal=0;//最终长度,分别找每个位置连续子数组,后筛选最小
int sumlength=0;
for (int i=0;i<nums.length;i++){
for(int j=i;j<nums.length;j++){
sum=sum+nums[j];
sumlength+=1;
if(sum>=target){
if((sumlengthfinal==0)||(sumlength<sumlengthfinal)){
sumlengthfinal=sumlength;
break;}
}
}
sum=0;
sumlength=0;
}
return sumlengthfinal;}
}
• 时间复杂度:O(n^2)
• 空间复杂度:O(1)
结果暴力解法超时了。
学习过后
滑动窗口
滑动窗口,本质是双指针,就是不断的调节子序列的起始位置和终止位置,从而得出我们要想的结果。
在暴力解法中,是一个for循环滑动窗口的起始位置,一个for循环为滑动窗口的终止位置,用两个for循环完成了一个不断搜索区间的过程。
只用一个for循环,那么这个循环的索引,一定是表示滑动窗口的终止位置,否则依旧是暴力解法了。
那么问题来了, 滑动窗口的起始位置如何移动呢?
这里还是以题目中的示例来举例,s=7, 数组是 2,3,1,2,4,3,来看一下查找的过程:
窗口就是 满足其和 ≥ s 的长度最小的 连续子数组。
窗口的起始位置如何移动:如果当前窗口的值大于s了,窗口就要向前移动了(也就是该缩小了)。
窗口的结束位置如何移动:窗口的结束位置就是遍历数组的指针,也就是for循环里的索引。
解题的关键在于 窗口的起始位置如何移动,如图所示:
可以发现滑动窗口的精妙之处在于根据当前子序列和大小的情况,不断调节子序列的起始位置,即学会如何收缩窗口。从而将O(n^2)暴力解法降为O(n)。
//伪代码
i=0//起始位置 j是终止位置
result=max;//只有取最大值后面才能取min比较
for(j=0;j<nums.length;j++){
sum+=nums[j];
while(sum>=target){//用while不用if 因为比如1,1,1,1,1,100,... target=100;应持续移动起点i,终点j不动,
//while连续移动,if移动一次
sumlength=j-i+1;
result=min(result,sumlength);
sum=sum-nums[i];
i++
}
}
return (result==max)?0:result;
class Solution {
public int minSubArrayLen(int target, int[] nums) {
int finallength=nums.length+1;
int i=0;//起点
int sum=0;
int length;
for (int j=0;j<nums.length;j++){
sum+=nums[j];
while(sum>=target){
length=j-i+1;
finallength=Math.min(length,finallength);
sum-=nums[i++];
}
}
return (finallength==nums.length+1)?0:finallength;}
}
• 时间复杂度:O(n)
• 空间复杂度:O(1)
一开始错误地认为时间复杂度为 O(n^2)
,以为for里放一个while就以为是O(n^2)啊, 其实主要是看每一个元素被操作的次数,每个元素在滑动窗后进来操作一次,出去操作一次,每个元素都是被操作两次,所以时间复杂度是 2 × n 也就是O(n)。
补充知识:
59.螺旋矩阵II
给定一个正整数 n,生成一个包含 1 到 n^2 所有元素, 且元素按顺时针顺序螺旋排列的
正方形矩阵。
示例:
输入: 3 输出: [ [ 1, 2, 3 ], [ 8, 9, 4 ], [ 7, 6, 5 ] ]
看到题目第一想法
无非找到四个角的临近条件以四个边为单位分四种情况,触碰临界条件,改变相应行列上下界,限定其变化范围。
错误方法
//边界条件分析错误
class Solution {
public int[][] generateMatrix(int n) {
int[][] result= new int[n][n];
int i=0;//行
int j=0;//列
int m=1;//放第几个数
int imax=n-1;
int imin=0;
int jmax=n-1;
int jmin=0;
while(m<=n){//左闭右开
result[i][j]=m;
m++;
if ((i==imin)&&(j==jmin)){
j++;
if(m!=1){
imax-=1;}}
if ((i==imin)&&(j<jmax)){
j++;}
if ((i==imin)&&(j==jmax)){
i++;
if(m!=n){
jmin+=1;}}
if ((i<imax)&&(j==jmax)){
i++;}
if ((i==imax)&&(j==jmax)){
j--;
imin+=1;}
if ((i==imax)&&(j>jmin)){
j--;}
if ((i==imax)&&(j==jmin)){
i--;
jmax-=1;}
if ((i>imin)&&(j==jmin)){
i--;}
}
return result;}
}
}
本题依然是要坚持循环不变量原则,其难点主要在于起始位置的判断固定与循环不变量–区间的划分。
模拟顺时针画矩阵的过程:
• 填充上行从左到右
• 填充右列从上到下
• 填充下行从右到左
• 填充左列从下到上
由外向内一圈一圈这么画下去。
可以发现这里的边界条件非常多,在一个循环中,如此多的边界条件,必须按照固定规则来遍历。
这里一圈下来,我们要画每四条边,这四条边怎么画,每画一条边都要坚持一致的左闭右开,或者左开右闭的原则,这样这一圈才能按照统一的规则画下来。
本题采用左闭右开。
然后是循环圈数的问题,假设给定数字为n,那么nn的矩阵需要转n/2圈。
例如当n=3需要3/2=1,没整除是因为是奇数,落单的中间块需要手动赋值,其地址为[1,1]=33
当n=5需要5/2=2圈,中间为[2,2]=55
当n=6需要6/2=3圈。
因此对于奇数偶数都有n/2圈,奇数额外手动赋值[n/2,n/2]=nn。
class Solution {
public int[][] generateMatrix(int n) {
int startx=0;
int starty=0;
int offset=1;//偏移量 每转一圈偏移+1 保证左闭右开
int m=1;
int[][] nums=new int[n][n];
int loop=n/2;//圈数
int i,j;
while(loop>0){
for(j=starty;j<n-offset;j++){
nums[startx][j]=m++;
}
for(i=startx;i<n-offset;i++){//上一个循环结束j=n-offset 以4为例 地址为0~3 4-1正好为3最后一列
nums[i][j]=m++;
}
for(;j>starty;j--){//可以不初始化
nums[i][j]=m++;
}
for(;i>startx;i--){
nums[i][j]=m++;
}
startx++;
starty++;
offset++;
loop--;
}
if (n%2==1){//奇数手动赋值中间最后一个
nums[n/2][n/2]=n*n;
}
return nums;}
}