第146场双周赛

tobegold574 Lv5

🚀竞赛

统计符合条件长度为3的子数组数目

做题过程

滑动窗口即可。

算法概述

本题要求为求多少个子数组中第一个和第三个数之和为第二个数的一半。 滑动窗口 秒了。

  • 时间复杂度为O(n)
  • 空间复杂度为O(1)

JAVA

1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution {
public int countSubarrays(int[] nums) {
int l=0,r=2;
int ans=0;
while(r<nums.length){
// 要转换成浮点数,不然判断奇数或者0、1会有问题
if((float)nums[l]+nums[r]==(float)nums[(l+r)/2]/2) ans++;
l++;
r++;
}
return ans;
}
}

总结

要转换为浮点数避免奇数除法只取整的问题。

统计异或值为给定值的路径数目

做题过程

使用了深度优先搜索遍历所有路径,使用了哈希表进行记忆化搜索剪枝优化,但是时间还是超了。因为哈希集合的查找开销还是高,即使是常数(毕竟是hash🥔)。

算法概述

本题要求位路径上所有值的异或值必须为给定值,统计符合要求的路径数。采用灵神的解法,用 动态规划 替代散列集。

  • 时间复杂度为O(m*n*u):u是最大的异或值范围,最坏情况下范围内每个异或值都可能出现
  • 空间复杂度为O(m*n*u):dp数组

JAVA

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
class Solution {
private static final int MOD = 1_000_000_007;

public int countPathsWithXorValue(int[][] grid, int k) {
int mx = 0;
for (int[] row : grid) {
for (int val : row) {
mx = Math.max(mx, val);
}
}
// 计算异或值范围
int u = 1 << (32 - Integer.numberOfLeadingZeros(mx));
// 如果K比最大的异或值还要大,那就没有路径
if (k >= u) {
return 0;
}

int m = grid.length;
int n = grid[0].length;
// 记忆化搜索(dp数组)
int[][][] memo = new int[m][n][u];
for (int[][] mat : memo) {
for (int[] row : mat) {
Arrays.fill(row, -1);
}
}
// 逆向搜索
return dfs(grid, m - 1, n - 1, k, memo);
}

private int dfs(int[][] grid, int i, int j, int x, int[][][] memo) {
// 越没越界
if (i < 0 || j < 0) {
return 0;
}
int val = grid[i][j];
// 回到起点了
if (i == 0 && j == 0) {
return x == val ? 1 : 0;
}
// 我原来用的是哈希集合
if (memo[i][j][x] != -1) {
return memo[i][j][x];
}
// 移动
int left = dfs(grid, i, j - 1, x ^ val, memo);
int up = dfs(grid, i - 1, j, x ^ val, memo);
// 计算异或值(逆序需要这样放)
return memo[i][j][x] = (left + up) % MOD;
}
}

重要实例方法及属性(JAVA)

  • Integer.numberOfLeadingZeros(mx):计算前导0的数量
  • <<1:生成一个2的幂次方的数字
  • int u = 1 << (32 - Integer.numberOfLeadingZeros(mx));:计算异或值的最大范围

总结

重要😠

  • 哈希集合的常数时间 不如 记忆化搜索!
  • 逆序搜索更易于处理边界,在其他类型的题目中(e.g., 符合条件的路径)还能剪枝
  • 要求条件的最大值 作为多维动态规划的 第三维

判断网格图能否被切割成块

算法概述

本题要求为在只能横着且两刀或者竖着切两刀的情况下,能不能使图中的矩形只属于一个分区。可以转换为“合并区间”问题,参考灵神解法。

  • 时间复杂度为O(mlogm):排序
  • 空间复杂度为O(m)

JAVA

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class Solution {
boolean checkValidCuts(int n, int[][] rectangles) {
int m = rectangles.length;
// 把四个点降维为两个区间上的端点
int[][] a = new int[m][2];
int[][] b = new int[m][2];
// 长放一起,宽放一起
for (int i = 0; i < m; i++) {
int[] rect = rectangles[i];
a[i][0] = rect[0];
a[i][1] = rect[2];
b[i][0] = rect[1];
b[i][1] = rect[3];
}
// 横着切就比长,竖着切就比宽,一种切法行就行
return check(a) || check(b);
}

private boolean check(int[][] intervals) {
// 升序排序
Arrays.sort(intervals, (a, b) -> a[0] - b[0]);
// 能分成几块
int cnt = 0;
// 记录分割的指针
int maxR = 0;

for (int[] interval : intervals) {
// maxR是跟下边界来比的
if (interval[0] >= maxR) {
cnt++;
}
// maxR是跟上边界来更新的
maxR = Math.max(maxR, interval[1]);
}
// 因为就切两刀,所以只能分三块
return cnt >= 3;
}
}

总结

早说不做第二题,做第三题了。

唯一中间众数子序列 I

算法概述

本题要求为求出有多少个唯一中间众数子序列(子序列:指的是将一个数组删除一些(也可以不删除)元素后,剩下元素不改变顺序得到的非空数组),规定长度为5。参考灵神,这道题偏数学。

  • 时间复杂度为
  • 空间复杂度为

JAVA

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
class Solution {
public int subsequencesWithMiddleMode(int[] nums) {
int n = nums.length;
long ans = (long) n * (n - 1) * (n - 2) * (n - 3) * (n - 4) / 120; // 所有方案数
Map<Integer, Integer> suf = new HashMap<>();
for (int x : nums) {
suf.merge(x, 1, Integer::sum); // suf[x]++
}
Map<Integer, Integer> pre = new HashMap<>(suf.size()); // 预分配空间
// 枚举 x,作为子序列正中间的数
for (int left = 0; left < n - 2; left++) {
int x = nums[left];
suf.merge(x, -1, Integer::sum); // suf[x]--
if (left > 1) {
int right = n - 1 - left;
int preX = pre.getOrDefault(x, 0);
int sufX = suf.get(x);
// 不合法:只有一个 x
ans -= (long) comb2(left - preX) * comb2(right - sufX);
// 不合法:只有两个 x,且至少有两个 y(y != x)
for (Map.Entry<Integer, Integer> e : suf.entrySet()) {
int y = e.getKey();
if (y == x) {
continue;
}
int sufY = e.getValue(); // 注意 sufY 可能是 0
int preY = pre.getOrDefault(y, 0);
// 左边有两个 y,右边有一个 x,即 yy x xz(z 可以等于 y)
ans -= (long) comb2(preY) * sufX * (right - sufX);
// 右边有两个 y,左边有一个 x,即 zx x yy(z 可以等于 y)
ans -= (long) comb2(sufY) * preX * (left - preX);
// 左右各有一个 y,另一个 x 在左边,即 xy x yz(z != y)
ans -= (long) preY * sufY * preX * (right - sufX - sufY);
// 左右各有一个 y,另一个 x 在右边,即 zy x xy(z != y)
ans -= (long) preY * sufY * sufX * (left - preX - preY);
}
}
pre.merge(x, 1, Integer::sum); // pre[x]++
}
return (int) (ans % 1_000_000_007);
}

private int comb2(int num) {
return num * (num - 1) / 2;
}
}

总结

后面再回头看,这道题偏数学不偏算法。

  • Title: 第146场双周赛
  • Author: tobegold574
  • Created at : 2024-12-23 14:26:45
  • Updated at : 2024-12-23 16:16:59
  • Link: https://tobegold574.me/2024/12/23/第146场双周赛/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments