力扣第 77 场双周赛
大约 4 分钟
力扣第 77 场双周赛
2022 四月的最后一天。本场周赛国服共 245 人 AK。
T1. 统计是给定字符串前缀的字符串数目(3 分)
解题思路
模拟。直接用库函数 String#startsWith(String prefix)
判断是否为前缀即可。
时间复杂度:库函数不讨论时间复杂度。
参考代码
class Solution {
public int countPrefixes(String[] words, String s) {
int cnt = 0;
for (String word : words) {
if (s.startsWith(word)) {
cnt++;
}
}
return cnt;
}
}
T2. 最小平均差(4 分)
解题思路
前缀和 + 模拟。枚举每个下标,分别计算出前 i+1
个元数和后 n-i-1
个元素的平均值绝对差,需注意边界条件 n-i-1
为 0 的情况。
时间复杂度:O(n)
。
参考代码
class Solution {
public int minimumAverageDifference(int[] nums) {
int n = nums.length;
// 前缀和
long[] preSum = new long[n + 1];
for (int i = 0; i < n; i++) {
preSum[i + 1] = preSum[i] + nums[i];
}
int min = Integer.MAX_VALUE;
int minIdx = n - 1;
// 如果有多个下标最小平均差相等,请你返回 最小 的一个下标。
for (int i = n - 1; i >= 0; i--) {
long leftSum = preSum[i + 1] - preSum[0];
long rightSum = preSum[n] - leftSum;
int leftAvg = (int) (leftSum / (i + 1));
int rightAvg = (n - i - 1 == 0) ? (int) rightSum : (int) (rightSum / (n - i - 1));
int diff = Math.abs(leftAvg - rightAvg);
if (diff <= min) {
min = diff;
minIdx = i;
}
}
return minIdx;
}
}
T3. 统计网格图中没有被保卫的格子数(5 分)
解题思路
模拟。数据范围 m * n <= 10^5
,咋眼一看直接模拟会超时,但注意题目条件 除非他们被一座墙或者另外一个警卫 挡住 了视线
,相当于一个幅度很大的剪枝。
时间复杂度约为:O(m * n)
,可以过。
参考代码
class Solution {
public int countUnguarded(int m, int n, int[][] guards, int[][] walls) {
char[][] grid = new char[m][n];
// 初始状态 置为未被保卫
for (char[] chars : grid) {
Arrays.fill(chars, 'F');
}
// 警卫
for (int[] guard : guards) {
grid[guard[0]][guard[1]] = 'G';
}
// 墙
for (int[] wall : walls) {
grid[wall[0]][wall[1]] = 'W';
}
// 模拟
for (int[] guard : guards) {
mock(m, n, grid, guard[0], guard[1]);
}
int cnt = 0;
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (grid[i][j] == 'F') {
cnt++;
}
}
}
return cnt;
}
private void mock(int m, int n, char[][] grid, int guardX, int guardY) {
// up
for (int i = guardX - 1; i >= 0 && grid[i][guardY] != 'W' && grid[i][guardY] != 'G'; i--) {
if (grid[i][guardY] == 'F') {
grid[i][guardY] = 'T';
}
}
// down
for (int i = guardX + 1; i < m && grid[i][guardY] != 'W' && grid[i][guardY] != 'G'; i++) {
if (grid[i][guardY] == 'F') {
grid[i][guardY] = 'T';
}
}
// left
for (int j = guardY - 1; j >= 0 && grid[guardX][j] != 'W' && grid[guardX][j] != 'G'; j--) {
if (grid[guardX][j] == 'F') {
grid[guardX][j] = 'T';
}
}
// right
for (int j = guardY + 1; j < n && grid[guardX][j] != 'W' && grid[guardX][j] != 'G'; j++) {
if (grid[guardX][j] == 'F') {
grid[guardX][j] = 'T';
}
}
}
}
T4. 逃离火灾(6 分)
解题思路
BFS + 二分。着火的格子扩散和人能到达的位置均满足 BFS 模型。停留时间也显然满足单调性(如果能停留到 n 分钟,则必定也能停留 n-1 分钟),可用二分法求出在初始位置可以停留的 最多 分钟数
。
因为数据范围 m * n <= 2 * 10^4
,着火的格子每分钟扩散 1 格,所以二分上界可以设置为 2 * 10^4
(当然设置为 10^9
也能过)。
时间复杂度:O(m*n*logT)
,其中 T = 2 * 10^4
。
参考代码
class Solution {
private static final int[][] DIRECTIONS = {{1, 0}, {0, 1}, {-1, 0}, {0, -1}};
private int M;
private int N;
public int maximumMinutes(int[][] grid) {
M = grid.length;
N = grid[0].length;
int left = 0;
int right = 20000;
while (left < right) {
int mid = left + (right - left) / 2;
int[][] gridClone = new int[M][N];
for (int i = 0; i < M; i++) {
if (N >= 0) {
System.arraycopy(grid[i], 0, gridClone[i], 0, N);
}
}
// 边界二分 F, F,..., F, [T, T,..., T] checkMid(mid) == T
// ----------------------^
if (!checkMid(gridClone, mid)) {
right = mid;
} else {
left = mid + 1;
}
}
// 左边界二分
return left == 20000 ? 1000000000 : left - 1;
}
private boolean checkMid(int[][] grid, int mid) {
Queue<int[]> fires = new LinkedList<>();
// 在初始位置停留 mid 分钟 火势蔓延
for (int i = 0; i < M; i++) {
for (int j = 0; j < N; j++) {
if (grid[i][j] == 1) {
fires.add(new int[]{i, j});
}
}
}
int minutes = 0;
while (!fires.isEmpty()) {
int size = fires.size();
if (minutes == mid) {
break;
}
minutes++;
for (int i = 0; i < size; i++) {
int[] cur = fires.remove();
// 烧到人,直接 G
if (cur[0] == 0 && cur[1] == 0) {
return false;
}
for (int[] dir : DIRECTIONS) {
int nextM = cur[0] + dir[0];
int nextN = cur[1] + dir[1];
if (nextM >= 0 && nextM < M && nextN >= 0 && nextN < N && grid[nextM][nextN] == 0) {
grid[nextM][nextN] = 1;
fires.add(new int[]{nextM, nextN});
}
}
}
}
// 开始逃离火灾
Queue<int[]> player = new LinkedList<>();
boolean[][] visited = new boolean[M][N];
player.add(new int[]{0, 0});
visited[0][0] = true;
while (!player.isEmpty()) {
int size = player.size();
for (int i = 0; i < size; i++) {
int[] cur = player.remove();
if (cur[0] == M - 1 && cur[1] == N - 1) {
return true;
}
if (grid[cur[0]][cur[1]] == 1) {
continue;
}
for (int[] dir : DIRECTIONS) {
int nextM = cur[0] + dir[0];
int nextN = cur[1] + dir[1];
if (nextM >= 0 && nextM < M && nextN >= 0 && nextN < N && !visited[nextM][nextN] && grid[nextM][nextN] == 0) {
visited[nextM][nextN] = true;
player.add(new int[]{nextM, nextN});
}
}
}
int fireSize = fires.size();
for (int i = 0; i < fireSize; i++) {
int[] cur = fires.remove();
// 烧到 安全屋,直接 G
if (cur[0] == M - 1 && cur[1] == N - 1) {
return false;
}
for (int[] dir : DIRECTIONS) {
int nextM = cur[0] + dir[0];
int nextN = cur[1] + dir[1];
if (nextM >= 0 && nextM < M && nextN >= 0 && nextN < N && grid[nextM][nextN] == 0) {
grid[nextM][nextN] = 1;
fires.add(new int[]{nextM, nextN});
}
}
}
}
return false;
}
}
(全文完)