第 204 场周赛
全套周赛题解新鲜出炉!
题目1:5499. 重复至少 K 次且长度为 M 的模式
思路:遍历
签到题。第一层循环遍历子数组起点,第二层循环遍历子数组终点之后的部分,遍历 k k k 次,若不匹配则返回 f a l s e false false,第二层循环遍历完成后说明匹配成功,返回 t r u e true true。否则继续匹配新的子数组,第一层循环遍历结束后还没有返回说明未匹配,返回 f a l s e false false 。
代码:
class Solution {
public:
bool containsPattern(vector<int>& arr, int m, int k) {
int n = arr.size();
for (int i = 0; i + m * k <= n; ++i) {
bool check = true;
for (int j = i + m; j < i + m * k; ++j) {
if (arr[j] != arr[j - m]) {
check = false;
break;
}
}
if (check) {
return true;
}
}
return false;
}
};
复杂度分析:
时间复杂度为 O ( N 2 ) O(N^2) O(N2),空间复杂度为 O ( 1 ) O(1) O(1)。
全套周赛题解新鲜出炉!
题目2:5500. 乘积为正数的最长子数组长度
思路:分治 + 前缀和
首先子数组含0肯定不满足条件,所以遍历一遍整个数组,通过 0 出现的位置将数组分为若干个子数组,每个子数组调用 d f s dfs dfs 函数进行分治计算。
根据题意,只要某一段区间内负数出现的次数为偶数,该区间即满足乘积为正数。所以先记录每一个子数组第一次出现负数的位置,然后用类似于前缀和的方式,记录子数组遍历到每一个位置时,该位置之前出现负数的数量,更新全局最优值即可。
代码:
class Solution {
public:
int res = 0;
void dfs(int l, int r, vector<int>& a) {
int f = -1; //记录第一次出现负数位置
int cnt = 0;
for(int i = l; i <= r; i++) {
if(a[i] < 0) { // 记录截至目前,负数出现次数奇偶性
cnt = cnt ^ 1;
}
if(cnt == 0) {
// 出现偶数次,从子数组起始点到当前位置满足条件
res = max(res, i - l + 1);
} else {
// 出现奇数次,从第一次出现负数位置到当前位置满足条件
if(f == -1) f = i;
if(f != -1) res = max(res, i - f);
}
}
}
int getMaxLen(vector<int>& a) {
int l = 0, r = a.size() - 1;
for(int i = 0; i < a.size(); i++) {
if(a[i] == 0) {
dfs(l, i - 1, a);
l = i + 1;
}
}
dfs(l, r, a);
return res;
}
};
复杂度分析:
时间复杂度为 O ( N ) O(N) O(N),空间复杂度为 O ( N ) O(N) O(N)。
全套周赛题解新鲜出炉!
题目3:5501. 使陆地分离的最少天数
思路:DFS
考虑图中左上角的陆地,最多只需要将其下面和右面两块陆地更改为水单元,即可使该陆地块与其他岛屿分离,所以答案最多为2。
首先深搜整个图,计算岛屿数量,若数量不为1,则不需操作就可以满足题意,返回 0 即可;
其次假设删除某一块陆地单元可以满足题意,遍历每一个陆地单元,将其删除后深搜整个图,计算岛屿数量,满足条件则返回 1 即可。
若遍历完所有陆地单元都无法满足题意,则说明需要操作两次,返回 2.
代码:
class Solution {
public:
int n, m;
vector<vector<bool>> st;
vector<vector<int>> g;
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
void dfs(int x, int y) {
st[x][y] = true;
for (int i = 0; i < 4; i ++ ) {
int a = x + dx[i], b = y + dy[i];
if (a >= 0 && a < n && b >= 0 && b < m && !st[a][b] && g[a][b] == 1)
dfs(a, b);
}
}
bool check() { // 判断岛屿块数量
int cnt = 0;
st = vector<vector<bool>>(n, vector<bool>(m));
for (int i = 0; i < n; i ++ )
for (int j = 0; j < m; j ++ )
if (!st[i][j] && g[i][j] == 1) {
cnt ++ ;
dfs(i, j);
}
return cnt > 1;
}
int minDays(vector<vector<int>>& grid) {
n = grid.size(), m = grid[0].size();
g = grid;
if (check()) return 0;
for (int i = 0; i < n; i ++ )
for (int j = 0; j < m; j ++ )
if (g[i][j]) {
g[i][j] = 0; //删除某一陆地单元
if (check()) return 1;
g[i][j] = 1; //失败则复原
}
return 2;
}
};
复杂度分析:
时间复杂度为 O ( N 2 M 2 ) O(N^2M^2) O(N2M2),空间复杂度为 O ( N M ) O(NM) O(NM)。
全套周赛题解新鲜出炉!
题目4:5502. 将子数组重新排序得到同一个二叉查找树的方案数
思路:组合数 + 递归
首先根节点是固定不能变的。
其次,根节点值 k k k 确定后,整个数组可以分为小于 k k k 的部分和大于 k k k 的部分,分别对应左子树和右子树。假设左子树和右子树结点顺序相对固定,那么这两个部分互相交叉是不会影响二叉搜索树的形态的。
举个例子,对于 [ 3 , 4 , 5 , 1 , 2 ] [3, 4, 5, 1, 2] [3,4,5,1,2] 这样的一个序列,确定根节点值 k = 3 k = 3 k=3 后,左子树序列为 [ 1 , 2 ] [1, 2] [1,2] ,右子树序列为 [ 4 , 5 ] [4, 5] [4,5] ,此时这两个部分互相交叉得到的新序列,如 [ 3 , 4 , 1 , 2 , 5 ] [3, 4, 1, 2, 5] [3,4,1,2,5], [ 3 , 1 , 2 , 4 , 5 ] [3, 1, 2, 4, 5] [3,1,2,4,5] 等形成的二叉搜索树都是相同的。
所以,这样的交叉方案数其实就是在 n − 1 n - 1 n−1 个位置中,寻找 k − 1 k - 1 k−1 个位置的方案数 C n − 1 k − 1 C_{n-1} ^ {k-1} Cn−1k−1
下面我们只需要考虑左右子树序列分别可以选取的方案数,而这就相当于原问题缩小规模的子问题,通过递归实现即可。
代码:
typedef long long LL;
class Solution {
public:
vector<vector<int>> C;
const int MOD = 1e9 + 7;
int numOfWays(vector<int>& nums) {
int n = nums.size();
C = vector<vector<int>> (n + 1, vector<int>(n + 1));
for (int i = 0; i <= n; i ++ )
for (int j = 0; j <= i; j ++ )
if (!j) C[i][j] = 1; // 计算组合数
else C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % MOD;
// 题目要求除给定以外,所以要减1
return (f(nums) + MOD - 1) % MOD;
}
int f(vector<int> nums) {
if (nums.empty()) return 1;
int n = nums.size();
int k = nums[0];
vector<int> left, right; // 左右子序列
for (auto x: nums)
if (x < k) left.push_back(x);
else if (x > k) right.push_back(x - k);
return (LL)C[n - 1][k - 1] * f(left) % MOD * f(right) % MOD;
}
};
复杂度分析:
时间复杂度为 O ( N 2 ) O(N^2) O(N2),空间复杂度为 O ( N 2 ) O(N^2) O(N2)。