第 254 场周赛
Problem A - 作为子字符串出现在单词中的字符串数目
方法一:暴力
逐个检查即可。
参考代码(Python 3)
class Solution:
def numOfStrings(self, patterns: List[str], word: str) -> int:
return sum(1 if pattern in word else 0 for pattern in patterns)
方法二:AC自动机(略)
Problem B - 构造元素不等于两相邻元素平均值的数组
方法一:排序+贪心
Leetcode也开始出构造题了?
本题最简单的解法就是排序之后,先放所有奇数位置,再放所有偶数位置,这样就可以保证所有元素的相邻元素要么都比它大,要么都比它小,从而一定符合要求。
- 时间复杂度。
- 空间复杂度。
参考代码(C++)
class Solution {
public:
vector<int> rearrangeArray(vector<int>& nums) {
int n = nums.size();
sort(nums.begin(), nums.end());
vector<int> ans(n);
int ptr = 0;
for (int i = 0; i < n; i += 2)
ans[i] = nums[ptr++];
for (int i = 1; i < n; i += 2)
ans[i] = nums[ptr++];
return ans;
}
};
Problem C - 数组元素的最小非零乘积
方法一:贪心
观察样例会发现,我们可以将所有小于的元素按照的方式两两配对,然后将每一对经过若干次操作后都变为,这样最后的乘积就是。
这里给出一个不太严格的说明:
- 因为每次操作不改变所有元素的总和,而我们知道总和一定时,要想乘积最小,应该让元素尽可能向两侧分布。
- 最大的无论与哪一个其他元素进行交换,乘积都不会减小,因此我们将其放在一边。
- 对于剩下的元素,如果不按照上面的方式进行配对,那么一定会有一对元素,某一个二进制位上都为;同时有另一对元素,某一个二进制位上都为,这时我们将第一对中的一个元素的换给第二对中的一个元素,我们就可以使得这四个元素在总和不变的情况下乘积减小。因此最优的配对中,一定是每一对元素的异或值都为。
- 在固定异或值为的情况下,可以得到的单对元素的最小非零乘积就是。
- 最后的操作结果就如上面所说。
复杂度为:
- 时间复杂度。
- 空间复杂度。
参考代码(Python 3)
mod = 1000000007
class Solution:
def minNonZeroProduct(self, p: int) -> int:
if p == 1:
return 1
hi = (1 << p) - 1
return pow(hi - 1, (hi - 1) >> 1, mod) * hi % mod
Problem D - 你能穿过矩阵的最后一天
方法一:二分答案+多源BFS
容易想到二分答案的思路。每次可以构造出当前的图,然后以所有第一行的点作为源点,跑一次多源BFS来判断是否能到达最后一行。
- 时间复杂度。
- 空间复杂度。
参考代码(C++)
const int d[4][2] = {{-1, 0}, {0, -1}, {0, 1}, {1, 0}};
class Solution {
public:
int latestDayToCross(int row, int col, vector<vector<int>>& cells) {
int lo = 0, hi = row * col;
while (lo <= hi) {
int mid = (lo + hi) >> 1;
vector<vector<bool>> mat(row, vector<bool>(col, true)), vis(row, vector<bool>(col));
for (int i = 0; i < mid; ++i)
mat[cells[i][0] - 1][cells[i][1] - 1] = false;
queue<pair<int, int>> q;
for (int i = 0; i < col; ++i)
if (mat[0][i])
q.emplace(0, i), vis[0][i] = true;
bool can = false;
while (!q.empty()) {
auto [r, c] = q.front();
q.pop();
if (r == row - 1) {
can = true;
break;
}
for (int k = 0; k < 4; ++k) {
int nr = r + d[k][0], nc = c + d[k][1];
if (nr >= 0 && nr < row && nc >= 0 && nc < col && mat[nr][nc] && !vis[nr][nc]) {
vis[nr][nc] = true;
q.emplace(nr, nc);
}
}
}
if (can)
lo = mid + 1;
else
hi = mid - 1;
}
return hi;
}
};
方法二:倒序处理+并查集
更好的做法是并查集。正序处理需要从并查集中删边,这是难于实现的;但如果我们倒序处理,就只需要连边了。
这里为了方便处理,我们增加了一个源点与所有第一行的点相连,一个汇点与所有最后一行的点相连。当我们处理到某一位置时,只要判断和是否在同一个连通块中,就可以知道是否能从第一行走到最后一行。
注意本题中每次新增(倒序处理时)的是点,所以我们需要用一个额外的数组记录每个点当前是否已经存在,在连边时,只将当前点与其周围已经存在的点相连,否则将导致错误。(举个例子,假设和已经存在,而还不存在,如果在连边时不进行存在性判断,就会导致和通过并不存在的连接了起来。)
- 时间复杂度。
- 空间复杂度。
参考代码(C++)
const int d[4][2] = {{-1, 0}, {0, -1}, {0, 1}, {1, 0}};
class UnionFind {
int n;
vector<int> parent, size;
public:
UnionFind(int n) {
this->n = n;
parent = vector<int>(n);
size = vector<int>(n, 1);
for (int i = 0; i < n; ++i)
parent[i] = i;
}
int find(int idx) {
if (parent[idx] == idx)
return idx;
return parent[idx] = find(parent[idx]);
}
void connect(int a, int b) {
int fa = find(a), fb = find(b);
if (fa != fb) {
if (size[fa] > size[fb]) {
parent[fb] = fa;
size[fa] += size[fb];
} else {
parent[fa] = fb;
size[fb] += size[fa];
}
}
}
};
class Solution {
public:
int latestDayToCross(int row, int col, vector<vector<int>>& cells) {
UnionFind uf(row * col + 2);
int S = row * col, T = row * col + 1;
vector<vector<bool>> exist(row, vector<bool>(col));
for (int i = row * col - 1; i >= 0; --i) {
int r = cells[i][0] - 1, c = cells[i][1] - 1;
if (r == 0)
uf.connect(S, c);
if (r == row - 1)
uf.connect(T, r * col + c);
exist[r][c] = true;
for (int k = 0; k < 4; ++k) {
int nr = r + d[k][0], nc = c + d[k][1];
if (nr >= 0 && nr < row && nc >= 0 && nc < col && exist[nr][nc])
uf.connect(r * col + c, nr * col + nc);
}
if (uf.find(S) == uf.find(T))
return i;
}
return -1; // Should not return from here.
}
};