977.有序数组的平方
解题思路
- 可用暴力法,将所有值都计算出来,进行排序 可用双指针,由两侧向中间靠拢,两边大,中间小
代码
- 暴力法
class Solution {
public int[] sortedSquares(int[] nums) {
// 对数组中的每一个元素进行平方操作
for (int i = 0; i < nums.length; i++) {
nums[i] = nums[i] * nums[i];
}
// 对平方后的数组进行排序
Arrays.sort(nums);
// 返回排序后的数组
return nums;
}
}
/*
解题思路:
1. 首先对数组中的每一个元素进行平方操作,即将原数组中的每个数值乘以自身。
2. 然后对平方后的数组进行排序,可以使用Arrays类的sort方法来完成排序。
3. 最后返回排序后的数组作为结果。
时间复杂度分析:
- 平方操作的时间复杂度为O(n),其中n为数组长度。
- 排序操作的时间复杂度为O(nlogn)。
总的时间复杂度为O(nlogn)。
空间复杂度分析:
- 原地修改数组,不需要额外的空间。
- 因此,空间复杂度为O(1)。
*/
- 双指针
class Solution {
public int[] sortedSquares(int[] nums) {
// 创建一个和原数组长度相同的结果数组
int size = nums.length;
int[] result = new int[size];
// 使用双指针技巧,分别从数组的两端开始遍历
// 因为原数组中的负数部分是递减的,正数部分是递增的
// 所以平方后,负数部分会变成递增的,正数部分也是递增的
// 可以通过比较平方值大小来确定放入结果数组的位置
// 定义左指针和右指针
int left = 0; // 左指针指向原数组的起始位置
int right = nums.length - 1; // 右指针指向原数组的末尾位置
// 从两端同时遍历到中间位置
while (left <= right) {
// 获取左指针对应元素的平方值
int leftSquare = nums[left] * nums[left];
// 获取右指针对应元素的平方值
int rightSquare = nums[right] * nums[right];
// 比较平方值大小,将较大的值放入结果数组,并移动对应的指针
if (leftSquare > rightSquare) {
result[size - 1] = leftSquare;
size--;
left++;
} else {
result[size - 1] = rightSquare;
size--;
right--;
}
}
// 返回结果数组
return result;
}
}
/*
解题思路:
首先对数组中的每一个元素进行平方操作,即将原数组中的每个数值乘以自身。
然后对平方后的数组进行排序,可以使用Arrays类的sort方法来完成排序。
最后返回排序后的数组作为结果。
时间复杂度分析:
平方操作的时间复杂度为O(n),其中n为数组长度。
排序操作的时间复杂度为O(nlogn)。
总的时间复杂度为O(nlogn)。
空间复杂度分析:
原地修改数组,不需要额外的空间。
因此,空间复杂度为O(1)。
*/
209.长度最小的子数组
解题思路
- 可用暴力法(力扣改版后超时)或者双指针法进行求解
代码
- 暴力法
class Solution {
public int minSubArrayLen(int target, int[] nums) {
List<Integer> result = new ArrayList<>(); // 创建一个存储子数组长度的列表
boolean flag = false; // 标记是否找到满足条件的子数组
for (int i = 0; i < nums.length; i++) { // 遍历数组中的每个元素作为子数组的起始点
int sum = 0; // 计算子数组的和
for (int j = i; j < nums.length; j++) { // 在起始点后继续遍历数组中的元素,构造子数组
sum += nums[j]; // 累加当前元素到子数组和中
if (sum >= target) { // 如果子数组和大于等于目标值
int len = j - i + 1; // 计算子数组的长度
result.add(len); // 将子数组长度添加到结果列表中
flag = true; // 标记已找到满足条件的子数组
break; // 跳出内层循环,继续寻找下一个起始点
}
}
}
if (flag == false) { // 如果未找到满足条件的子数组
return 0; // 返回0
}
Collections.sort(result); // 对结果列表进行排序
return result.get(0); // 返回最小长度的子数组长度
}
}
/*
解题思路:
遍历数组中的每一个元素作为子数组的起始点。
在起始点后继续遍历数组中的元素,构造子数组,并计算子数组的和。
如果子数组的和大于等于目标值,则记录当前子数组的长度,并将其添加到结果列表中。
最后,如果找到了满足条件的子数组,对结果列表进行排序,并返回最小长度的子数组长度;如果没有找到满足条件的子数组,则返回0。
时间复杂度分析:
两层嵌套循环,每个元素都可能作为子数组的起始点,因此总的时间复杂度为O(n^2),其中n为数组长度。
排序操作的时间复杂度为O(klogk),其中k为结果列表的长度。
最坏情况下,结果列表的长度为n,所以总的时间复杂度为O(n^2 + nlogn)。
空间复杂度为O(1),除了结果列表外,没有使用额外的空间。
注意事项:
暴力法通常在面对简单问题时使用,但它的时间复杂度较高,在处理大规模数据时效率低下。可以考虑其他更优化的解法。
*/
- 滑动窗口
class Solution {
public int minSubArrayLen(int target, int[] nums) {
// 创建一个结果列表,用于存储满足条件的子数组长度
List<Integer> result = new ArrayList<>();
// 标记是否存在满足条件的子数组
boolean flag = false;
// 初始化窗口的起始位置和窗口内元素的和
int sum = 0;
int i = 0;
// 遍历数组中的每个元素
for (int j = 0; j < nums.length; j++) {
// 将当前元素加入窗口内
sum += nums[j];
// 当窗口内元素的和大于等于目标值时,开始缩小窗口
while (sum >= target) {
// 计算当前子数组的长度并加入结果列表
int len = j - i + 1;
result.add(len);
// 更新标志位,表示存在满足条件的子数组
flag = true;
// 缩小窗口,将窗口左侧元素移出窗口
sum -= nums[i];
i++;
}
}
// 如果不存在满足条件的子数组,则返回0
if (flag == false) {
return 0;
}
// 对结果列表进行排序
Collections.sort(result);
// 返回最小长度的子数组长度
return result.get(0);
}
}
/*
解题思路:
使用滑动窗口的思想,通过维护窗口的起始位置和窗口内元素的和来找到满足条件的子数组长度。
初始化窗口的起始位置为0,窗口内元素的和为0。
遍历数组中的每个元素,将当前元素加入窗口内。
当窗口内元素的和大于等于目标值时,开始缩小窗口:
计算当前子数组的长度并加入结果列表。
更新标志位,表示存在满足条件的子数组。
缩小窗口,将窗口左侧元素移出窗口。
如果不存在满足条件的子数组,则返回0。
对结果列表进行排序,返回最小长度的子数组长度作为结果。
时间复杂度分析:
使用了双指针遍历整个数组,时间复杂度为O(n),其中n为数组长度。
对结果列表进行排序的时间复杂度为O(mlogm),其中m为结果列表的长度。
总的时间复杂度为O(n + mlogm)。
空间复杂度分析:
创建了一个结果列表,最坏情况下需要存储n个元素,因此空间复杂度为O(n)。
*/
59.螺旋矩阵II
解题思路
- 控制循环不变量,左闭右开,一圈圈来
代码
- 逻辑思考:循环不变量原则
class Solution {
public int[][] generateMatrix(int n) {
// 创建一个n*n的二维数组作为结果
int[][] result = new int[n][n];
// 循环次数,每次循环覆盖矩阵的一圈
int loop = n / 2;
// 矩阵左上角的起始坐标
int startx = 0;
int starty = 0;
// 每次循环需要填入的数字的偏移量
int offset = 1;
// 当前要填入的数字
int count = 1;
// 循环变量
int i = 0, j = 0;
// 每次循环填入一圈数字
while (loop-- > 0) {
// 填入当前圈的上边
for (j = starty; j < n - offset; j++) {
result[startx][j] = count++;
}
// 填入当前圈的右边
for (i = startx; i < n - offset; i++) {
result[i][j] = count++;
}
// 填入当前圈的下边
for (; j > starty; j--) {
result[i][j] = count++;
}
// 填入当前圈的左边
for (; i > startx; i--) {
result[i][j] = count++;
}
// 更新起始坐标和偏移量
startx++;
starty++;
offset++;
}
// 对于奇数长度的矩阵,需要填入最中间的数字
if (n % 2 == 1) {
result[n / 2][n / 2] = count;
}
// 返回结果矩阵
return result;
}
}
/*
解题思路:
根据循环不变量原则,每一次循环填入一个圈的数字。
循环的次数为n/2,因为每一次循环都会覆盖掉4个边界上的数字,所以只需要循环n/2次即可完成填入整个矩阵的操作。
每一次循环都是按照顺时针方向填入当前圈的数字,依次填入上边、右边、下边和左边,直到回到起始位置。
每一次循环都会更新起始坐标和偏移量,保证下一次循环填入的是内层圈的数字。
对于奇数长度的矩阵,最中间的位置需要特殊处理,直接填入最后一个数字。
时间复杂度分析:
矩阵中共有n^2个元素,每个元素只被访问和修改一次,所以时间复杂度为O(n^2)。
空间复杂度分析:
使用了一个n*n的二维数组存储结果,所以空间复杂度为O(n^2)。
*/