Leetcode - 双周赛155
目录
- 一,3527. 找到最常见的回答
- 二,3528. 单位转换 I
- 三,3529. 统计水平子串和垂直子串重叠格子的数目
- 四,3530. 有向无环图中合法拓扑排序的最大利润
一,3527. 找到最常见的回答
题目列表
本题是一道模拟题,对于 responses[i]
的每个元素,先使用 Set
进行去重,再枚举 Set
,将其中的元素添加到 Map
中,最后统计出现次数最多的字符串,如果有多个,返回字典序最小的。
代码如下:
class Solution {public String findCommonResponse(List<List<String>> responses) {Map<String, Integer> map = new HashMap<>();String ans = "";int cnt = 0;for(List<String> res : responses){Set<String> set = new HashSet<>();// 去重for(String x : res){set.add(x);}// 统计出现次数 + 更新答案for(String x : set){map.merge(x, 1, Integer::sum);int c = map.get(x);if(c > cnt || c == cnt && x.compareTo(ans) < 0){cnt = c;ans = x;}}}return ans;}
}
二,3528. 单位转换 I
题目列表
本题提示,保证单位 0 可以通过唯一的转换路劲(不需要反向转换)转换成任何其他单位,说明它是一颗数,直接使用 dfs 遍历树来做。
代码如下:
class Solution {private static final int MOD = (int)1e9+7;public int[] baseUnitConversions(int[][] con) {int n = con.length + 1;int[] ans = new int[n];// 建树List<int[]>[] g = new ArrayList[n];Arrays.setAll(g, e->new ArrayList<>());for(int[] e : con){int x = e[0], y = e[1], w = e[2];g[x].add(new int[]{y, w});}ans[0] = 1;dfs(0, g, ans);return ans;}void dfs(int x, List<int[]>[] g, int[] ans){for(int[] e : g[x]){int y = e[0], w = e[1];// 防止溢出long res = (long) ans[x] * w % MOD;ans[y] = (int)res;dfs(y, g, ans);}}
}
三,3529. 统计水平子串和垂直子串重叠格子的数目
题目列表
本题求有多少个单元格它即使水平子串,又是垂直子串。字符串匹配问题直接想到 kmp 算法,剩下的就是如何判断随水平展开和垂直展开的字符串原来所在的位置:
- 假设
n x m
的的矩阵 - 对于水平展开的字符串
s1
来说,s1[i] = grid[i/m][i%m]
,其实i = r1 * m + c1
,(r1,c1) = (i/m,i%m)
- 对于垂直展开的字符串
s2
来说,s2[i] = grid[i/n][i%n]
,其实i = r2 * n + c2
,(r2,c2) = (i/n,i%n)
- 要是还不理解,自己画个图理一理就明白了,这里不多说了。
知道对应关系后,还有一个问题,就是通过 kmp 算法我们只能得知 [i-k+1,i]
是与 pattern
匹配的,k = pattern.length()
,但是要怎么标记 [i-k+1,i]
这一段下标,如果暴力枚举标记会超时。其实可以发现,[i-k+1,i]
是一段区间,那么是否可以使用差分数组来解决,毕竟差分只用 O(1)
就能完成标记。答案是可以的,因为只需要判断前缀和是否大于 0 就可以判断它是否被标记且不会重复标记。
代码如下:
class Solution {public int countCells(char[][] grid, String pattern) {char[] p = pattern.toCharArray();int n = grid.length;int m = grid[0].length;int[] next = getNext(p);// 水平展开char[] g = new char[n * m];int k = 0;for(int i = 0; i < n; i++){for(int j = 0; j < m; j++){g[k++] = grid[i][j];}}int[] res1 = kmpSearch(g, p, next);// 垂直展开k = 0;for(int j = 0; j < m; j++){for(int i = 0; i < n; i++){g[k++] = grid[i][j];}}int[] res2 = kmpSearch(g, p, next);long s1 = 0;long s2 = 0;boolean[][] t = new boolean[n][m]; // 记录是否被标记for(int i = 0; i < n * m; i++){s1 += res1[i];if(s1 > 0) {int x = i / m;int y = i % m;t[x][y] = true;}}int ans = 0;for(int i = 0; i < n * m; i++){s2 += res2[i];if(s2 > 0) {int y = i / n;int x = i % n;if(t[x][y]) ans++;}}return ans;}// 求 next 数组private int[] getNext(char[] pattern) {int k = pattern.length;int[] next = new int[k];for(int i = 1, j = 0; i < k; i++){while(j > 0 && pattern[i] != pattern[j]){j = next[j-1];}if(pattern[i] == pattern[j])j++;next[i] = j;}return next;}// kmp 查询public int[] kmpSearch(char[] t, char[] pattern, int[] next) {int n = t.length;int[] res = new int[n + 1];// 差分数组int k = pattern.length;for(int i = 0, j = 0; i < n; i++){while(j > 0 && j < k && t[i] != pattern[j]){j = next[j-1];}if(t[i] == pattern[j]) {j++;}if(j == k){j = next[j-1];//[i-k+1 ,i]res[i-k+1]++;res[i+1]--;}}return res;}
}
四,3530. 有向无环图中合法拓扑排序的最大利润
题目列表
本题数据范围较小,可以使用状压来做,类似于全排列那道题,不过本题有个额外的要求,需要满足有效拓扑排序这一条件。简单解释一下拓扑排序——类似于做游戏任务,你要完成一个任务,但是必须要先完成它的前置任务才行,即它有一定的顺序,画个图理解一下:
这里为了方便计算,可以将它的前置任务也使用二进制来表示集合,代码如下:
class Solution {public int maxProfit(int n, int[][] edges, int[] score) {// 这个特判可以不加,但是速度会变慢if (edges.length == 0) {Arrays.sort(score);int ans = 0;for (int i = 0; i < n; i++) {ans += score[i] * (i + 1);}return ans;}// 使用二进制记录任务的前置任务int[] pre = new int[n];for(int[] e : edges){int x = e[0], y = e[1];pre[y] |= 1 << x;}memo = new int[1<<n];return dfs(0, pre, score);}int[] memo;// O(n*2^n)int dfs(int mask, int[] pre, int[] score){if(memo[mask] > 0) return memo[mask];int j = Integer.bitCount(mask) + 1;int res = 0;for(int i = 0; i < pre.length; i++) {// 判断是否已经做过该任务 + 判断前置任务是否做完if((mask >> i & 1) == 0 && (mask | pre[i]) == mask){res = Math.max(res, dfs(mask | (1 << i), pre, score) + j * score[i]);}}return memo[mask] = res;}
}