今日内容
977.有序数组的平方 ,209.长度最小的子数组 ,59.螺旋矩阵II ,总结
代码随想录链接:代码随想录 (programmercarl.com)
977.有序数组的平方
给你一个按 非递减顺序 排序的整数数组
nums,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。
暴力排序
第一个思路,全体平方后排序(时间复杂度很大),代码如下
class Solution {
public int[] sortedSquares(int[] nums) {
int[] pf = new int[nums.length];
for(int i = 0; i < nums.length ; i++){
pf[i] = nums[i] * nums[i];
}
Arrays.sort(pf);
return pf;
}
}
这是暴力排序,这个时间复杂度是 O(n + nlogn), 可以说是O(nlogn)的时间复杂度,但为了和下面双指针法算法时间复杂度有鲜明对比,我记为 O(n + nlog n)。
Tips
Array.sort() 快速排序(时间复杂度 nlogn)
- sort(T[] a):对指定T型数组按数字升序排序。
- sort(T[] a,int formIndex, int toIndex):对指定T型数组的指定范围按数字升序排序。
- sort(T[] a, Comparator<? supre T> c): 根据指定比较器产生的顺序对指定对象数组进行排序。
- sort(T[] a, int formIndex, int toIndex, Comparator<? supre T> c): 根据指定比较器产生的顺序对指定对象数组的指定对象数组进行排序。
双指针法
数组平方的最大值在数组的两端,因此只要比较最左边和最右边的平方值,就可以从大到小找到结果,代码如下:
class Solution {
public int[] sortedSquares(int[] nums) {
int k = nums.length-1;
int l = 0,r = k;
int[] ans = new int[nums.length] ;
while(l <= r){
if(nums[l] * nums[l] < nums[r] * nums[r]){
ans[k--] = nums[r] * nums[r];
r--;
}
else{
ans[k--] = nums[l] * nums[l];
l++;
}
}
return ans;
}
}
此时的时间复杂度为O(n),相对于暴力排序的解法O(n + nlog n)还是提升不少的。
209.长度最小的子数组
给定一个含有
n个正整数的数组和一个正整数target。 找出该数组中满足其总和大于等于target的长度最小的 连续子数组[numsl, numsl+1, ..., numsr-1, numsr],并返回其长度。如果不存在符合条件的子数组,返回0。
暴力求解(超时)
看到这题直接眉头一皱,但是要求是连续子数组,那么就需要一个固定的“窗口”去一个个“圈”(遍历)出可能的子数组,查看是否相等。
写了半天的废物代码如下:
class Solution {
public int minSubArrayLen(int target, int[] nums) {
int len ; //滑动数组的长度
int ans = 0 ;
int key = 0;
for(len = 1; len <= nums.length; len++ ){
for(int i = 0; i <= nums.length - len; i++ ){
int sum = 0;
for(int j = i; j < i+len; j++){
sum = sum + nums[j];
}
if(sum >= target){
ans = len;
key = 1;
}
}
if(key == 1) break;
}
return ans ;
}
}
这个代码成功超过了力扣的时间限制,时间复杂度O(n^2),这段代码中的key是用于break的,体现本人原始的代码水平。
经过学习后,改进后的代码:
class Solution {
public int minSubArrayLen(int target, int[] nums) {
int result = Integer.MAX_VALUE;
int sum = 0;
int subLength = 0;
for (int i = 0; i < nums.length; i++) {
sum = 0;
for (int j = i; j < nums.length; j++) {
sum += nums[j];
if (sum >= target) {
subLength = j - i + 1;
result = result < subLength ? result : subLength;
break;
}
}
}
return result == Integer.MAX_VALUE ? 0 : result;
}
}
其实在我自己写的时候就发现了,如果子数组长度len的初始值设置为0,那么在第一次Min(result = result < subLength ? result : subLength;) 的比较中将会一直取0,因此将result值去一个相对来说很大的值,在这种题目的情况下,就是Integer.MAX_VALUE,这样在结尾跟一个正则表达式就可以用较少的代码完成事情,归根结底是我代码写少了。。
滑动窗口
旨在只用一个循环完成任务,这种情况下我们对右边的指针进行循环,当左右指针之间的值之和大于target时,就要移动左指针,灰常巧妙。
看懂了,开始写,不会了,哈哈,代码如下:
class Solution {
public int minSubArrayLen(int target, int[] nums) {
int result = Integer.MAX_VALUE;
int left = 0;
int sum = 0;
int sublength = 0;
for(int right = 0;right < nums.length; right++){
sum += nums[right];
while(sum >= target){
sublength = right - left + 1;
result = sublength < result ? sublength : result;
sum -= nums[left];
left++;
}
}
return result == Integer.MAX_VALUE ? 0 :result ;
}
}
可以发现滑动窗口的精妙之处在于根据当前子序列和大小的情况,不断调节子序列的起始位置。从而将O(n^2)暴力解法降为O(n)。,最关键的就是这个left++,牛的。这样是一种双指针,双指针确实关键。
螺旋矩阵
给你一个正整数
n,生成一个包含1到n2所有元素,且元素按顺时针顺序螺旋排列的n x n正方形矩阵matrix。
主要就是变化坐标[i][j],代码如下:
class Solution {
public int[][] generateMatrix(int n) {
int res[][] = new int[n][n];
int num = 1; //赋值用
int start = 0;
int loop = 0;
int offset = 1;
int mid = n / 2;
int i,j;
while(loop++ < n / 2){
for (j = start; j < n - offset; j++) {
res[start][j] = num++;
}
// 模拟填充右列从上到下(左闭右开)
for (i = start; i < n - offset; i++) {
res[i][j] = num++;
}
// 模拟填充下行从右到左(左闭右开)
for (; j > start; j--) {
res[i][j] = num++;
}
// 模拟填充左列从下到上(左闭右开)
for (; i > start; i--) {
res[i][j] = num++;
}
start++;
offset++;
}
if(n % 2 == 1){
res[mid][mid] = num;
}
return res;
}
}
看懂了,估计下一次也写不起。。。
晚上22:50,疲惫的代码消愁结束了今日的学习,今日份遗留:快速排序的代码需要学一下,写一下;螺旋还要自己写一个,不看答案写。