LeetCode --- 419周赛

时间:2024-10-19 11:12:51

题目列表

3318. 计算子数组的 x-sum I

3319. 第 K 大的完美二叉子树的大小

3320. 统计能获胜的出招序列数

3321. 计算子数组的 x-sum II

一、第 K 大的完美二叉子树的大小

求第 k 大的子树的大小,本质就是统计树中的完美二叉子树的大小,排序后返回第 k 个即可,关键是如何判断一棵树是完美二叉树?从递归的角度来思考:如果一棵树的左子树和右子树都是完美二叉树,那么它是完美二叉树吗?显然不是,我们还需要让它们的高度相同(即结点个数相同),所以我们需要用pair返回两个值,一个表示树的高度(或者节点数),一个表示是否是完美二叉树,代码如下

class Solution {
    vector<int> ans;
    pair<int,bool> dfs(TreeNode* root){
        if(root == nullptr)
            return {0, true};
        auto [hl, isl] = dfs(root->left);
        auto [hr, isr] = dfs(root->right);
        bool flag = hl == hr && isl && isr;
        if(flag){
            ans.push_back((1<<(hl+1))-1); // 完美二叉树的大小为2^h-1
        }
        return {max(hl, hr) + 1, flag};
    }
public:
    int kthLargestPerfectSubtree(TreeNode* root, int k) {
        dfs(root);
        int n = ans.size();
        ranges::sort(ans, greater<>());
        return n < k ? -1 : ans[k-1];
    }
};

// 当然也可以对状态进行压缩,用-1表示不是完美二叉树,这样只要返回一个值即可
class Solution {
    vector<int> ans;
    int dfs(TreeNode* root){
        if(root == nullptr) // 预防空树的情况
            return 0;
        int hl = dfs(root->left);
        int hr = dfs(root->right);
        bool flag = hl == hr && hl >= 0 && hr >= 0;
        if(flag){
            ans.push_back((1<<(hl+1))-1); // 完美二叉树的大小为2^h-1
        }
        return flag ? hl + 1 : -1;
    }
public:
    int kthLargestPerfectSubtree(TreeNode* root, int k) {
        dfs(root);
        int n = ans.size();
        ranges::sort(ans, greater<>());
        return n < k ? -1 : ans[k-1];
    }
};

二、统计能获胜的出招序列数

上面的游戏规则,可以套用剪刀石头布去理解,现在给你Alice的出招序列,让你去查找Bob有多少种出招序列能赢,并且Bob不能连续两次出一样的。

这题很明显是个dp问题,我们令dfs(i, j, k)表示前 i 次战斗后,Alice和Bob的分值之差为 j 的所有可能排列数,其中 k 表示Bob第i-1次的出招(注意:这里我们只关注Alice和Bob的分差即可,因为我们只需要知道输赢,而不关注他们各自得了多少分),代码如下

class Solution {
    const string t = "FWE";
    const int MOD = 1e9 + 7;
public:
    int countWinningSequences(string s) {
        int n = s.size();
        int memo[n][2001][4];
        memset(memo, -1, sizeof(memo));
        // 还能通过提前判断Bob必赢/必输来进行减枝,有兴趣可以试试
        function<int(int,int,int)> dfs = [&](int i, int j, int k)->int{
            if(i < 0) return j > 1000;// 为了让记忆化的下标不为负数,给了个1000作为偏移量
            if(memo[i][j][k]!=-1) return memo[i][j][k];
            int pos = t.find(s[i]);
            int res = 0;
            if(k != pos)
                res = (res + dfs(i - 1, j, pos))%MOD; // 平局
            if(k != (pos + 1) % 3)
                res = (res + dfs(i - 1, j + 1, (pos + 1)%3))%MOD; // Bob赢
            if(k != (pos + 2) % 3)
                res = (res + dfs(i - 1, j - 1, (pos + 2)%3))%MOD; // Alice赢
            return memo[i][j][k] = res;
        };
        return dfs(n - 1, 1000, 3);
    }
};

三、计算子数组的 x-sum I & II

本题的关键在于如何维护长度为 k 的子数组中出现次数最多的前 x 个元素,及它们的和。

这里提供一个思路:同时维护子数组中出现次数最多的前 x 个元素,和随着子数组的移动可能出现在前 x 个元素中的数字,简而言之,就是维护一个当前状态的前x个元素集合,和一个备用元素集合,这样只要当前状态的前x个元素集合缺少元素,就从备用元素集合中拿就行。

满足上诉要求的数据结构就是对顶堆,用大堆和小堆模拟上诉行为即可,但是库中的堆不支持任意的删除元素(该问题可以结合懒删除进行解决),这里我们用 set 来代替堆实现,代码如下

class Solution {
public:
    vector<long long> findXSum(vector<int>& nums, int k, int x) {
        int n = nums.size();
        unordered_map<int,int>mp;
        set<pair<int, int>> stL, stR; // stL --- "现任" stR --- "备胎"
        long long s = 0;
        auto remove = [&](int i){
            auto it = stR.find({mp[i], i});
            if(it != stR.end()){
                stR.erase(it);
                return;
            }
            it = stL.find({mp[i], i});
            if(it == stL.end()) return;
            s -= 1LL*(it->first)*(it->second);
            stL.erase(it);
            if(stR.size()){
                it = prev(stR.end());
                stL.insert(*it);
                s += 1LL*(it->first)*(it->second);
                stR.erase(it);
            }
        };
        auto add = [&](int i){
            if(stL.size() < x){
                stL.insert({mp[i], i});
                s += 1LL * mp[i] * i;
                return;
            }
            stR.insert({mp[i], i});
            auto mx = prev(stR.end());
            auto mn = stL.begin();
            if(*mx > *mn){
                s += 1LL*(mx->first)*(mx->second);
                s -= 1LL*(mn->first)*(mn->second);
                stR.insert(*mn);
                stL.erase(mn);
                stL.insert(*mx);
                stR.erase(mx);
            }
        };
        vector<long long> ans(n - k + 1);
        for(int l = 0, r = 0; r < n; r++){
            remove(nums[r]);
            ++mp[nums[r]];
            add(nums[r]);
            if(r - l + 1 > k){
                remove(nums[l]);
                --mp[nums[l]];
                add(nums[l]);
                l++;
            }
            if(r - l + 1 == k) ans[l] = s;
        }
        return ans;
    }
};