羊羊刷题笔记Day02/60 | 第一章 数组P2 | 977. 有序数组的平方、209. 长度最小的子数组、59. 螺旋矩阵II

81 阅读3分钟

学习资料:

代码随想录

977.有序数组的平方

209.长度最小的子数组

59.螺旋矩阵II

977 有序数组的平方

动手写

总思路是先平方再排序,排序有很多种方法,但现有记忆只能写效率最低的冒泡排序:

 public int[] sortedSquares(int[] nums) {
     // [-3,-1,0,2]
     // 先平方
     for (int i = 0; i < nums.length; i++) {
         nums[i] = nums[i] * nums[i];
     }
 ​
 ​
     // [9,1,0,4]
     // 后排序 - 冒泡排序
     for (int i = 0; i <= nums.length - 2; i++) {
         for (int i1 = i + 1; i1 <= nums.length - 1; i1++) {
             if (nums[i1] < nums[i]){
                 // 交换位置
                 int temp = nums[i1];
                 nums[i1] = nums[i];
                 nums[i] = temp;
             }
         }
     }
     return nums;
 }

解答成功: 执行耗时:637 ms,击败了5.02% 的Java用户 内存消耗:44.5 MB,击败了5.11% 的Java用户

看视频

双指针法: 为什么会想到双指针法? 虽然和第一天名字一样,但仅仅是名字一样用了两个指针,原理不同。 结合本题的特点,由于该数组是递增的,且包含了负数,负数有可能平方之后更大 因此,越往两边的数平方后越大。 那么就设置两个指针在头尾比较,最大的插入新数组。

对比起来,双指针更针对本题的情况,取得了最佳性能。 而传统的方法更为通用,无论数组是否有序都可以使用。 反思:优化算法可以从“是否充分运用题目条件,题目特点”思考

双指针代码:

 // 双指针
 int k = nums.length - 1;
 int[] result = new int[nums.length];
 ​
 // 注意i和j范围,由于i == j时还要判断并且写入新数组,因此为<=
 for (int i = 0, j = nums.length - 1; i <= j; ) {
     if ((nums[i] * nums[i]) > (nums[j] * nums[j])) {
         // 给新数组赋予最大值
         result[k] = nums[i] * nums[i];
         i++;
         k--;
     }
     else {
         // 给新数组赋予最大值
         result[k] = nums[j] * nums[j];
         k--;
         j--;
     }
 }
 return result;

此时的时间复杂度为O(n) 执行耗时:1 ms,击败了100.00% 的Java用户 内存消耗:43.7 MB,击败了39.33% 的Java用户

209 长度最小的子数组

动手写

想法:使用双指针先选定一个连续大区域,然后左指针往右缩小值最小。以此不断生成最小区域,取最小的数组长度值min。但自己敲的代码由于时间复杂度高,面对大量数组测试时TimeOut

 public int minSubArrayLen(int target, int[] nums) {
     // 双指针
     int l = 0;
     int r;
     int min = 0;
 ​
     for (r = 0; r < nums.length; r++) {
         // 指针内区域相加 - 获得区域
         int sum = 0;
         for (int i = l; i <= r; i++) {
             sum = sum + nums[i];
         }
 ​
         // 优化区域
         if (sum >= target) {
             int num = minification(++l, r, nums, target);
             if (min == 0 || num < min){
                 min = num;
             }
             l++;
         }
     }
     return min;
 ​
 }
 ​
 public int minification(int l, int r, int[] nums, int target) {
     // 缩小范围
     int sum1 = 0;
     for (int i = l; i <= r; i++) {
         sum1 = sum1 + nums[i];
     }
     if (sum1 >= target) {
         // 如果还是>= 则继续优化
         return minification(++l, r, nums, target);
     } else {
         // 出现<情况时返回l指针
         l--;
         System.out.print(l);
         System.out.print(r);
         return r - l + 1;
     }
 }

看视频后

计算sum时候写的太复杂了,借助上层循环完成相加操作,根据视频优化后:

 // 双指针
 int l = 0;
 int r;
 int min = Integer.MAX_VALUE;
 int sum = 0;
 ​
 for (r = 0; r < nums.length; r++) {
     // 指针内区域相加 - 获得区域
     sum = sum + nums[r];
 ​
     // 优化区域
     while (sum >= target) {
         // 记录当前值
         min = Math.min(min, r - l + 1);
         sum = sum - nums[l];
         l++;
     }
 ​
 }
 return min == Integer.MAX_VALUE? 0 : min;

反思:利用已做的事情,优化不必要的时间复杂度

59 螺旋矩阵 II

本题对昨天的"循环不变量"进行巩固,四次循环如果还不设定一个标准,就乱套了。

卡哥: 大家写二分法经常写乱,主要是因为对区间的定义没有想清楚,区间的定义就是不变量。要在二分查找的过程中,保持不变量,就是在while寻找中每一次边界的处理都要坚持根据区间的定义来操作,这就是循环不变量规则。

代码:左闭右开[ )

 public int[][] generateMatrix(int n) {
 int x = 0;
 int y = 0;
 int offset = 1;
 int count = 1;
 int loop = 0;//循环次数
 int[][] arr = new int[n][n];
 ​
 while (loop++ < n / 2) {
     int i = x;
     int j = y;
 ​
     // →
     for (; j < n - offset; j++) {
         arr[i][j] = count++;
     }
     // ↓
     for (; i < n - offset; i++) {
         arr[i][j] = count++;
     }
     // ←
     for (; j >= offset; j--) {
         arr[i][j] = count++;
     }
     // ↑
     for(; i >= offset; i--){
         arr[i][j] = count++;
     }
 ​
     // 改变参数
     offset++;
     x++;
     y++;
 }
 ​
 // 特殊:当n为奇数时,填补中间数值
 if (n % 2 == 1) {
     arr[x][y] = count;
 }
 ​
 return arr;
 }

要非常注意四条边的边界划分: