欢迎大家加QQ群:623375442,可以方便群里面交流。这周题目着实很难。第一题和第四题一样,我就只放第四题题解了。

100373. K-th Largest Perfect Subtree Size in Binary Tree

给你一棵 二叉树 的根节点 root 和一个整数k。

返回第 k 大的 完美二叉子树 的大小,如果不存在则返回 -1。

完美二叉树 是指所有叶子节点都在同一层级的树,且每个父节点恰有两个子节点。

子树 是指树中的某一个节点及其所有后代形成的树。

测试样例:

输入:root = [5,3,6,5,2,5,7,1,8,null,null,6,8], k = 2

输出:3

解释:完美二叉子树的根节点在图中以黑色突出显示。它们的大小按降序排列为 [3, 3, 1, 1, 1, 1, 1, 1]。
第 2 大的完美二叉子树的大小是 3。

解答:两个条件,一个是叶子结点深度一样,且所有每个父节点恰有两个子节点。这样我们dfs遍历这棵树,寻找到符合要求结点,然后存入一个list中。这题空间范围不大,直接排序取第k大。

class Solution {
    public int kthLargestPerfectSubtree(TreeNode root, int k) {
        List<Integer> list = new ArrayList<>();
        child(root, list);
        if (list.size() < k) {
            return -1;
        }
        Collections.sort(list, (a, b) -> (b.compareTo(a)));
        return list.get(k - 1);
    }

    private int[] child(TreeNode node, List<Integer> res) {
        if (node == null) {
            return null;
        } else if (node.left == null && node.right == null) {
            res.add(1);
            return new int[]{1,1};
        } else {
            int[] left = child(node.left, res), right = child(node.right, res);
            // 左右都是完美二叉树,且深度一致。则当前也是完美二叉树
            if (left == null || right == null || left[1] != right[1]) {
                return null;
            } else {
                int[] tmp = {left[0] + right[0] + 1, left[1] + 1};
                res.add(tmp[0]);
                return tmp;
            }
        }
    }
}

100439. Count The Number of Winning Sequences

Alice 和 Bob 正在玩一个幻想战斗游戏,游戏共有 n 回合,每回合双方各自都会召唤一个魔法生物:火龙(F)、水蛇(W)或地精(E)。每回合中,双方 同时 召唤魔法生物,并根据以下规则得分:

  • 如果一方召唤火龙而另一方召唤地精,召唤 火龙 的玩家将获得一分。
  • 如果一方召唤水蛇而另一方召唤火龙,召唤 水蛇 的玩家将获得一分。
  • 如果一方召唤地精而另一方召唤水蛇,召唤 地精 的玩家将获得一分。
  • 如果双方召唤相同的生物,那么两个玩家都不会获得分数。

给你一个字符串 s,包含 n 个字符 'F'、'W' 和 'E',代表 Alice 每回合召唤的生物序列:

  • 如果 s[i] == 'F',Alice 召唤火龙。
  • 如果 s[i] == 'W',Alice 召唤水蛇。
  • 如果 s[i] == 'E',Alice 召唤地精。

Bob 的出招序列未知,但保证 Bob 不会在连续两个回合中召唤相同的生物。如果在 n 轮后 Bob 获得的总分 严格大于 Alice 的总分,则 Bob 战胜 Alice。

返回 Bob 可以用来战胜 Alice 的不同出招序列的数量。

由于答案可能非常大,请返回答案对 10^9 + 7 取余 后的结果。

测试样例:

输入:s = "FFF"

输出:3

解释:Bob 可以通过以下 3 种出招序列战胜 Alice:"WFW"、"FWF" 或 "WEW"。注意,其他如 "WWE" 或 "EWW" 的出招序列是无效的,因为 Bob 不能在连续两个回合中使用相同的生物。

解答:动态规划,当前保存Bob最后选择的情况,和当前情况的分数。

class Solution {
    private static final int mod = 1_000_000_007;

    public int countWinningSequences(String s) {
        int len = s.length();
        int score = 2 * len + 1;

        int[][] last = new int[3][score];
        {
            int current = getCurrent(s.charAt(0));
            int win = getWin(s.charAt(0));
            for (int i = 0; i < 3; ++i) {
                if (i == current) {
                    last[i][len] = 1;
                } else if (i == win) {
                    last[i][len + 1] = 1;
                } else {
                    last[i][len - 1] = 1;
                }
            }
        }
        for (int n = 1; n < s.length(); ++n) {
            int[][] next = new int[3][score];
            int current = getCurrent(s.charAt(n));
            int win = getWin(s.charAt(n));
            for (int i = 0; i < 3; ++i) {
                for (int j = 0; j <= 2 * len; ++j) {
                    if (last[i][j] != 0) {
                        for (int other = 0; other < 3; ++other) {
                            // 不能连续相同
                            if (other == i) continue;
                            if (other == current) {
                                // 与当前Alice相同,则分数不变
                                next[other][j] = (next[other][j] + last[i][j]) % mod;
                            } else if (other == win) {
                                // 与胜利选项,则分数+1
                                next[other][j + 1] = (next[other][j + 1] + last[i][j]) % mod;
                            } else {
                                // 与失败选项,则分数+1
                                next[other][j - 1] = (next[other][j - 1] + last[i][j]) % mod;
                            }
                        }
                    }
                }
            }
            last = next;
        }
        int res = 0;
        for (int i = 0; i < 3; ++i) {
            for (int j = len + 1; j <= 2 * len; ++j) {
                res = (res + last[i][j]) % mod;
            }
        }
        return res;
    }

    private int getWin(char c) {
        return switch (c) {
            case 'F' -> 1;
            case 'W' -> 2;
            case 'E' -> 0;
            default -> -1;
        };
    }

    private int getCurrent(char c) {
        return switch (c) {
            case 'F' -> 0;
            case 'W' -> 1;
            case 'E' -> 2;
            default -> -1;
        };
    }
}

100442. Find X-Sum of All K-Long Subarrays II

给你一个由 n 个整数组成的数组 nums,以及两个整数 k 和 x。

数组的 x-sum 计算按照以下步骤进行:

  • 统计数组中所有元素的出现次数。
  • 仅保留出现次数最多的前 x 个元素的每次出现。如果两个元素的出现次数相同,则数值 较大 的元素被认为出现次数更多。
  • 计算结果数组的和。

注意,如果数组中的不同元素少于 x 个,则其 x-sum 是数组的元素总和。

返回一个长度为 n - k + 1 的整数数组 answer,其中 answer[i] 是 子数组 nums[i..i + k - 1] 的 x-sum。

子数组 是数组内的一个连续 非空 的元素序列。

测试样例:

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

输出:[6,10,12]

解释:

  • 对于子数组 [1, 1, 2, 2, 3, 4],只保留元素 1 和 2。因此,answer[0] = 1 + 1 + 2 + 2。
  • 对于子数组 [1, 2, 2, 3, 4, 2],只保留元素 2 和 4。因此,answer[1] = 2 + 2 + 2 + 4。注意 4 被保留是因为其数值大于出现其他出现次数相同的元素(3 和 1)。
  • 对于子数组 [2, 2, 3, 4, 2, 3],只保留元素 2 和 3。因此,answer[2] = 2 + 2 + 2 + 3 + 3。

解答:这题和数字流中选择最大的N个数逻辑差不多。动态变动两个红黑树。

class Solution {
    public long[] findXSum(int[] nums, int k, int x) {
        long[] res = new long[nums.length - k + 1];
        TopCount count = new TopCount(x);
        for (int i = 0; i < nums.length; ++i) {
            count.addNum(nums[i]);
            if (i >= k - 1) {
                res[i - k + 1] = count.getResult();
                count.removeNum(nums[i - k + 1]);
            }
        }
        return res;

    }
}

class TopCount {
    public TreeSet<int[]> valid, buffer;
    public HashMap<Integer, Integer> count;
    private long res;
    private int x;

    public TopCount(int x) {
        // 记录需要保存的x个数
        valid = new TreeSet<>((a, b) -> (a[1] != b[1] ? Integer.compare(a[1], b[1]) : Integer.compare(a[0], b[0])));
        // 记录需要未的x个数
        buffer = new TreeSet<>((a, b) -> (a[1] != b[1] ? Integer.compare(a[1], b[1]) : Integer.compare(a[0], b[0])));
        count = new HashMap<>();
        res = 0;
        this.x = x;
    }

    public void addNum(int num) {
        int old = count.getOrDefault(num, 0);
        count.put(num, old + 1);
        if (old > 0) {
            int[] key = {num, old};
            if (buffer.contains(key)) {
                buffer.remove(key);
            } else if (valid.contains(key)) {
                // 已经是valid的数字,就不用进行数据流变动了(增加不会造成valid的数字变成非法)
                valid.remove(key);
                res += num;
                key[1] += 1;
                valid.add(key);
                return;
            }
        }
        if (!valid.isEmpty()) {
            int[] tmp = valid.pollFirst();
            res -= (long) tmp[0] * tmp[1];
            buffer.add(tmp);
        }
        // 更新完,开始转动数据流
        buffer.add(new int[]{num, old + 1});
        while (valid.size() < x && !buffer.isEmpty()) {
            int[] tmp = buffer.pollLast();
            res += (long) tmp[0] * tmp[1];
            valid.add(tmp);
        }
    }

    public void removeNum(int num) {
        int old = count.get(num);
        if (old == 1) {
            count.remove(num);
        } else {
            count.put(num, old - 1);
        }

        int[] key = {num, old};
        if (buffer.contains(key)) {
            // 删除数字类似。不valid的数字,删除后没办法变得valid
            buffer.remove(key);
            key[1] -= 1;
            if (key[1] > 0) {
                buffer.add(key);
            }
            return;
        } else if (valid.contains(key)) {
            valid.remove(key);
            res -= (long) key[0] * key[1];
        }

        if (old - 1 > 0) {
            key[1] -= 1;
            buffer.add(key);
        }
        while (valid.size() < x && !buffer.isEmpty()) {
            int[] tmp = buffer.pollLast();
            res += (long) tmp[0] * tmp[1];
            valid.add(tmp);
        }
    }

    public long getResult() {
        return res;
    }
}

Leave a Reply

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