76. 最小覆盖子串
方法一:滑动窗口
思路
哈希表tFreq
记录字符串t各个字符出现的频数,维护一个滑动窗口,若窗口内的字符出现的频数都大于等于需要的频数,则窗口对应的子串是符合要求的。
窗口移动逻辑
移动右边界寻找符合要求的子串,当找到符合要求的子串后,停止移动右边界,因为此时继续移动右边界得到的子串肯定也是符合要求的,但是因为长度会增加,所以肯定不是最小子串;
此时开始移动左边界,因为移动左边界后得到的子串也可能满足要求,且长度更短;
移动左边界直到当前窗口不满足要求,此时停止移动左边界,因为当前都不符合要求,继续缩短肯定也不符合要求;此时改为移动右边界。
判断窗口内子串是否符合要求
用一个哈希表winFreq
记录当前子串内各字符的频数,用一个数count
来表示当前符合条件的字符数量,当count
等于tFreq
时,说明所有字符都满足要求,此时当前子串就是符合要求的。
注意修改count
的条件,只有当winFreq[s[right]] == tFreq[s[right]]
时才修改。
- 时间复杂度: O ( C n + m ) O(Cn+m) O(Cn+m),C为t的字符集的数量,n为s的长度,m为t的长度。因为遍历s的时候,每一步都需要遍历一次字符集查看当前字符是否在字符集中,所以有一项 C n Cn Cn。
- 空间复杂度: O ( C ) O(C) O(C)。需要哈希表存储字符集的频数。
C++代码
class Solution {
public:
string minWindow(string s, string t) {
int left = 0;
int right = 0;
int count = 0;
int size = s.size();
int start = 0;
int len = INT_MAX;
unordered_map<char, int> tFreq; // t中各个字母出现的频数
unordered_map<char, int> winFreq; // 滑动窗口中各个字母出现的频数
// 统计t中各个字母出现的频数
for (char& c : t) ++tFreq[c];
int tFreqSize = tFreq.size();
while (right < size) {
// 移动右边界寻找可行解
if (tFreq.count(s[right]) != 0) {
++winFreq[s[right]];
if (winFreq[s[right]] == tFreq[s[right]]) {
++count;
}
}
// 只要count==tFreqSize说明当前滑动窗口内的子串是符合条件的
// 移动左边界寻找最优解,直到窗口内子串不符合条件再继续移动右边界寻找可行解
while (count == tFreqSize) {
if (len > right - left + 1) {
start = left;
len = right - left + 1;
}
if (tFreq.count(s[left]) != 0) {
if (tFreq[s[left]] == winFreq[s[left]]) {
--count;
}
--winFreq[s[left]];
}
++left;
}
++right;
}
return len == INT_MAX ? "" : s.substr(start, len);
}
};
看完讲解的思考
左右窗口不要同时移动,一次只移动一边,搞清楚移动左右边界的条件以及目的。
一开始总想着移动边界时一步到位,想着移动左边界要直接移动到下个满足条件的子串,搞得代码逻辑十分混乱,但其实每次只需移动一步就够了。
滑动窗口题目的要点:
- 左右边界不要同时移动。
- 边界一次只移动一步。
- 移动左右边界的条件逻辑。
- 移动右边界是为了找可行解,移动左边界是为了找最优解。
代码实现遇到的问题
- 没有理清楚边界移动的逻辑
- 判断窗口内子串是否符合条件时处理得过于复杂
59. 螺旋矩阵II
方法一:模拟1
思路
模拟顺时针螺旋填入数字的过程,设置变量state
作为当前的方向,1、2、3、4分别表示方向上下左右,每次走的时候判断是否已经到头(边界或者已填的部分),没到的话就继续沿着当前方向填,到头的话就改变方向。
- 时间复杂度: O ( n 2 ) O(n^2) O(n2)
- 空间复杂度: O ( 1 ) O(1) O(1)
C++代码
class Solution {
public:
vector<vector<int>> generateMatrix(int n) {
vector<vector<int>> res(n, vector<int>(n, -1));
int state = 4; // 1-上 2-下 3-左 4-右
int x = 0;
int y = 0;
for (int i = 1; i <= n * n; ++i) {
res[x][y] = i;
if (state == 1) {
if (res[x - 1][y] == -1) x -= 1;
else {
y += 1;
state = 4;
}
} else if (state == 2) {
if (x + 1 < n && res[x + 1][y] == -1) x += 1;
else {
y -= 1;
state = 3;
}
} else if (state == 3) {
if (y - 1 >= 0 && res[x][y - 1] == -1) y -= 1;
else {
x -= 1;
state = 1;
}
} else if (state == 4) {
if (y + 1 < n && res[x][y + 1] == -1) y += 1;
else {
x += 1;
state = 2;
}
}
}
return res;
}
};
方法二:模拟2
思路
直接用4个for循环以此填入矩阵每圈的四条边。
- 时间复杂度: O ( n 2 ) O(n^2) O(n2)
- 空间复杂度: O ( 1 ) O(1) O(1)
C++代码
class Solution {
public:
vector<vector<int>> generateMatrix(int n) {
int left = 0;
int right = n - 1;
int top = 0;
int down = n - 1;
int count = 1;
vector<vector<int>> res(n, vector<int>(n, -1));
while (count <= n * n) {
for (int i = left; i <= right; ++i) res[top][i] = count++;
++top;
for (int i = top; i <= down; ++i) res[i][right] = count++;
--right;
for (int i = right; i >= left; --i) res[down][i] = count++;
--down;
for (int i = down; i >= top; --i) res[i][left] = count++;
++left;
}
return res;
}
};
看完讲解的思考
无
代码实现遇到的问题
无
54. 螺旋矩阵
方法一:模拟
思路
做法与上一题大同小异,不同的地方在于结束循环的条件。
- 时间复杂度: O ( n 2 ) O(n^2) O(n2)
- 空间复杂度: O ( 1 ) O(1) O(1)
C++代码
class Solution {
public:
vector<int> spiralOrder(vector<vector<int>>& matrix) {
int row = matrix.size();
int column = matrix[0].size();
int left = 0;
int right = column - 1;
int top = 0;
int down = row - 1;
vector<int> res;
while (res.size() != row * column) {
for (int i = left; i <= right; ++i) res.push_back(matrix[top][i]);
++top;
for (int i = top; i <= down; ++i) res.push_back(matrix[i][right]);
--right;
if (res.size() == row * column) break;
else {
for (int i = right; i >= left; --i) res.push_back(matrix[down][i]);
--down;
for (int i = down; i >= top; --i) res.push_back(matrix[i][left]);
++left;
}
}
return res;
}
};
看完讲解的思考
无。
代码实现遇到的问题
一开始没有考虑清楚循环结束的条件。
最后的碎碎念
今日第一题是hard,折腾了一个多钟,第一次做hard,有点顶的…