题目与题解
209.长度最小的子数组
题目链接:209.长度最小的子数组
代码随想录题解:209.长度最小的子数组
解题思路
利用滑动窗口实现,基本原理为:首先设置一个左右边界分别为left和right的滑动窗口,都初始化为0,同时记录窗口元素的和sum,用于比对和是否大于target。
用右边界逐一遍历数组元素,每遍历到一个新值,讲将其加入sum,如果此时sum大于等于target了,左边界就要开始收缩,逐渐前进。每前进一格,sum就要减去nums[left],相当于滑动窗口去掉了最左边的值,直到sum小于target为止,同时计算滑动窗口的大小,记录minLen。
class Solution {
public int minSubArrayLen(int target, int[] nums) {
int minLen = nums.length+1;
int left = 0, sum = 0;
for (int right = 0; right < nums.length; right++) {
sum += nums[right];
while (sum >= target) {
minLen = Math.min(minLen, right - left + 1);
sum -= nums[left++];
}
}
if (minLen > nums.length) return 0;
return minLen;
}
}
注意点
尽管for循环里面套了while循环,但是left边界最多也就遍历一遍数组元素,right也是遍历一遍数组元素,所以最高复杂度为O(n)。
另外,为了避免复杂化解题,滑动窗口的右边界最好设置为每次循环都前进一格,直到遇到题目要求的特殊情况,再对左边界进行处理和计算,会清晰很多。
904.水果成篮
题目链接:904.水果成篮
leetcode题解:904.水果成篮
解题思路
这题也属于滑动窗口的题目,比上题复杂一些,需要记录两个左边界,分别对应窗口中两种水果最新一次出现的位置,便于新品种出现时更新。
用type1和type2分别记录在滑动窗口中的水果种类,方便后序比对,初始化为-1。用pos1和pos2分别记录type1和type2水果最近一次出现的位置,同样初始化为-1。用count记录篮子中水果的数量,并用maxCount记录篮子中最多有多少水果。
从头开始遍历fruits,如果fruits[i]等于type1或者type2,即水果在当前窗口的两个品种里面,count加一,同时根据fruits[i]的具体值记录最新的pos1或者pos2。
如果fruits[i]不等于type1或者type2,有两种情况:一种是type1和type2还等于-1,需要用fruits[i]初始化一下;另一种就是新品种的水果出现了,此时需要先记录当前水果数量count是否比maxCount要大,更新maxCount,然后根据fruits[i-1]是type1还是type2,更新pos和type。假设fruits[i-1]是type1,窗口左边界更新为pos2+1,count更新为i - pos2,type2更新为fruits[i],pos2更新为i,如果fruits[i-1]是type2也是同理。
最后返回maxCount即可。
class Solution {
public int totalFruit(int[] fruits) {
if (fruits.length <= 2) return fruits.length;
int count = 0, maxCount = 0;
int type1 = -1, type2 = -1;
int pos1 = -1, pos2 = -1;
int i = 0;
for (int j = 0; j < fruits.length; j++) {
if (fruits[j] == type1 || fruits[j] == type2) {
if (fruits[j] == type1) pos1 = j;
else pos2 = j;
count++;
} else {
if (type1 == -1) {
type1 = fruits[j];
count++;
pos1 = j;
} else if (type2 == -1) {
type2 = fruits[j];
count++;
pos2 = j;
} else {
if (fruits[j-1] == type1) {
count = j - pos2;
pos2 = j;
type2 = fruits[j];
} else {
count = j - pos1;
pos1 = j;
type1 = fruits[j];
}
}
}
maxCount = Math.max(maxCount, count);
}
return maxCount;
}
}
注意点
这题多了一个左边界,难写了很多。一开始写的时候考虑不周,把type1或type2等于-1的情况写在了前面,导致碰到[0,0,1,1]这个测试用例就没有过。答案有用哈希表的算法,没看懂,就这样吧。
76.最小覆盖子串
题目链接:76.最小覆盖子串
代码随想录题解:76.最小覆盖子串
解题思路
这题够复杂,就算边看答案边抄也是看了好几遍才看懂,涉及到的步骤太多了,即使有想法也要想到如何用合适的数据结构去实现,抄一下官方答案的思路仅供学习,代码是选了一版能看懂的抄下来了。
可以用滑动窗口的思想解决这个问题。在滑动窗口类型的问题中都会有两个指针,一个用于「延伸」现有窗口的 right指针,和一个用于「收缩」窗口的 left 指针。在任意时刻,只有一个指针运动,而另一个保持静止。我们在 s 上滑动窗口,通过移动 right 指针不断扩张窗口。当窗口包含 t 全部所需的字符后,如果能收缩,我们就收缩窗口直到得到最小窗口。
如何判断当前的窗口包含所有 t 所需的字符呢?我们可以用一个哈希表tMap表示 t 中所有的字符以及它们的个数,用一个哈希表sMap动态维护窗口中所有的字符以及它们的个数,如果这个动态表中包含 t 的哈希表中的所有字符,并且对应的个数都不小于 t 的哈希表中各个字符的个数,那么当前的窗口是「可行」的。
具体实现的时候,除了要维护窗口左右边界的值,还需要用minLen记录最终的最小覆盖长度,用valid记录当前窗口的字符数量是否符合t的数量要求,用start记录获得最小窗口时其左边界的大小,方便最后返回s的子字符串。
class Solution {
public String minWindow(String s, String t) {
if (t.length() > s.length()) return "";
Map<Character, Integer> tMap = new HashMap<>();
for (int i = 0; i < t.length(); i++) {
char c = t.charAt(i);
tMap.put(c, tMap.getOrDefault(c, 0) + 1);
}
int left = 0, right = 0;
int minLen = s.length()+1;
int start = 0;
int valid = 0;
Map<Character, Integer> sMap = new HashMap<>();
while (right < s.length()) {
char c = s.charAt(right);
if (tMap.containsKey(c)) {
sMap.put(c, sMap.getOrDefault(c, 0) + 1);
if (sMap.get(c).equals(tMap.get(c))) valid++;
}
while (valid == tMap.size()) {
if (right - left + 1 < minLen) {
start = left;
minLen = right - left + 1;
}
char l = s.charAt(left);
if (tMap.containsKey(l)) {
sMap.put(l, sMap.get(l)-1);
if (sMap.get(l) < tMap.get(l)) valid--;
}
left++;
}
right++;
}
return minLen == s.length()+1 ? "": s.substring(start, start + minLen);
}
}
注意点
hard题,要是考到就算了。
59.螺旋矩阵II
题目链接:59.螺旋矩阵II
代码随想录题解:59.螺旋矩阵II
解题思路
因为做过一次,题目印象还是很深刻的。这里主要得想到每次如何去遍历当前圈的四个方向的值,以及跳出循环的时机。
事实上,因为要求的螺旋矩阵是正方形的,所以其四条边都是一样长,也就是说每条边每次可以按规律遍历相同的长度,写起来会容易很多。主要需要注意每次遍历的边界,下标顺序要写对,以及当n为奇数时需要额外计算一下中心的数值。
class Solution {
public int[][] generateMatrix(int n) {
int[][] matrix = new int[n][n];
if (n % 2 == 1) matrix[n/2][n/2] = n*n;
int i = 1;
for (int round = 0; round < n/2; round++) {
for(int j1 = round; j1 < n - round - 1; j1++) {
matrix[round][j1] = i++;
}
for(int j2 = round; j2 < n - round - 1; j2++) {
matrix[j2][n - round - 1] = i++;
}
for(int j3 = n - round - 1; j3 > round; j3--) {
matrix[n - round - 1][j3] = i++;
}
for(int j4 = n - round - 1; j4 > round; j4--) {
matrix[j4][round] = i++;
}
}
return matrix;
}
}
注意点
边界不要多写,下标要自己试一试,很容易写错行列位置。
54.螺旋矩阵
题目链接:54.螺旋矩阵
代码随想录题解:54.螺旋矩阵
解题思路
被上一题绕进去了,本来想通过一样的写法完成,但是发现不好把控循环停止的时机。所以最简单的方法还是按照官方的题解,设置上下左右四个边界,每次遍历每条边界所在边的同时更新边界的值,就绝对不会出错了。
class Solution {
public List<Integer> spiralOrder(int[][] matrix) {
List<Integer> list = new ArrayList<>();
if (matrix.length == 0) return list;
int up = 0, down = matrix.length - 1;
int left = 0, right = matrix[0].length - 1;
while (true) {
for (int i = left; i <= right; i++) list.add(matrix[up][i]);
if (++up > down) break;
for (int i = up; i <= down; i++) list.add(matrix[i][right]);
if (--right < left) break;
for (int i = right; i >= left; i--) list.add(matrix[down][i]);
if (--down < up) break;
for (int i = down; i >= up; i--) list.add(matrix[i][left]);
if (++left > right) break;
}
return list;
}
}
注意点
模拟过程就行。
今日收获
复习了一下滑动窗口法和螺旋矩阵的题目。
滑动窗口注意点:右边界不断扩展,左边界根据需求收缩,收缩的同时计算并记录所需窗口的大小、起始位置等参数。
螺旋矩阵注意点:根据边界的限制和矩阵的长宽规律,模拟并按顺序打印或赋值即可,要注意i下标和边界的位置需要符合要求。