周赛 331 题解

plus2047 于 2023-02-05 发布

本周周赛中规中矩,没有什么非常特别的解法。

1.

image

这道题目作为第一题来说比较难了,利用优先队列,每一次拿出一个最大的数,然后把平方根按照要求放回去即可。

class Solution {
public:
    long long pickGifts(vector<int>& gifts, int k) {
        long long total = accumulate(gifts.begin(), gifts.end(), 0LL);
        priority_queue<int> pq(gifts.begin(), gifts.end());
        while(k--) {
            int t = pq.top();
            pq.pop();
            int _t = sqrt(t);
            pq.push(_t);
            total -= t - _t;
        }
        return total;
    }
};

2.

image

可以预计算好哪些 word 是满足需求的。为了应付快速查询,可以计算一个前缀数组,保存前 i 个位置满足要求的 word 总数。

class Solution:
    def vowelStrings(self, words: List[str], queries: List[List[int]]) -> List[int]:
        n = len(words)
        # 前缀数组,pre[i+1] 是前 i+1 个 word 中满足要求的总数
        pre = [0] * (n + 1)
        for i in range(n):
            # 类似于前缀和
            pre[i + 1] = pre[i]
            if words[i][0] in "aeiou" and words[i][-1] in "aeiou":
                pre[i + 1] += 1
        return [pre[p[1] + 1] - pre[p[0]] for p in queries]

3.

image image

这个问题可以二分法+贪心来求解。基本思路是,二分结果,也即二分的尝试某个 capability 是否可行。capability 给定之后,哪些房子可以取就可以确定了,只要能取的房子大于 k 即可。另一个要求是不能取近邻的房子。很容易验证,即使有这个限制条件,从左到右贪心的选取房子一定能取到最多的房子(也即只要 capability 满足并且前一个房子没有选取,就一定选取这个房子)。这样,我们只需要扫一遍就能确认某个 capability 下最多能取多少房子,然后就能确定这个 capability 是否可行。如此,借助二分法就能求得 min capability.

class Solution {
public:
    int minCapability(vector<int>& nums, int k) {
        auto p = minmax_element(nums.begin(), nums.end());
        int left = *p.first, right = *p.second;
        while(left < right) {
            int mid = (left + right) / 2;
            bool last = false;
            int taken = 0;
            for(int x: nums) {
                if(x <= mid and not last) {
                    taken++;
                    last = true;
                } else {
                    last = false;
                }
            }
            if(taken >= k) {
                right = mid;
            } else {
                left = mid + 1;
            }
        }
        return left;
    }
};

4.

image

这个问题并不是特别难,而且 LeetCode 前几周有一次双周赛有过一道思路类似的题目,大大降低了本题难度。

首先,我们是很容易验证能否另两个篮子相等的,只要两个篮子中所有元素能两两配对即可。也很容易求出每个篮子最终的目标 target,也即交换结束后篮子中的元素,这只要将所有元素均分到两个篮子中即可。

我们考察 basket1 (考察 basket2 结果是一样的),已知其最终 target 的情况下,我们可以很容易做个比对,求出 basket1 中需要换出去哪些元素,这些元素会换进来哪些元素(尽管我们是跟 target 比较的,但这些元素是从 basket2 中交换进来的)。很直观的方案是两两配对,这种方案下,总 cost 是需要交换的所有元素中最小的一半元素的和,因为我们总是可以用较小的一半元素去配对较大的一半元素。但实际上,如果用最小的元素做「跳板」,可能 cost 更小。更具体的,最小元素当然在两个篮子中都存在,我们可以将需要交换的两个元素分别与对方的最小元素交换,就能间接交换这两个元素了。

class Solution:
    def minCost(self, basket1: List[int], basket2: List[int]) -> int:
        
        n = len(basket1)

        overall = sorted(basket1 + basket2)
        # 检查是否所有元素能够两两配对
        for i in range(n):
            if overall[i * 2] != overall[i * 2 + 1]:
                return -1
        
        # 两个篮子最终要达成的目标状态
        target = Counter(overall[i * 2] for i in range(n))
        # 篮子中的最小值
        minval = min(target)

        count1 = Counter(basket1)
        # 两个篮子中共同的元素:
        common = count1 & target
        # 两个篮子中需要交换的元素:
        diff1, diff2 = count1 - common, target - common
        # 这些元素两两配对需要的 cost:
        costs = sorted((diff1 + diff2).elements())
        costs = costs[:len(costs) // 2]
        # 最终 cost 是两两配对的 cost1 或者使用最小元素做跳板的 cost2 中较小的一个
        return sum(min(x, minval * 2) for x in costs)