977. 有序数组的平方
题目建议 : 本题关键在于理解双指针思想题目链接:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
文章讲解:代码随想录
视频讲解: 双指针法经典题目 | LeetCode:977.有序数组的平方_哔哩哔哩_bilibili
重点:相向双指针+非倒序数组最左和最右的值的平方为较大的
public static int[] sortedSquares(int[] nums) {
// 相向双指针
int left = 0;
int right = nums.length - 1;
int[] newArr = new int[nums.length];
int newArrIndex = nums.length - 1;
while (left <= right) {
// 如果左指针的值的平方小于右指针的,更新新数组为右指针的值
if (nums[left] * nums[left] < nums[right] * nums[right]) {
newArr[newArrIndex--] = nums[right] * nums[right];
right--;
// 如果右指针的值的平方小于等于左指针的,更新新数组为左指针的值
} else {
newArr[newArrIndex--] = nums[left] * nums[left];
left++;
}
}
return newArr;
}
209.长度最小的子数组
题目建议: 本题关键在于理解滑动窗口,这个滑动窗口看文字讲解 还挺难理解的,建议大家先看视频讲解。 拓展题目可以先不做。
题目链接:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
文章讲解:代码随想录
视频讲解:拿下滑动窗口! | LeetCode 209 长度最小的子数组_哔哩哔哩_bilibili
重点:滑动窗口,for循环的变量究竟是窗口的起始位置还是终止位置
想法:
1. for循环的变量应该是窗口的终止位置,如果是起始位置的话,相当于还是终止位置不断变化,遍历完所有元素,跟暴力破解是没区别的。
2. 既然是窗口的终止位置,我们只需要先固定好起始位置,循环改变窗口的终止位置,一旦窗口中的元素的和大于等于target,我们就可以开始移动起始位置来不断缩小窗口的大小,期间不断更新我们所记录的最小长度。如果期间窗口中的元素又小于target了,我们就移动终止位置,扩大窗口大小,再接着判断。
public static int minSubArrayLen(int target, int[] nums) {
int result = Integer.MAX_VALUE;
int currentWindowSize;
int sum = 0;
int start = 0;
// 移动终止位置
for (int end = 0; end < nums.length; end++) {
sum += nums[end];
// 不断缩小窗口大小,来查找是否又更小的数组满足条件
while (sum >= target) {
currentWindowSize = end - start + 1;
result = Math.min(result, currentWindowSize);
// 因为起始位置变化了,所以sum也要变小
sum -= nums[start];
start++;
}
}
return result == Integer.MAX_VALUE ? 0 : result;
}
59.螺旋矩阵II
题目建议: 本题关键还是在转圈的逻辑,在二分搜索中提到的区间定义,在这里又用上了。
题目链接:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
文章讲解:代码随想录
视频讲解:一入循环深似海 | LeetCode:59.螺旋矩阵II_哔哩哔哩_bilibili
重点:循环不变量原则,处理每一条边的时候,坚持左闭右开或者左闭右闭,从始至终都坚持一个
想法:坚持左闭右开原则,然后顶边->右边->底边->左边依次赋值,一圈赋值完毕后,往圈中心收缩。如果n为奇数,则需手动填写圈中心的值
public static int[][] generateMatrix(int n) {
int[][] nums = new int[n][n];
int startX = 0;
int startY = 0;
// 控制循环一轮之后,要往圈中心缩紧
int offset = 1;
int count = 1;
// 要走多少圈
int loop = n / 2;
int i;
int j;
while (loop-- > 0) {
// 处理顶上的边
for (j = startY; j < n - offset; j++) {
nums[startX][j] = count++;
}
// 处理右边的边
for (i = startX; i < n - offset; i++) {
nums[i][j] = count++;
}
// 处理底下的边
for (; j > startY; j--) {
nums[i][j] = count++;
}
// 处理左边的边
for (; i > startX; i--) {
nums[i][j] = count++;
}
// 往圈中心缩小一圈
offset++;
startX++;
startY++;
}
// 如果为奇数,也就是最后会剩下最中心的没有填,直接填上即可
if (n % 2 == 1) {
nums[startX][startY] = count;
}
return nums;
}
数组总结
在内存中的存储方式:数组是存放在连续内存空间上的相同类型数据的集合。
需要两点注意的是
- 数组下标都是从0开始的。
- 数组内存空间的地址是连续的。
正是因为数组的在内存空间的地址是连续的,所以我们在删除或者增添元素的时候,就难免要移动其他元素的地址。
数组的元素是不能删的,只能从后往前覆盖。
那么二维数组直接上图,大家应该就知道怎么回事了:
那么二维数组在内存的空间地址是连续的么?
我们来举一个Java的例子,例如:
int[][] rating = new int[3][4];
, 这个二维数组在内存空间可不是一个3*4
的连续地址空间看了下图,就应该明白了:
所以Java的二维数组在内存中不是
3*4
的连续地址空间,而是四条连续的地址空间组成!数组经典题目
二分法
坚持循环不变量原则,左闭右闭或者左闭右开
双指针法
双指针法(快慢指针法):通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。
滑动窗口
滑动窗口的精妙之处在于根据当前子序列和大小的情况,不断调节子序列的起始位置。从而将O(n^2)的暴力解法降为O(n)。
模拟行为
相信大家有遇到过这种情况: 感觉题目的边界调节超多,一波接着一波的判断,找边界,拆了东墙补西墙,好不容易运行通过了,代码写的十分冗余,毫无章法,其实真正解决题目的代码都是简洁的,或者有原则性的(坚持循环不变量原则),大家可以在这道题目中体会到这一点。
数组思维导图