今日题目:
目录
-
- 今日总结
- [Problem 1:有序数组平方 ⭐⭐⭐](#Problem 1:有序数组平方 ⭐⭐⭐)
- [Problem 2:滑动窗口法 【必会】](#Problem 2:滑动窗口法 【必会】)
-
- [LeetCode 209. 长度最小的子数组 Medium](#LeetCode 209. 长度最小的子数组 Medium)
- [LeetCode 76. 最小覆盖子串 Hard](#LeetCode 76. 最小覆盖子串 Hard)
- [Problem 3:螺旋矩阵 II 【还行】](#Problem 3:螺旋矩阵 II 【还行】)
今日总结
今天继续做的《数组》系列的题目,其中最重要的是学习滑动窗口法,学会了滑动窗口的代码框架思路,并借助两道题目来学习利用这个框架代码来解决具体问题,这是重点要掌握的。
除此之外,还解决了另外两道题,其中"有序数组平方"这道题锻炼了我对双指针法的灵活运行,学习到了在使用双指针时,选择一个好的行进方向可以避免对很多临界情况的判断,从而减少代码复杂度避免出错。
Problem 1:有序数组平方 ⭐⭐⭐
这个题考验对"双指针"的灵活运用,不能死板。
最开始的做法是先找到第一个非负整数和最大负数,然后从中间向两边逐个比较得出结果,但这样的缺点是需要判断大量的临界条件,这很容易就产生错误,所以应该换一个思路。
在这里需要转换一个思路,不要从中间向两边走,而是从两边向中间走,从而利用双指针。这样在 while 中判断终止条件就只需要是 left 和 right 有没有碰上了。
这个题目最简单的方法就是,让 left 指向 0,right 指向末尾,然后两个指针逐渐向中间移动,每次将最大的放到结果集中:
java
class Solution {
public int[] sortedSquares(int[] nums) {
List<Integer> result = new ArrayList<>();
int left = 0, right = nums.length - 1;
while (!(left > right)) { // 直到 left 和 right 碰上
int leftValue = nums[left] * nums[left];
int rightValue = nums[right] * nums[right];
if (leftValue < rightValue) {
result.add(rightValue);
right--;
} else {
result.add(leftValue);
left++;
}
}
Collections.reverse(result);
return result.stream().mapToInt(Integer::valueOf).toArray();
}
}
因为这里是每次让平方值大的移动,当 left 或 right 走到中间绝对值是最小值的数字后就不会再移动了,所以不用担心 left 会超过负数范围或者 right 超过正数范围。我的一次 commit 就因为担心这个而多写了很多判断条件。
总结的经验是,利用好题目的特性和性质,选择好双指针的移动方向,尽量减少对临界条件的判断,从而减小代码复杂度。
Problem 2:滑动窗口法 【必会】
这个方法是个通用的方法,用于解决子串问题。
参考 labuladong 的讲解,滑动窗口的代码框架如下:
cpp
int left = 0, right = 0;
while (left < right && right < s.size()) {
// 增大窗口
window.add(s[right]);
right++;
// 更新相关数据 ...
...
while (window needs shrink) {
// 缩小窗口(在这之前可能需要记录一下解)
window.remove(s[left]);
left++;
// 更新相关数据 ...
...
}
}
具体的讲解可以参考 labuladong 的文章。这里使用这个思路来解决 LeetCode 中 209 和 76 两个题目。重要是通过这几个题目来理解如何利用上面这个滑动窗口框架来解决具体的问题。
LeetCode 209. 长度最小的子数组 Medium
这个题可以经典地套用上面的框架,解题代码如下:
java
class Solution {
public int minSubArrayLen(int target, int[] nums) {
int low = 0, high = 0;
int sum = 0;
int minLen = nums.length;
int curLen = 0;
boolean found = false;
while (high < nums.length) {
// 扩大右窗口
sum += nums[high];
high++;
// 更新相关数据
curLen++;
while (sum >= target) {
// 记录解
found = true;
if (curLen < minLen) {
minLen = curLen;
}
// 收缩左窗口
sum -= nums[low];
low++;
// 更新相关数据
curLen--;
}
}
return found? minLen: 0;
}
}
LeetCode 76. 最小覆盖子串 Hard
这个题虽然很难,但仍然能够套用上面介绍的滑动窗口框架,需要额外处理的就是一些条件的检查等问题。
通过这个题,可以很好地锻炼如何套用之前滑动窗口框架代码。
关于这个题的讲解,可以参考 labuladong 文章中的讲解。
Problem 3:螺旋矩阵 II 【还行】
这个题目还行,比较偏技巧,抓住问题的要点:"方向的改变是固定的,也就是向右走的下一个方向一定是向下走",所以我们需要确定好什么时候发生方向的转变。
这个题目在 LeetCode 中题解所介绍的有点麻烦,这里我的关键实现是实现一个 next()
函数,这个函数根据当前的方向和位置,确定出下一个走到的位置。确定下一个位置的思路是:判断一下当前方向能不能继续向下走,不能走的话就转变方向。
代码实现:
java
class Solution {
int row; // 当前位置的行号
int col; // 当前位置的列号
int direction; // 当前移动的方向
public int[][] generateMatrix(int n) {
row = 0;
col = 0;
direction = 1;
int[][] matrix = new int[n][n];
int square = n * n;
for (int i = 1; i <= square; i++) {
matrix[row][col] = i;
if (i != square) {
next(matrix, n);
}
}
return matrix;
}
// 根据当前的方向和位置,确定下一个移动到的位置
private boolean next(int[][] matrix, int n) {
if (direction == 1) { // 向右走
if (col + 1 < n && matrix[row][col + 1] == 0) { // 判断是否能继续按这个方向走
col += 1;
return true;
} else { // 不能继续的话,就转变方向
direction = 2;
return next(matrix, n);
}
}
if (direction == 2) { // 向下走
if (row + 1 < n && matrix[row + 1][col] == 0) {
row += 1;
return true;
} else {
direction = 3;
return next(matrix, n);
}
}
if (direction == 3) { // 向左走
if (col - 1 >= 0 && matrix[row][col - 1] == 0) {
col -= 1;
return true;
} else {
direction = 4;
return next(matrix, n);
}
}
if (direction == 4) { // 向上走
if (row - 1 >= 0 && matrix[row - 1][col] == 0) {
row -= 1;
return true;
} else {
direction = 1;
return next(matrix, n);
}
}
return false;
}
}
- 关键就是这里的
next()
函数的实现