Leetcode - 双周赛155

一,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;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一叶祇秋

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值