7020. Count Symmetric Integers

给你两个正整数 low 和 high 。

对于一个由 2 * n 位数字组成的整数 x ,如果其前 n 位数字之和与后 n 位数字之和相等,则认为这个数字是一个对称整数。

返回在 [low, high] 范围内的 对称整数的数目 。

测试样例:

输入:low = 1, high = 100

输出:9

解释:
在 1 到 100 范围内共有 9 个对称整数:11、22、33、44、55、66、77、88 和 99 。

解答:搜索范围很小,没必要用困难写法。暴力计算。

class Solution {
    public int countSymmetricIntegers(int low, int high) {
        int res = 0;
        for (int i = low; i <= high; ++i) {
            if (isSymmetric(i)) {
                ++res;
            }
        }
        return res;
    }

    private boolean isSymmetric(int n) {
        String num = String.valueOf(n);
        if (num.length() %  2 == 0) {
            int left = 0, right = 0;
            int point = num.length() / 2;
            for (int i = 0; i < num.length(); ++i) {
                if (i < point) {
                    left += num.charAt(i);
                } else {
                    right += num.charAt(i);
                }
            }
            return left == right;
        }
        return false;
    }
}

8040. Minimum Operations to Make a Special Number

给你一个下标从 0 开始的字符串 num ,表示一个非负整数。

在一次操作中,您可以选择 num 的任意一位数字并将其删除。请注意,如果你删除 num 中的所有数字,则 num 变为 0。

返回最少需要多少次操作可以使 num 变成特殊数字。

如果整数 x 能被 25 整除,则该整数 x 被认为是特殊数字。

测试样例:

输入:num = "2245047"

输出:2

解释:
删除数字 num[5] 和 num[6] ,得到数字 "22450" ,可以被 25 整除。可以证明要使数字变成特殊数字,最少需要删除 2 位数字。

解答:能被25整除,数字结尾必然是00,25,50和75。先计算一下,如果让当前数字结尾符合要求所需要的删除次数。还有一种可能是把所有数字都删除,这种情况下,可以查看一下当前数字有多少个0(0不需要被删除)。

class Solution {
    private static final String[] endWith = {"00", "50", "25", "75"};

    public int minimumOperations(String num) {
        int min = num.length() - countZero(num);
        for (String e : endWith) {
            min = Math.min(cal(num, e), min);
        }
        return min;
    }

    private int cal(String num, String e) {
        int[] result = {-1,-1};
        int p = 1, c = 0;
        for (int i = num.length() - 1; i >= 0; --i) {
            if (num.charAt(i) == e.charAt(p)) {
                result[p--] = i;
                if (p == -1) {
                    return c - 1;
                }
            }
            ++c;
        }
        return num.length();
    }

    private int countZero(String num) {
        int count = 0;
        for (int i = 0; i < num.length(); ++i) {
            if (num.charAt(i) == '0') {
                ++count;
            }
        }
        return count;
    }
}

6952. Count of Interesting Subarrays

给你一个下标从 0 开始的整数数组 nums ,以及整数 modulo 和整数 k 。

请你找出并统计数组中 趣味子数组 的数目。

如果 子数组 nums[l..r] 满足下述条件,则称其为 趣味子数组 :

  • 在范围 [l, r] 内,设 cnt 为满足 nums[i] % modulo == k 的索引 i 的数量。并且 cnt % modulo == k 。

以整数形式表示并返回趣味子数组的数目。

注意:子数组是数组中的一个连续非空的元素序列。

测试样例

输入:nums = [3,2,4], modulo = 2, k = 1

输出:3

解释:
在这个示例中,趣味子数组分别是:

子数组 nums[0..0] ,也就是 [3] 。

  • 在范围 [0, 0] 内,只存在 1 个下标 i = 0 满足 nums[i] % modulo == k 。
  • 因此 cnt = 1 ,且 cnt % modulo == k 。

子数组 nums[0..1] ,也就是 [3,2] 。

  • 在范围 [0, 1] 内,只存在 1 个下标 i = 0 满足 nums[i] % modulo == k 。
  • 因此 cnt = 1 ,且 cnt % modulo == k 。

子数组 nums[0..2] ,也就是 [3,2,4] 。

  • 在范围 [0, 2] 内,只存在 1 个下标 i = 0 满足 nums[i] % modulo == k 。
  • 因此 cnt = 1 ,且 cnt % modulo == k 。

可以证明不存在其他趣味子数组。因此,答案为 3 。

解答:这道题目主要考察前缀和。题目比较绕,但是逻辑很简单,查看前缀和是否可以用差值凑出k。

class Solution {
    public long countInterestingSubarrays(List<Integer> nums, int modulo, int k) {
        HashMap<Integer, Integer> mem = new HashMap<>();
        mem.put(0, 1);
        int count = 0;
        long res = 0;
        for (int n : nums) {
            int add = (n % modulo == k ? 1 : 0);
            count = (count + add) % modulo;
            if (count < k) {
                res += mem.getOrDefault(modulo - k + count, 0);
            } else {
                res += mem.getOrDefault(count - k, 0);
            }
            mem.put(count, mem.getOrDefault(count, 0) + 1);
        }
        return res;
    }
}

100018. Minimum Edge Weight Equilibrium Queries in a Tree

现有一棵由 n 个节点组成的无向树,节点按从 0 到 n - 1 编号。给你一个整数 n 和一个长度为 n - 1 的二维整数数组 edges ,其中 edges[i] = [ui, vi, wi] 表示树中存在一条位于节点 ui 和节点 vi 之间、权重为 wi 的边。

另给你一个长度为 m 的二维整数数组 queries ,其中 queries[i] = [ai, bi] 。对于每条查询,请你找出使从 ai 到 bi 路径上每条边的权重相等所需的 最小操作次数 。在一次操作中,你可以选择树上的任意一条边,并将其权重更改为任意值。

注意:

  • 查询之间 相互独立 的,这意味着每条新的查询时,树都会回到 初始状态 。
  • 从 ai 到 bi的路径是一个由 不同 节点组成的序列,从节点 ai 开始,到节点 bi 结束,且序列中相邻的两个节点在树中共享一条边。

返回一个长度为 m 的数组 answer ,其中 answer[i] 是第 i 条查询的答案。

测试样例

输入:n = 7, edges = [[0,1,1],[1,2,1],[2,3,1],[3,4,2],[4,5,2],[5,6,2]], queries = [[0,3],[3,6],[2,6],[0,6]]

输出:[0,0,1,3]

解释:
第 1 条查询,从节点 0 到节点 3 的路径中的所有边的权重都是 1 。因此,答案为 0 。
第 2 条查询,从节点 3 到节点 6 的路径中的所有边的权重都是 2 。因此,答案为 0 。
第 3 条查询,将边 [2,3] 的权重变更为 2 。在这次操作之后,从节点 2 到节点 6 的路径中的所有边的权重都是 2 。因此,答案为 1 。
第 4 条查询,将边 [0,1]、[1,2]、[2,3] 的权重变更为 2 。在这次操作之后,从节点 0 到节点 6 的路径中的所有边的权重都是 2 。因此,答案为 3 。
对于每条查询 queries[i] ,可以证明 answer[i] 是使从 ai 到 bi 的路径中的所有边的权重相等的最小操作次数。

解答:这题感觉很难,但是好像通过率很高的样子。。。我能想到的思路是假定0点是root,那么从0点开始,计算一下每个数到0所经过的权重边。但是实际上,任意2点之间的最短路径可能不经过0点,所以我们还需要寻找LCA。因为query数还挺多的,我能想到的是利用倍增法+折半搜索寻找这个目标节点。

有这些准备之后,遍历query,寻找最长共同祖节点,最后计算路径上的权重。

class Solution {
    class TreeAncestor {
        private TreeMap<Integer, Integer>[] parentMap;
        private HashSet<Integer> pow;

        public TreeAncestor(int n, int[] parent) {
            parentMap = new TreeMap[n];
            parentMap[0] = new TreeMap<>();
            for (int i = 0; i < n; ++i) {
                parentMap[i] = initialParent(i, parent);
            }

            int d = 1;
            pow = new HashSet<>();
            while (d <= n) {
                pow.add(d);
                d *= 2;
            }
        }

        private TreeMap<Integer, Integer> initialParent(int p, int[] parent) {
            if (parentMap[p] == null) {
                int d = 1, last = parent[p];
                parentMap[p] = new TreeMap<>();
                parentMap[p].put(1, last);
                for (;;) {
                    TreeMap<Integer, Integer> tmp = initialParent(last, parent);
                    if (tmp.containsKey(d)) {
                        last = tmp.get(d);
                        parentMap[p].put(2 * d, last);
                        d *= 2;
                    } else {
                        break;
                    }
                }
            }
            return parentMap[p];
        }

        public int getKthAncestor(int node, int k) {
            if (k == 0) {
                return node;
            } else if (pow.contains(k) || node == 0) {
                return parentMap[node].getOrDefault(k, -1);
            } else {
                Map.Entry<Integer, Integer> kv = parentMap[node].floorEntry(k);
                if (kv == null || kv.getKey() < k / 2) return -1;
                else if (kv.getKey().equals(k)) return kv.getValue();
                else return getKthAncestor(kv.getValue(), k - kv.getKey());
            }
        }
    }

    private List<int[]>[] edges;
    private int[][] mem;
    private int[] depth, parent;
    private TreeAncestor treeAncestor;

    public int[] minOperationsQueries(int n, int[][] _edges, int[][] queries) {
        edges = new List[n];
        for (int i = 0; i < n; ++i) {
            edges[i] = new ArrayList<>();
        }
        for (int[] e : _edges) {
            e[2] -= 1;
            edges[e[0]].add(e);
            edges[e[1]].add(e);
        }

        mem = new int[n][26];
        depth = new int[n];
        parent = new int[n];

        dfs(0, -1, 0, new int[26]);
        treeAncestor = new TreeAncestor(n, parent);

        int[] res = new int[queries.length];
        for (int i = 0; i < res.length; ++i) {
            res[i] = getResult(queries[i][0], queries[i][1]);
        }
        return res;
    }

    private void dfs(int pos, int last, int curDepth, int[] mark) {
        for (int i = 0; i < 26; ++i) {
            mem[pos][i] = mark[i];
        }
        depth[pos] = curDepth;
        parent[pos] = last;

        for (int[] e : edges[pos]) {
            int nextNode = e[0] == pos ? e[1] : e[0];
            if (nextNode != last) {
                mark[e[2]] += 1;
                dfs(nextNode, pos, curDepth + 1, mark);
                mark[e[2]] -= 1;
            }
        }
    }

    private int getResult(int n1, int n2) {
        int d1 = depth[n1], d2 = depth[n2];
        int min = Math.min(d1, d2);
        int st = 0, ed = min;
        while (st < ed) {
            int mid = (st + ed + 1) / 2;
            int p1 = treeAncestor.getKthAncestor(n1, transLateFromSeed(d1, mid)), p2 = treeAncestor.getKthAncestor(n2, transLateFromSeed(d2, mid));
            if (p1 != p2) {
                ed = mid - 1;
            } else {
                st = mid;
            }
        }
        int commonParent = treeAncestor.getKthAncestor(n1, transLateFromSeed(d1, st));
        int[] cal = new int[26];
        for (int i = 0; i < 26; ++i) {
            cal[i] += mem[n1][i];
            cal[i] += mem[n2][i];
            cal[i] -= 2 * mem[commonParent][i];
        }
        int max = 0, sum = 0;
        for (int i = 0; i < 26; ++i) {
            sum += cal[i];
            max = Math.max(cal[i], max);
        }
        return sum - max;
    }

    private int transLateFromSeed(int totalDepth, int needDepth) {
        return totalDepth - needDepth;
    }
}

Leave a Reply

Your email address will not be published. Required fields are marked *