Leetcode - 361周赛

一,2843. 统计对称整数的数目

 这道题直接暴力,要注意的一点是这个数字必须是由 2 * N 位数字组成。

代码如下:

class Solution {
    public int countSymmetricIntegers(int low, int high) {
        int ans = 0;
        for(int i=low; i<=high; i++){
            if(i>10 && isVaild(i)){
                ans++;
            }
        }
        return ans;
    }
    boolean isVaild(int i){
        int sum1 = 0, sum2 = 0;
        String s = String.valueOf(i);
        int n = s.length();
        if(n%2 == 1) return false;//必须是2*N位数组成的整数
        for(int j=0; j<n/2; j++){
            sum1 += (int)(s.charAt(j)-'0');
            sum2 += (int)(s.charAt(n-j-1)-'0');
        }
        return sum1==sum2;
    }
}

二,2844. 生成特殊数字的最少操作

 这道题要我们找到将 num 字符串操作最少次数得到 %25 == 0 的数,那我们首先要找到%25==0的数有什么特点?我们发现 %25==0 的数的后两位必然是 00,25,50,75。那么我们可以分情况讨论,比如:将 num 字符串的后两位变成 00 需要操作多少次?将 num 字符串的后两位变成 25 需要操作多少次?将 num 字符串的后两位变成 50 需要操作多少次?将 num 字符串的后两位变成 75 需要操作多少次?最后取其中的最小值。

还需要注意的一点是,有可能该字符串不管怎么操作都不能使得后两位的值变成上述四种情况中的一种,比如:"2230467",这个时候,我们就需要将该字符串变成 0 (毕竟 0%25 = 0),这种情况我们需要统计字符串中 0 的个数,然后得到答案 num.length() - cnt(0)。

代码如下:

class Solution {
    public int minimumOperations(String num) {
        int cnt = 0;
        int index0 = num.lastIndexOf('0');
        int index2 = num.lastIndexOf('2');
        int index5 = num.lastIndexOf('5');
        int index7 = num.lastIndexOf('7');
        int ans = 100;
        if(index0 >= 0){
            for(int i=index0-1; i>=0; i--){
                char ch = num.charAt(i);
                System.out.println(ch);
                if(ch=='0'||ch=='5'){//将00,50的情况合并
                    ans = Math.min(ans,num.length()-i-2);
                    break;
                }
            }
        }
        if(index5 >= 0)
            for(int i=index5-1; i>=0; i--){
                char ch = num.charAt(i);
                if(ch=='2'||ch=='7'){//将25,75的情况合并
                    ans = Math.min(ans,num.length()-i-2);
                    break;
                }
            }
        for(int i=0; i<num.length(); i++){
            if(num.charAt(i)=='0')
                cnt++;
        }
        return Math.min(num.length()-cnt,ans);
    }
}

三,2845. 统计趣味子数组的数目

这道题目分成 4 个部分:

1. 转化 

        遍历数组,将满足 nums[ i ] % modulo == k 的 nums[ i ] 直接赋值成 1 ,将不满足条的nums[ i ] 赋值为 0 ,因为我们最后要算的是满足 cnt % modulo == k 的子数组

2. 前缀和

        得到转化后的nums数组后,定义一个数组 prev 来统计nums的前缀和,此时我们设 left 是nums子数组的左边界, right 是nums子数组的右边界 [left,right],此时我们子数组中的 cnt = prev[right+1] - prev[left],这里有一个注意点,我们定义的 prev 数组的长度 = nums.length + 1,prev[0] = 0,画个图理解一下:

3. %运算 (注意:R = right+1)

        cnt%modulo = prev[R]%modulo-prev[L]%modulo = k

        1. prev[R]%modulo > prev[L]%modulo

            prev[R]%modulo-prev[L]%modulo = k

            prev[R]%modulo - k = prev[L]%modulo

        2. prev[R]%modulo < prev[L]%modulo

            prev[R]%modulo-prev[L]%modulo+modulo = k

            prev[R]%modulo - k + modulo = prev[L]%modulo

    结果:prev[L]%modulo = (prev[R]%modulo - k + modulo)%modulo

    现在这道题就变成了一道 "两数之和" 的题目!!

4. 滑动数组

代码如下:

class Solution {
    public long countInterestingSubarrays(List<Integer> nums, int modulo, int k) {
        Map<Integer,Integer> map = new HashMap<>();
        int[] preSum = new int[nums.size()+1];
        for(int i=0; i<nums.size(); i++){
            preSum[i+1] = preSum[i]+(nums.get(i)%modulo==k?1:0);
        }
        long ans = 0;
        for(int i=0; i<=nums.size(); i++){
            ans += map.getOrDefault((preSum[i]%modulo - k + modulo)%modulo,0);
            //R = i,代表子树组的右边为 i,在[0,i]下标处找子数组的左边边界

            map.put(preSum[i]%modulo,map.getOrDefault(preSum[i]%modulo,0)+1);
            //L = i,更新下标为 i 的左边边界的子数组有几个
        }
        return ans;
    }
}

四,2846. 边权重均等查询

这道题还是使用了树上倍增的算法思想,我借鉴了一下大佬的写法,我加了一些注释,直接看代码:

class Solution {
    public int[] minOperationsQueries(int n, int[][] edges, int[][] queries) {
        List<int[]>[] g = new ArrayList[n];
        Arrays.setAll(g,e -> new ArrayList<>());
        for(var e : edges){
            int x = e[0], y = e[1], w = e[2]-1;
            g[x].add(new int[]{y,w});
            g[y].add(new int[]{x,w});
        }//记录每一个节点的相邻节点及权重

        int m = 32 - Integer.numberOfLeadingZeros(n);
        var pa = new int[n][m];
        //n:节点,m:向上走2^m步, pa[i][j]:到达哪个节点
        for(int i=0; i<n; i++){
            Arrays.fill(pa[i],-1);
        }
        var cnt = new int[n][m][26];
        //从n向上走 2^m步 过程中 每个权重的个数
        var depth = new int[n];//每个节点的深度
        dfs(0,-1,g,pa,cnt,depth);

        //树上倍增
        for(int i=0; i<m-1; i++){
            for(int x = 0; x < n; x++){
                int p = pa[x][i];
                if(p != -1){
                    int pp = pa[p][i];
                    pa[x][i+1] = pp;
                    for(int j = 0; j < 26; j++){
                        cnt[x][i+1][j] = cnt[x][i][j] + cnt[p][i][j];
                    }
                }
            }
        }

        //交换
        var ans = new int[queries.length];
        for(int qi = 0; qi < queries.length; qi++){
            int x = queries[qi][0], y = queries[qi][1];
            int pathLen = depth[x] + depth[y];
            var cw = new int[26];//统计不同权重的个数
            if(depth[x] > depth[y]){
                int tmp = x;
                x = y;
                y = tmp;
            }
            //让y向上走,使得x,y节点的深度一样
            for(int k = depth[y]-depth[x]; k > 0; k &= k-1){
                int i = Integer.numberOfTrailingZeros(k);
                int p = pa[y][i];//j向上走2^i步
                for(int j=0; j<26; j++){
                    cw[j] += cnt[y][i][j];//统计y向上走的过程中的权重
                }
                y = p;//y节点走到p节点位置
            }

            if(y != x){//x,y同时向上走,找到相遇点
                for(int i=m-1; i>=0; i--){
                    int px = pa[x][i];//从x节点向上走2^i步
                    int py = pa[y][i];//从y节点向上走2^i步
                    if(px != py){
                        for(int j=0; j<26; j++){
                            cw[j] += cnt[x][i][j]+cnt[y][i][j];//统计x,y向上走的过程中的权重
                        }
                        x = px;//更新x的位置
                        y = py;//更新x的位置
                    }
                }
                for(int j = 0; j < 26; j++){
                    cw[j] += cnt[x][0][j] + cnt[y][0][j];//统计x,y向上走的过程中,不同权重的个数
                }
                x = pa[x][0];//注意要在往上走一步才是相遇点
            }

            int lca = x;
            pathLen -= depth[lca]*2;//真正的路径长度
            int maxCw = 0;//求出现次数最多的权重
            for(int i=0; i<26; i++){
                maxCw = Math.max(maxCw,cw[i]);
            }
            ans[qi] = pathLen - maxCw;//路径长度-权重出现最多次数=操作最少
        }
        return ans;
    }

    private void dfs(int x, int fa, List<int[]>[] g, int[][] pa, int[][][] cnt, int[] depth) {
        pa[x][0] = fa;//初始化
        for (var e : g[x]) {//遍历x节点可以走向那些节点
            int y = e[0], w = e[1];
            //y:x走向的那些节点,w:从x节点到y节点的权重
            if (y != fa) {//不能重复计算 比如:i->j j->i
                cnt[y][0][w] = 1;//初始化每个节点向周围走一步的权重
                depth[y] = depth[x] + 1;//初始化每个节点的深度
                dfs(y, x, g, pa, cnt, depth);
            }
        }
    }
}

猜你喜欢

转载自blog.csdn.net/m0_74859835/article/details/132795926