代码随想录训练营day02|977.有序数组的平方 ,209.长度最小的子数组 ,59.螺旋矩阵II

121 阅读4分钟

@TOC


前言

代码随想录算法训练营day02


一、Leetcode 977.有序数组的平方

1.题目

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

提示:

  • 1 <= nums.length <= 104
  • -104 <= nums[i] <= 104
  • nums 已按 非递减顺序 排序

来源:力扣(LeetCode) 链接:leetcode.cn/problems/sq…

2.解题思路

  1. 方法一:暴力解法(执行用时:4 ms,内存消耗:43.1 MB)
  • 创建新数组nums_sort用于存放原数组nums各元素的平方
  • 然后通过排序算法将nums_sort排序并return
  1. 方法二:双指针(执行用时:1 ms, 内存消耗:43.5 MB)

代码随想录网站动画展示 (辅助理解) 在这里插入图片描述

  • 由于数组nums是从小到大排序,所以原数组nums各元素平方后的最大值一定在数组两端
  • 基于该特点我们可以在nums数组两端分别定义左指针 i右指针 j,分别从两边向中间遍历并平方,这样我们可以得到一个由大到小排列的新数组
  • 由于题目要求得到的新数组从小到大排列,所以我们可以将新数组从右(末端)往左(前端)更新赋值
  • 我们可以定义一个更新新数组元素位置的下标k,并将数组末端元素下标nums.length - 1赋值给它(数组元素下标从0开始),让他随着左右指针的移动依次往前移动

3.代码实现

  • 暴力解法
class Solution {
    public int[] sortedSquares(int[] nums) {
       //定义新数组nums_存放nums各元素平方后的值
       int []nums_sort = new int[nums.length];
       //逐个对应更新nums_sort各元素的值
       for(int i = 0; i < nums.length; i++) {
    	   nums_sort[i] = nums[i] * nums[i];
       }
       //调用Arrays类自带的sort方法对nums_sort进行排序
       Arrays.sort(nums_sort);
       return nums_sort;
    }
}
  • 双指针法
class Solution {
    public int[] sortedSquares(int[] nums) {
        //方法二:双指针
		//定义新数组nums_存放nums各元素平方后的值
		int []nums_sort = new int[nums.length];
		//nums_sort数组从大下标往前更新
		int k = nums.length - 1;
		//定义数组左右两端指针i、j
		//当 i == j 的时候也要将其更新到新数组,故循环终止条件为 i <= j
		//i、j左右指针的移动是有条件的,并不是每次循环都变,需要将其写在循环体中
		for(int i = 0, j = nums.length - 1; i <= j;) {
			//i、j指针指的哪个元素平方大就更新哪个指针的位置
			if(nums[i] * nums[i] > nums[j] * nums[j]) {
				nums_sort[k] = nums[i] * nums[i];
				//左端的i指针更新是向右移动
				i++;
				//每次循环都要更新数组下标k,每个位置依次更新
				k--;
				//当 i == j的时候无论更新 i 还是 j 效果都是一样的,故用else  
			} else {
				nums_sort[k] = nums[j] * nums[j];
				//右端j指针更新是向右移动
				j--;
				k--;
			}
		}
		return nums_sort;
    }
}

二、Leetcode 209.长度最小的子数组

1.题目

给定一个含有 n 个正整数的数组和一个正整数 target 。

找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl+1, ..., numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。

示例 1:

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

示例 2:

输入:target = 4, nums = [1,4,4] 输出:1

示例 3:

输入:target = 11, nums = [1,1,1,1,1,1,1,1] 输出:0

提示:

  • 1 <= target <= 109
  • 1 <= nums.length <= 105
  • 1 <= nums[i] <= 105

来源:力扣(LeetCode) 链接:leetcode.cn/problems/mi…

2.解题思路

  1. 方法一:暴力解法 两个for循环,然后不断的寻找符合条件的子序列
  2. 方法二:双指针法(滑动窗口)
  • 代码随想录网站动画展示(辅助理解) 在这里插入图片描述

  • 滑动窗口 (指针之间的区间) 是双指针的一种特例,可以称为左右指针,在任意时刻,只有一个指针运动,而另一个保持静止。滑动窗口一般用于解决特定的序列中符合条件的连续的子序列的问题。

  • 其本质思路在于:

    1. 初始化将滑动窗口压满,取得第一个滑动窗口的目标值
    2. 继续滑动窗口,每往前滑动一次,需要删除一个和添加一个元素,求最优的目标值
  • 在本题中实现滑动窗口,主要确定如下三点:

    1. 窗口内是什么? 窗口就是 满足其和 ≥ target 的长度最小的 连续 子数组。

    2. 如何移动窗口的起始位置? 窗口的起始位置如何移动:如果当前窗口的值大于target了,窗口就要向前移动了(也就是该缩小了)。

    3. 如何移动窗口的结束位置 j ? 窗口的结束位置如何移动:窗口的结束位置就是遍历数组的指针,也就是for循环里的索引。

3.代码实现

  • 暴力解法(仅能通过部分样例,会超时)
class Solution {
    public int minSubArrayLen(int target, int[] nums) {
      	//方法一:暴力解法
    	//最终结果result
    	int result = Integer.MAX_VALUE;
    	//子序列的的数值总和
    	int sum = 0;
    	//子序列的长度
    	int subL = 0;
    	//假设子序列起点位置为i,终点位置为j
    	for(int i = 0; i < nums.length; i++) {
    		//同一起点sum是可以累加的,不同起点不能累加
    		//每次更新起点后需要重给子序列总和sum初始化为0
    		sum = 0;
    		for(int j = i; j < nums.length; j++) {
    			sum += nums[j];
    			if(sum >= target) {
    				//数值下标从0开始
    				subL = j - i + 1;
    				result = Math.min(result, subL);
    				//由于我们是找符合条件最短的子序列,所以一旦符合条件就break
    				break;
    			}
    		}
    	}
    	//如果result没有被赋值的话,就返回0,说明没有符合条件的子序列
    	return result == Integer.MAX_VALUE ? 0 : result;
    }
}
  • 双指针(滑动窗口)(执行用时: 1 ms,内存消耗: 48.9 MB)
class Solution {
    public int minSubArrayLen(int target, int[] nums) {
        //方法二:滑动窗口
    	//最终结果result
    	int result = Integer.MAX_VALUE;
    	//滑动窗口内的元素集合长度subL
    	int subL = 0;
    	//滑动窗口内的元素总和
    	int sum = 0;
    	//滑动窗口的起始位置i
    	int i = 0;
    	//滑动窗口的终止位置j
    	for(int j = 0; j < nums.length; j++) {
    		sum += nums[j];
    		//当滑动窗口内的元素总和sum大于或者等于目标值值时进行各项指标更新操作
    		while(sum >= target) {
    			//数组下标从0开始,所以+1
    			//更新符合调节剂的滑动窗口区间长度
    			subL = j - i + 1;
    			//取长度最小的滑动窗口长度并赋值给结果result
    			result = Math.min(result, subL);
    			//由于滑动窗口起点右移,所以集合内元素的和要减去对应的原始起始下标值
    			sum = sum - nums[i];
    			//更新起点坐标,向右滑动一位
    			i++;
    		}
    	}
    	//如果结果数组nums所有元素加起来都小于target,则不会进入while循环,故以此为依据做判断result是否为0
    	return result == Integer.MAX_VALUE ? 0 : result;
    }
}

三、Leetcode 59.螺旋矩阵II

1.题目

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

示例 1: 在这里插入图片描述

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

示例 2:

输入:n = 1 输出:[[1]]

来源:力扣(LeetCode) 链接:leetcode.cn/problems/sp…

2.解题思路

  • 二分法的边界问题——循环不变量原则 当我们在一个区间里搜索时,我们要明确这个区间是 左闭右闭原则 [ left, right ] 还是 左闭右开原则 [ left, right ),这两个的区别就是包不包括right值。当我们确定了是哪一种原则,我们就根据哪一种来解决边界问题

  • 模拟顺时针画矩阵的过程:

    填充上行从左到右 填充右列从上到下 填充下行从右到左 填充左列从下到上 这里一圈下来,我们要画每四条边,这四条边怎么画,每画一条边都要坚持一致的左闭右开,这样这一圈才能按照统一的规则画下来。

3.代码实现

class Solution {
    public int[][] generateMatrix(int n) {
        int loop = 0;  // 控制循环次数
        int[][] res = new int[n][n];
        int start = 0;  // 每次循环的开始点(start, start)
        int count = 1;  // 定义填充数字
        int i, j;

        while (loop++ < n / 2) { // 判断边界后,loop从1开始
            // 模拟上侧从左到右
            for (j = start; j < n - loop; j++) {
                res[start][j] = count++;
            }

            // 模拟右侧从上到下
            for (i = start; i < n - loop; i++) {
                res[i][j] = count++;
            }

            // 模拟下侧从右到左
            for (; j >= loop; j--) {
                res[i][j] = count++;
            }

            // 模拟左侧从下到上
            for (; i >= loop; i--) {
                res[i][j] = count++;
            }
            start++;
        }

        if (n % 2 == 1) {
            res[start][start] = count;
        }

        return res;
    }
}

四、参考网站及博客

总结

  1. 我们在写二分法时,只需要有始有终的遵循着其中一个开闭原则就能思路清晰的解决边界问题。不要一开始是左闭右闭,后面又左闭右开了。