代码随想录算法训练营第二天| 977.有序数组的平方 ,209.长度最小的子数组 ,59.螺旋矩阵II ,总结
977.有序数组的平方
给你一个按 非递减顺序 排序的整数数组 nums
,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。
示例 1:
输入:nums = [-4,-1,0,3,10]
输出:[0,1,9,16,100]
解释:平方后,数组变为 [16,1,0,9,100]
排序后,数组变为 [0,1,9,16,100]
个人解题思路:
- 最初想到暴力平方再排序,这种办法的时间复杂度为O(nlogn)
- 根据提示使用双指针的办法,于是开始考虑快慢指针还是左右指针
- 因为这题要比较的是平方值,即最大值将会分布在左右两侧,故考虑使用左右指针
- 起初还想能不能不使用额外的数组空间,只在原数组处理,但会出现一个问题,若左指针的值较大,右指针的值不好存储的问题
- 最终还是使用额外的数组空间解题
- 代码:
class Solution {
public int[] sortedSquares(int[] nums) {
int[] nums_ = new int[nums.length];
int left = 0;
int right = nums.length - 1;
for (int i = nums_.length - 1; i >= 0; i--) {
if (nums[left] * nums[left] > nums[right] * nums[right]) {
nums_[i] = nums[left] * nums[left];
left++;
} else {
nums_[i] = nums[right] * nums[right];
right--;
}
}
return nums_;
}
}
题解方案:同样是两种方法(暴力+双指针法)
实现逻辑基本一致,此处不贴代码了。
209.长度最小的子数组
给定一个含有 n
个正整数的数组和一个正整数 target
。
找出该数组中满足其和 ≥ target
的长度最小的 连续子数组 [numsl, numsl+1, ..., numsr-1, numsr]
,并返回其长度**。**如果不存在符合条件的子数组,返回 0
。
示例 1:
输入:target = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3] 是该条件下的长度最小的子数组。
个人解题思路:
- 这道题在没学过双指针可能比较难想到,学过之后不难想到使用快慢指针包住一个区间,根据其中的sum与target比较
- 符合条件就比较最小长度并保存
- 整个过程走完,最终返回最小长度即可
代码:
class Solution {
public int minSubArrayLen(int target, int[] nums) {
int slow = 0;
int fast = 0;
int sum = nums[fast];
int min = nums.length + 1; //设置最小区间长度比数组长度大
while (slow < nums.length) {
if (sum >= target) {
min = Integer.min(fast - slow + 1,min);
sum -= nums[slow++];
} else if (fast < nums.length - 1) {
sum += nums[++fast];
} else break; //当快指针到头了,但快慢指针区间的sum值<target,就可以直接退出循环了
}
return min == nums.length + 1 ? 0 : min;
}
}
题解解析:
滑动窗口
- 通过不断调整快慢指针的位置,从而得到不同的子序列,子序列中找到最优解
class Solution {
// 滑动窗口
public int minSubArrayLen(int s, int[] nums) {
int left = 0;
int sum = 0;
int result = Integer.MAX_VALUE;
for (int right = 0; right < nums.length; right++) {
sum += nums[right];
while (sum >= s) {
result = Math.min(result, right - left + 1);
sum -= nums[left++];
}
}
return result == Integer.MAX_VALUE ? 0 : result;
}
}
对比自己的代码会发现,题解的思路要清晰一些, for循环中放置右区间(快指针)
- 若符合条件,通过while循环来不断缩小子序列
- 不符合条件则快指针往前扩大序列
59.螺旋矩阵II
给你一个正整数 n
,生成一个包含 1
到 n2
所有元素,且元素按顺时针顺序螺旋排列的 n x n
正方形矩阵 matrix
。
示例 1:
输入:n = 3
输出:[[1,2,3],[8,9,4],[7,6,5]]
个人思路:
- 使用直接法,使用多个if判断+循环遍历即可
- 但过程中会出现一个问题,if判断的摆放顺序较为固定导致会乱拐弯
- 想到使用一个变量来记录上一次方向,优先进行上一次方向的if判断
代码:
class Solution {
int j = 0;
int k = 0;int[][] nums = null;
public int[][] generateMatrix(int n) {
nums = new int[n][n];
int index = 0;
for (int i = 1; i <= n * n; i++) {
nums[j][k] = i;
// System.out.println("nums[" + j + "][" + k + "] = " + i);
if (choice(index,n))
continue;
if (k + 1 < n && nums[j][k + 1] == 0) {//第一个条件是防止数组越界,第二个条件判断是否赋值
k++;
index = 1;
} else if (j + 1 < n && nums[j + 1][k] == 0) {
j++;
index = 2;
} else if (k - 1 >= 0 && nums[j][k - 1] == 0) {
k--;
index = 3;
} else if (j - 1 >= 0 && nums[j - 1][k] == 0) {
j--;
index = 4;
}
}
return nums;
}
public boolean choice(int index,int n) {
switch (index) {
case 1:
if (k + 1 < n && nums[j][k + 1] == 0) {
k++;
index = 1;
return true;
}
break;
case 2:
if (j + 1 < n && nums[j + 1][k] == 0) {
j++;
index = 2;
return true;
}
break;
case 3:
if (k - 1 >= 0 && nums[j][k - 1] == 0) {
k--;
index = 3;
return true;
}
break;
case 4:
if (j - 1 >= 0 && nums[j - 1][k] == 0) {
j--;
index = 4;
return true;
}
break;
}
return false;
}
}
题解:
使用循环不变量原则,处理好行列遍历的边界问题,边界处理规则上保持一致,比如都是左闭右开区间
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xpDj1xA0-1672329757166)(C:\Users\耿飞扬\AppData\Roaming\Typora\typora-user-images\image-20221229230644756.png)]
- 就像这样,我们只需要一圈一圈处理就行
- 下一圈就更新起始位置即可
- 注意处理的层数为n/2,如果n为奇数,则需要再给中心位置赋值
代码:
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;
}
}
//了解题解后自己重写的代码
class Solution {
public int[][] generateMatrix(int n) {
int index = 1;
int [][] result = new int[n][n];
for (int i = 0; i < n/2; i++) {
for (int j = i; j < n-i-1; j++) {
result[i][j] = index++;
}
for (int j = i; j < n-i-1; j++) {
result[j][n-i-1] = index++;
}
for (int j = n-i-1; j > i; j--) {
result[n-i-1][j] = index++;
}
for (int j = n-i-1; j > i; j--) {
result[j][i] = index++;
}
}
if (n%2!=0)
result[n/2][n/2] = index;
return result;
}
}
总结
数组理论基础
- 数组是存放在连续内存空间的相同数据类型的集合
- 数组下标从0开始,内存地址连续
- 增删操作要移动其他元素
- 多维数组同样是连续地址
数组题型常用思想
二分法
- 左闭右开
- 左闭右闭
- 循环不变量/一致原则
双指针法
- 通过两个指针在一个for循环完成两个for的工作
- 快慢指针
- 左右指针
- 因题目情况而定采用不同的双指针
滑动窗口法
- 实质还是快慢指针,指针不回头,不停移动序列位置区间,更新结果集
- 滑动窗口的精妙之处在于根据当前子序列和大小的情况,不断调节子序列的起始位置。从而将O(n^2)的暴力解法降为O(n)
模拟行为
- 根据题意模拟行为
- 关键在于边界条件的处理
- 循环不变量原则,边界处理方式要保持一致性
数组题型常用思想
二分法
- 左闭右开
- 左闭右闭
- 循环不变量/一致原则
双指针法
- 通过两个指针在一个for循环完成两个for的工作
- 快慢指针
- 左右指针
- 因题目情况而定采用不同的双指针
滑动窗口法
- 实质还是快慢指针,指针不回头,不停移动序列位置区间,更新结果集
- 滑动窗口的精妙之处在于根据当前子序列和大小的情况,不断调节子序列的起始位置。从而将O(n^2)的暴力解法降为O(n)
模拟行为
- 根据题意模拟行为
- 关键在于边界条件的处理
- 循环不变量原则,边界处理方式要保持一致性