【LeetCode】第 331 场周赛题解

55 阅读5分钟

6348. 从数量最多的堆取走礼物

给你一个整数数组 gifts ,表示各堆礼物的数量。每一秒,你需要执行以下操作:

  • 选择礼物数量最多的那一堆。
  • 如果不止一堆都符合礼物数量最多,从中选择任一堆即可。
  • 选中的那一堆留下平方根数量的礼物(向下取整),取走其他的礼物。

返回在 k 秒后剩下的礼物数量

示例 1:

输入: gifts = [25,64,9,4,100], k = 4 输出: 29 解释: 按下述方式取走礼物:

  • 在第一秒,选中最后一堆,剩下 10 个礼物。
  • 接着第二秒选中第二堆礼物,剩下 8 个礼物。
  • 然后选中第一堆礼物,剩下 5 个礼物。
  • 最后,再次选中最后一堆礼物,剩下 3 个礼物。 最后剩下的礼物数量分别是 [5,8,9,4,3] ,所以,剩下礼物的总数量是 29 。

示例 2:

输入: gifts = [1,1,1,1], k = 4
输出: 4
解释:
在本例中,不管选中哪一堆礼物,都必须剩下 1 个礼物。 
也就是说,你无法获取任一堆中的礼物。 
所以,剩下礼物的总数量是 4 。

提示:

  • 1 <= gifts.length <= 103
  • 1 <= gifts[i] <= 109
  • 1 <= k <= 103

题解:堆

class Solution {
public:
    long long pickGifts(vector<int>& g, int k) {
        priority_queue<int, vector<int>, less<int>> q;
        for(auto& c : g) q.push(c);
        while(k-- && !q.empty()){
            int t = q.top();
            q.pop();
            t = (int)sqrt(t);
            q.push(t);
        }
        long long res = 0;
        while(!q.empty()){
            res += q.top();
            q.pop();
        }
        return res;
    }
};

6347. 统计范围内的元音字符串数

给你一个下标从 0 开始的字符串数组 words 以及一个二维整数数组 queries 。

每个查询 queries[i] = [li, ri] 会要求我们统计在 words 中下标在 li 到 ri 范围内(包含 这两个值)并且以元音开头和结尾的字符串的数目。

返回一个整数数组,其中数组的第 i 个元素对应第 i 个查询的答案。

注意: 元音字母是 'a''e''i''o' 和 'u' 。

 

示例 1:

输入: words = ["aba","bcb","ece","aa","e"], queries = [[0,2],[1,4],[1,1]]
输出: [2,3,0]
解释: 以元音开头和结尾的字符串是 "aba""ece""aa""e" 。
查询 [0,2] 结果为 2(字符串 "aba""ece")。
查询 [1,4] 结果为 3(字符串 "ece""aa""e")。
查询 [1,1] 结果为 0 。
返回结果 [2,3,0] 。

示例 2:

输入: words = ["a","e","i"], queries = [[0,2],[0,1],[2,2]]
输出: [3,2,1]
解释: 每个字符串都满足这一条件,所以返回 [3,2,1]

 

提示:

  • 1 <= words.length <= 105
  • 1 <= words[i].length <= 40
  • words[i] 仅由小写英文字母组成
  • sum(words[i].length) <= 3 * 105
  • 1 <= queries.length <= 105
  • 0 <= queries[j][0] <= queries[j][1] < words.length

题解:前缀和

class Solution {
public:
    vector<int> vowelStrings(vector<string>& w, vector<vector<int>>& q) {
        int n = w.size();
        vector<int> f(n+1);
        f[0] = 0;
        string s = "aeiou";
        for(int i=0; i<n; i++){
            bool flag = false;
            if(s.find(w[i][0]) != string::npos && s.find(w[i][w[i].size()-1]) != string::npos){
                flag = true;
            }
            f[i+1] = f[i] + (flag?1:0);
        }
        vector<int> res;
        for(auto c : q){
          res.push_back(f[c[1]+1]-f[c[0]]);
        }
        return res;
    }
};

6346. 打家劫舍 IV

沿街有一排连续的房屋。每间房屋内都藏有一定的现金。现在有一位小偷计划从这些房屋中窃取现金。

由于相邻的房屋装有相互连通的防盗系统,所以小偷 不会窃取相邻的房屋 。

小偷的 窃取能力 定义为他在窃取过程中能从单间房屋中窃取的 最大金额 。

给你一个整数数组 nums 表示每间房屋存放的现金金额。形式上,从左起第 i 间房屋中放有 nums[i] 美元。

另给你一个整数数组 k ,表示窃贼将会窃取的 最少 房屋数。小偷总能窃取至少 k 间房屋。

返回小偷的 最小 窃取能力。

 

示例 1:

输入: nums = [2,3,5,9], k = 2
输出: 5
解释:
小偷窃取至少 2 间房屋,共有 3 种方式:
- 窃取下标 02 处的房屋,窃取能力为 max(nums[0], nums[2]) = 5 。
- 窃取下标 03 处的房屋,窃取能力为 max(nums[0], nums[3]) = 9 。
- 窃取下标 13 处的房屋,窃取能力为 max(nums[1], nums[3]) = 9 。
因此,返回 min(5, 9, 9) = 5

示例 2:

输入: nums = [2,7,9,3,1], k = 2
输出: 2
解释: 共有 7 种窃取方式。窃取能力最小的情况所对应的方式是窃取下标 0 和 4 处的房屋。返回 max(nums[0], nums[4]) = 2 。

 

提示:

  • 1 <= nums.length <= 105
  • 1 <= nums[i] <= 109
  • 1 <= k <= (nums.length + 1)/2

题解:二分+dp, dp[i]=max(dp[i1],dp[i2]+1)dp[i] = max(dp[i-1], dp[i-2]+1)

class Solution {
public:
    int minCapability(vector<int>& nums, int k) {
       int n = nums.size();
        int left = nums[0], right = nums[0];
        for(int i=0; i<n; i++){
            left = min(left, nums[i]);
            right = max(right, nums[i]);
        }
        
        auto check = [&](int mx) {
            int dp0 = 0, dp1 = 0;
            for(auto x : nums){
                if(x >mx){
                    dp0 = dp1;
                }else{
                    int t = dp1;
                    dp1 = max(dp1, dp0+1);
                    dp0 = t;
                }
                if(dp1 >= k) return true;
            }
            return false;
        };
        
        while(left < right){
            int mid = (left+right)>>1;
            if(check(mid)){
                right = mid;
            }else left = mid+1;
        }
        return left;
    }
};

6345. 重排水果

你有两个果篮,每个果篮中有 n 个水果。给你两个下标从 0 开始的整数数组 basket1 和 basket2 ,用以表示两个果篮中每个水果的成本。

你希望两个果篮相等。为此,可以根据需要多次执行下述操作:

  • 选中两个下标 i 和 j ,并交换 basket1 中的第 i 个水果和 basket2 中的第 j 个水果。
  • 交换的成本是 min(basket1i,basket2j) 。

根据果篮中水果的成本进行排序,如果排序后结果完全相同,则认为两个果篮相等。

返回使两个果篮相等的最小交换成本,如果无法使两个果篮相等,则返回 **-1 **。

 

示例 1:

输入: basket1 = [4,2,2,2], basket2 = [1,4,1,2]
输出: 1
解释: 交换 basket1 中下标为 1 的水果和 basket2 中下标为 0 的水果,交换的成本为 1 。此时,basket1 = [4,1,2,2] 且 basket2 = [2,4,1,2] 。重排两个数组,发现二者相等。

示例 2:

输入: basket1 = [2,3,4,1], basket2 = [3,2,5,1]
输出: -1
解释: 可以证明无法使两个果篮相等。

 

提示:

  • basket1.length == bakste2.length
  • 1 <= basket1.length <= 105
  • 1 <= basket1i,basket2i <= 109

题解:思维题,最小值也可以用来做交换, 有 2 种交换方式,第一种直接交换,代价为 min(a[i],b[j])min(a[i], b[j]);第二种通过最小值的中间数来进行交换代价 2minv2*minv, 最终的代价为 min(min(a[i],b[j]),2minv)min(min(a[i], b[j]), 2*minv)

class Solution {
public:
    long long minCost(vector<int>& b1, vector<int>& b2) {
        unordered_map<int, int> mp, mpA, mpB;
        int n = b1.size();
        for(auto& c : b1){
            mp[c]++;
            mpA[c]++;
        }
        for(auto& c : b2){
            mp[c]++;
            mpB[c]++;
        }
        for(auto& [k, v] : mp){
            if(v%2){
                return -1;
            }
        }
        vector<int> remain;
        for(auto& [k, v] : mpA){
            int t = v, g = mp[k]/2;
            // A多余的数,需要用于交换
            if(t > g){
                for(int i=0; i<v-g; i++){
                    remain.push_back(k);
                }
            }
        }
        for(auto& [k, v] : mpB){
            int t = v, g = mp[k]/2;
            // B 多余的数,需要用于交换
            if(t > g){
                for(int i=0; i<v-g; i++) remain.push_back(k);
            }
        }
        sort(remain.begin(), remain.end());
        int minv = 1e9+5;
        for(auto& [k,v] : mp) minv = min(minv, k);
        long long res = 0;
        for(int i=0; i<remain.size()/2; i++){
            res += min(remain[i], minv*2);
        }
        return res;
        
    }
};

总结:
元宵节快乐~