算法学习day02:有序数组的平方,长度最小的子数组,螺旋矩阵II(Java)

发布时间:2024年01月15日

有序数组的平方

题目:
给你一个按 非递减顺序 排序的整数数组 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]

最直白(暴力)的解法是把数组中每个数都平方,再进行排序。但这样做的时间复杂度是O(nlogn),于是乎尝试用双指针对其进行优化

思路:
如果数组内只有正数,那根本不需要排序,直接平方就搞定了,但数组中有正数和负数,就要引出双指针。
把数组想象成一个数轴,0在中间,左右是正负,我们知道数字离0越远(绝对值大),它的平方就越大。所以我们在数组左右都放上一个指针,对其平方进行比较。把较大的数字提出来,然后让左右指针向中间靠拢,就得到了一串从大到小排序的数字。再创建一个新的数组,从后往前依次将数字填进去,就达成了目标。
代码如下:
public static void main(String[] args) {
int[] arr = {-9,-5,0,1,2,4,6,7,9,10};
System.out.println(Arrays.toString(calc(arr)));
}

public static void main(String[] args) {
    int[] arr = {-9,-5,0,1,2,4,6,7,9,10};
    System.out.println(Arrays.toString(calc(arr)));//打印出目标数组
}
public static int[] calc(int[] arr){
    int[] newArr = new int[arr.length];
    int i = 0;
    int j = arr.length - 1;
    int k = j;
    while (i<=j){
        if(arr[i]*arr[i] > arr[j]*arr[j]){
            newArr[k] = arr[i]*arr[i];
            k--;
            i++;
        }else {
            newArr[k] = arr[j]*arr[j];
            k--;
            j--;
        }
    }
    return newArr;
}

这样的时间复杂度就是O(n),简单了一些
代码还可以再优化一下(如newArr[k–] = arr[i]*arr[i])但为了更简便的展示未做更改

接下来是代码的一些注意事项:
1.while语句可以换成for(i=0,j=0;i<=j;),但要注意第二个分号后面的限制条件不需要写 arr[i]与arr[j]相比较,大的数字指针才会移动,写在for语句后面不好判断
2.while语句的i<=j,等于号不能漏掉。如果去掉了等于号,那么在i=j时循环就结束了,但arr[i]的元素并没有被成功排序,会漏掉一个数字。
3.while语句里的if判定,可以是>也可以是>=,当两个数相等的时候,自然拿出哪一个给k指针都没问题

977.力扣题目:有序数组的平方
代码随想录:有序数组的平方

长度最小的子数组

给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的 连续 子数组,并返回其长度。如果不存在符合条件的子数组,返回 0。

示例:

输入:s = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3] 是该条件下的长度最小的子数组。

和上题一样,先来看暴力解法:使用两个for循环嵌套,外部for循环控制起始位置,内部for循环找到终止位置,暴力解法代码如下:

public static void main(String[] args) {
    int[] arr = {2,3,1,2,4,3};
    System.out.println(Test01(arr, 7));
    
}

public static int Test01(int[] arr, int s){
    int result = Integer.MAX_VALUE;//用来存储最小的数组长度,与length作比较
    int sum = 0;
    int length = 0;//目前的数组长度
    for (int i = 0; i < arr.length; i++) {
        sum = 0;
        for (int j = i; j <arr.length; j++) {
            sum= sum +arr[j];
            if(sum >=s){
                length = j - i + 1;
                result = Math.min(length,result);
                break;//已经找到符合长度的数据,结束内部循环
            }
        }
    }
    return result == Integer.MAX_VALUE ? 0 : result;
}

这样的时间复杂度为O(n^2),我们需要尝试减少时间复杂度,优化的思路和双指针类似。
用一个for循环来控制一个指针从左往右走,再在数组第一位(0)放一个左指针,计算两个指针之间的数据相加,如果相加超过了s,就把左指针往右移,并记录当前的窗口(即左右指针包裹的范围)长度,与当前存储的result作比较,存入较小值

public static void main(String[] args) {
    int[] arr = {2,3,1,2,4,3};
    System.out.println(Test01(arr, 7));
    System.out.println(Test02(arr,7));
}

public static int Test02(int[] arr, int s){
	int left = 0;
	int sum = 0;
	int result = Integer.MAX_VALUE;
	int length = 0;
	for (int right = 0; right < arr.length; right++) {
   		sum += arr[right];
    	while (sum >= s){
    	    length = right - left + 1;
        	result = Math.min(result,length);
        	sum -= arr[left++];
    	}
	}
    return result == Integer.MAX_VALUE ? 0 : result;
}

这样的时间复杂度就是O(n),优化成功
记一些注意事项&解答:

  • 为什么result要设置成int最大值:因为计算要取result和length的较小值存储进result,如果result的初始值是0的话,结果就一直是0了
  • 为什么for循环里是while不是if:考虑一下特殊情况,如果数组是{1,1,1,1,100}且用if语句,s为100的话,左指针在初始位置,右指针到100的时候sum=105,进入if判断一次,左指针到第二个"1"处,右指针继续动起来了,不会得到正确的结果。
    如果是while的话,左指针会一直移动到100处,记录长度为1,得到正确结果

904.力扣相关题目
76.力扣相关题目
代码随想录:长度最小的子数组

螺旋矩阵II

给定一个正整数 n,生成一个包含 1 到 n^2 所有元素,且元素按顺时针顺序螺旋排列的正方形矩阵。

示例:

输入: 3 输出: [ [ 1, 2, 3 ], [ 8, 9, 4 ], [ 7, 6, 5 ] ]

这道题有一点绕,但只要处理好边界,不是很难。
首先看到这道题就会想到用循环解决,但要保证每个循环要干的事是一样的,比如这样一个二维数列
[1 2 3]
[8 9 4]
[7 6 5]
我们对每一行每一列的处理都应该保持一致,比如先填充1和2,在最后一列填充3和4,在最后一行填充5和6,这样一个左闭右开的处理方式。也就是说,每一行/列都要留最后一个空白,所以我们引入一个变量offset。

然后,以一圈为一个循环,来看总共要转多少圈才能完成填充。用草稿纸画一下,会发现4x4的矩阵需要转2圈,3x3的矩阵需要转一圈(但是中间是个空白),得出:循环次数=n/2,如果n为奇数,则需要在中间补上一个数

把这样的二维数组看成矩阵,写成arr[i][j]的形式

int[][] arr = new int[n][n];
int start = 0;//起始位置
int offset = 1;//为了控制循环条件一致而保留的位置
int i = 0;
int j = 0;
int count = 1;//计数用
for(;j<n - offset;j++){
    arr[start][start] = count++;
}

第一个循环结束,此时矩阵为[1,2,0][0,0,0][0,0,0]
开始第二个循环,这个时候应该改变i的位置。注意这里我把i和j放在了循环外面,经历完第一个循环后,此刻j=2,刚好在[1,2,0]的“0”处,继续代码:

for(;i < n - offset;i++){
    arr[i][j] = count++;
}

此刻矩阵为:[1,2,3][0,0,4][0,0,0]

以此类推,第三个循环应该是arr[i][j],限制条件为j>=循环次数,使用j–;最后一个循环不再赘述,基本一致

跑完一圈后,如果有第二圈(即n>=4),应该把startX和startY都进行递增,把offset也进行递增
以n=4举例:跑完一圈后的矩阵为:
[1,2,3,4]
[12,0,0,5]
[11,0,0,6]
[10,9,8,7]
此刻第二圈的起始位置是[1][1],应该对start进行递增。同样根据左闭右开的原则,应该只填充一个位置,所以for循环的限制为i<n-offset(即i<2,只执行一次)

到这里代码基本完成,最外层加上while循环判断次数,以及遇到n为奇数时加一行代码:n[i][j] = count;

最终代码如下:

public static int[][] Test(int n){
    int loop = 0;//当前的循环次数
    int[][] arr = new int[n][n];
    int start = 0;//起始位置
    int offset = 1;//为了控制循环条件一致而保留的位置
    int i = 0;
    int j = 0;
    int count = 1;
    while (loop++<n/2) {
        for(j = start;j < n - offset;j++){//此循环控制上面一行
            arr[start][j] = count++;
        }
        for(i = start;i < n - offset;i++){//此循环控制右边一列
            arr[i][j] = count++;
        }
        for(;j>=loop;j--){//此循环控制下面一行
            arr[i][j] = count++;
        }
        for(;i>=loop;i--){//此循环控制左边一列
            arr[i][j] = count++;
        }
        start++;
        offset++;
    }
    if(n%2 == 1){
        arr[i][j] = count;
    }
    return arr;
}

其实还可以进行优化,比如循环里的offset都可以换成n-loop,但为了更加直观所以没进行更改

一些注意事项:

  • 为什么要左闭右开:保持处理的方式一致,假设第一行我填充了1,2,3,就会影响到第二次或者之后的循环,左闭右开是最佳的处理方式
  • 第三个循环的限制条件为什么是j>=循环次数:如果j>0的话,在第二圈的循环会把第一圈的数字覆盖掉
    54.力扣相关题目
    29.力扣相关题目
    代码随想录:螺旋矩阵II

今日学习时间:1h+1h+2h

文章来源:https://blog.csdn.net/2301_80840872/article/details/135582655
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。