1. 977.有序数组的平方
1.1 思路
- 数组A是有序的 平方的最大值是在数组两端—>左右设置两指针,一个指向开头,一个指向末尾,向中间靠拢
- 同时,定义一个新数组res
- 长度和A数组一样大,存入平方后数组
- 设置一个指针 指向新数组res的末尾,依次往起始方向移动
- 数组左右两端依次比较,谁大存谁,并且指针向中间移动
1.2 双指针解法
class Solution {
//双指针(左右指针)
public int[] sortedSquares(int[] nums) {
int l = 0;
int r = nums.length - 1;
//定义一个结果数组 长度与旧数组一样
int[] res = new int[nums.length];
//定义一个新指针,指向结果数组的末尾,之后从末尾开始减
int j = nums.length - 1;
while (l <= r) {
//旧数组左右两端比较,最大的值存入新数组,并将指针移动
if (nums[l] * nums[l] > nums[r] * nums[r]) {
/*res[j]=nums[l]*nums[l];
j--;
l++;*/
//简写为
res[j--] = nums[l] * nums[l++];
} else {
res[j--] = nums[r] * nums[r--];
}
}
return res;
}
}
2. 209.长度最小的子数组 M
2.1 题目
给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的 连续 子数组,并返回其长度。如果不存在符合条件的子数组,返回 0。
2.2 暴力解法
思路
套用两层for循环进行遍历暴力求解
class Solution {
public int minSubArrayLen(int target, int[] nums) {
//定义最终长度
int res = Integer.MAX_VALUE;
//定义子序列和
int sum=0;
//定义子序列长度
int subLength = 0;
//两个for循环遍历把所有可能性都出来进行比较
for (int i = 0; i < nums.length; i++) {
for (int j = i; j < nums.length; j++) {
sum += nums[j];//子序列求和
//比较
if (sum >= target) {
subLength = j - i + 1;//得到子序列长度
//res与subLength比较,将最小值赋值给res
res = res < subLength ? res : subLength;
break;//目的是为了找符合条件的最短子序列1,一但符合就break,后面就不用再找了,因为如果还有,长度就增加了
}
}
}
// result没有被赋值的话,就返回0,说明没有符合条件的子序列
return res == Integer.MAX_VALUE ? 0 : res;
}
}
- 时间复杂度:O(n^2) 空间复杂度:O(1)
2.3 滑动窗口(双指针变形)
思想
滑动窗口,就是不断的调节子序列的起始位置和终止位置,从而得出我们要想的结果
固定滑动窗口的终止位置,根据当前子序列的情况调节子序列的起始位置,如果当前窗口值满足条件,改变子序列的起始位置。
如果只用一个for循环来表示 滑动窗口的起始位置,那么如何遍历剩下的终止位置?此时难免再次陷入 暴力解法的怪圈。
所以 只用一个for循环,那么这个循环的索引,一定是表示滑动窗口的终止位置
实现滑动窗口,要考虑以下三点:
-
窗口内是什么?
- 窗口就是 满足其和 ≥ s 的长度最小的 连续 子数组。
-
如何移动窗口的起始位置?
- 如果当前窗口的值大于s了,窗口就要向前移动了(也就是该缩小了)。
-
如何移动窗口的结束位置?
- 窗口的结束位置如何移动:窗口的结束位置就是遍历数组的指针,也就是for循环里的索引。
-
两个指针都能固定住并且一个指针能固定住,另一个指针还能连续变化。
-
先做加法 后做减法
class Solution {
public int minSubArrayLen(int target, int[] nums) {
int left=0;
int sum=0;//子序列和
int res=Integer.MAX_VALUE;
//遍历终止位置
for (int right = 0; right < nums.length; right++) {
sum+=nums[right]; //先做加法
//窗口内的值大于了目标值了,就要开始移动窗口起始位置而了
//这里是while不是if,要移动起始位置,让窗口滑动
while (sum>=target) {
res=Math.min(res,right-left+1);//选择最小长度
// res=res<(right-left+1)?res:(right-left+1);
sum-=nums[left++]; //后做减法
}
}
// 如果result没有被赋值的话,就返回0,说明没有符合条件的子序列
return res==Integer.MAX_VALUE?0:res;
}
}
- 时间复杂度:O(n)
- 空间复杂度:O(1)
注意: ✏️时间复杂度问题
不要以为for里放一个while就以为是O(n^2), 主要是看每一个元素被操作的次数,每个元素在滑动窗后进来操作一次,出去操作一次,每个元素都是被操作两次,所以时间复杂度是 2 × n 也就是==O(n)==。
3. 59.螺旋矩阵II M
3.1 题目
给你一个正整数 n
,生成一个包含 1
到 n²
所有元素,且元素按顺时针顺序螺旋排列的 n x n
正方形矩阵 matrix
。
本题关键还是在转圈的逻辑,在二分搜索中提到的区间定义,在这里又用上了。
3.2 思路
要坚持循环不变量原则。填充顺序:上行从左到右、右列从上到下、下行从右到左、左列从下到上。
此题要添加的变量还蛮多的
3.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开始;比如n为4,n/2=2,说明转loop=2圈
// 模拟上侧从左到右 列要变换 所以改变j
//通过控制圈数来控制边界
//第一次while后loop为1 假如n=4,j<3
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++;
}
// 如果n为奇数,需要单独给矩阵中间赋值
if (n % 2 == 1) {
res[start][start] = count;
}
return res;
}
}
3.4 方法二(模拟法,设定边界,代码简短清晰)
思路:
生成一个 n×n 空矩阵 mat,随后模拟整个向内环绕的填入过程:
- 定义当前左右上下边界 l,r,t,b,初始值
num = 1
,迭代终止值tar = n * n
; - 当
num <= tar
时,始终按照 从左到右 从上到下 从右到左 从下到上 填入顺序循环,每次填入后: - 执行
num += 1
:得到下一个需要填入的数字; - 更新边界:例如从左到右填完后,上边界
t += 1
,相当于上边界向内缩 1。 - 使用
num <= tar
而不是l < r || t < b
作为迭代条件,是为了解决当n为奇数时,矩阵中心数字无法在迭代过程中被填充的问题。 - 最终返回 mat 即可。
class Solution {
public int[][] generateMatrix(int n) {
int l = 0, r = n - 1, t = 0, b = n - 1;
int[][] res = new int[n][n];
int count = 1, tar = n * n;
while (count <= tar) {
//上侧从左往右
for (int i = l; i <= r; i++) {
res[t][i] = count++;
}
t++;//这里t++了,右侧此时遍历的时候上边界已经+1了,再次遍历不会覆盖前面的数
//右列从上到下 上t下b
for (int i = t; i <= b; i++) {
res[i][r] = count++;
}
r--;
//下侧从右往左 右r左l
for (int i = r; i >= l; i--) {
res[b][i] = count++;
}
b--;
//左侧从下往上 下b上t
for (int i = b; i >= t; i--) {
res[i][l] = count++;
}
l++;
}
return res;
}
- for循环里写的时候是有规律的,代码中已添加注释了
至此,数组部分已经完结,但是对应数组部分的方法仍需要进一步练题深化,目前来说学会了二分查找,双指针法,滑动窗口,模拟法,后续仍需要多加练习!