【C/C++】2212. 射箭比赛中的最大得分

207 阅读4分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第26天,点击查看活动详情


题目链接:2212. 射箭比赛中的最大得分

题目描述

Alice 和 Bob 是一场射箭比赛中的对手。比赛规则如下:

  1. Alice 先射 numArrows 支箭,然后 Bob 也射 numArrows 支箭。
  2. 分数按下述规则计算:
    1. 箭靶有若干整数计分区域,范围从 011 (含 011)。
    2. 箭靶上每个区域都对应一个得分 k(范围是 011),Alice 和 Bob 分别在得分 k 区域射中 akbk 支箭。如果 ak >= bk ,那么 Alice 得 k 分。如果 ak < bk ,则 Bob 得 k
    3. 如果 ak == bk == 0 ,那么无人得到 k 分。
  • 例如,Alice 和 Bob 都向计分为 11 的区域射 2 支箭,那么 Alice 得 11 分。如果 Alice 向计分为 11 的区域射 0 支箭,但 Bob 向同一个区域射 2 支箭,那么 Bob 得 11 分。

给你整数 numArrows 和一个长度为 12 的整数数组 aliceArrows ,该数组表示 Alice 射中 011 每个计分区域的箭数量。现在,Bob 想要尽可能 最大化 他所能获得的总分。

返回数组 bobArrows ,该数组表示 Bob 射中 011 每个 计分区域的箭数量。且 bobArrows 的总和应当等于 numArrows

如果存在多种方法都可以使 Bob 获得最大总分,返回其中 任意一种 即可。

提示:

  • 1numArrows1051 \leqslant numArrows \leqslant 10^5
  • aliceArrows.length == bobArrows.length == 12
  • 0aliceArrows[i],bobArrows[i]numArrows0 \leqslant aliceArrows[i], bobArrows[i] \leqslant numArrows
  • sum(aliceArrows[i]) == numArrows

示例 1: 1647744752-kQKrXw-image.png

输入:numArrows = 9, aliceArrows = [1,1,0,1,0,0,2,1,0,1,2,0]
输出:[0,0,0,0,1,1,0,0,1,2,3,1]
解释:上表显示了比赛得分情况。
Bob 获得总分 4 + 5 + 8 + 9 + 10 + 11 = 47 。
可以证明 Bob 无法获得比 47 更高的分数。

示例 2: 1647744785-cMHzaC-image.png

输入:numArrows = 3, aliceArrows = [0,0,1,0,0,0,0,0,0,0,0,2]
输出:[0,0,0,0,0,0,0,0,1,1,1,0]
解释:上表显示了比赛得分情况。
Bob 获得总分 8 + 9 + 10 = 27 。
可以证明 Bob 无法获得比 27 更高的分数。

整理题意

题目给定 numArrows 支箭和长度为 12 的整数数组 aliceArrows(下标从 0 开始),aliceArrows[i] 表示在得分区域 i 上 Alice 的箭数,我们需要帮助 Bob 计算最大总得分,对于任意一个得分区域 i ,需要大于 aliceArrows[i] 即可得到 i 分,现在问使用 numArrows 支箭最大能够得到多少分。

如果存在多种方法都可以使 Bob 获得最大总分,返回其中 任意一种 即可。

解题思路分析

习惯性动作,首先看题目数据范围,箭的数量最大为 10510^5,得分区域为 12 个。

由于总共只有 12 个得分区域(011 分),对于每个得分区域有得分和不得分两种情况,那么总共就有 2122^{12} 种情况,总的情况数量较小,我们可以枚举所有可能的情况,分别判断每种情况对应的每个得分区域的得分情况和所需箭的总数,记录满足条件下的最大得分状态即可。

对于状态的枚举方法,可以选择 DFS 递归深搜枚举,由于总共有 2122^{12} 种情况,我们还可以想到使用 二进制枚举 的方法。对于二进制中第 i 位来说,0 表示在得分为 i 的区域中不得分,为 1 则代表在该区域得分。

枚举过程中维护最大得分以及最大得分所对应的状态,最后根据最大得分所对应的状态进行相应的答案输出即可。

具体实现

  1. 遍历 [0,212)[0, 2^{12}) ,其中每个整数的二进制表示为一种得分状态。
  2. 对于每种得分状态我们需要判断所需的最少箭数,因为需要大于 aliceArrows[i] 即可得到 i 分,所以我们为了使用尽可能少的箭术,对于第 i 个区域,我们只需要使用 aliceArrows[i] + 1 支箭即可。
  3. 判断当前状态下最少使用的箭数量是否小于给定的 numArrows,当小于 numArrows 时表示满足条件,此时记录最大得分以及得分状态。
  4. 最后根据最大得分状态进行构造答案所需的箭数数组。

需要注意的是如果还有剩余的箭,我们需要将其添加到任意区域即可。

复杂度分析

  • 时间复杂度:O(n×2n)O(n \times 2^n),其中 n 为箭靶的数量,在本题中 n = 12。所有的得分状态共有 2n2^n 种,对于单个状态,判断是否可行以及维护最大得分的时间复杂度为 O(n)O(n)
  • 空间复杂度:O(1)O(1),利用整数记录二进制状态以及最大得分情况仅需常数空间。

如果使用 DFS 递归做法,递归深度为箭靶的数量 n,需要 O(n)O(n) 的空间复杂度。

代码实现

class Solution {
public:
    vector<int> maximumBobPoints(int numArrows, vector<int>& aliceArrows) {
        int n = aliceArrows.size();
        //cnt 记录当前状态所需要的箭数量
        int cnt;
        //score 记录当前得分
        int score;
        //maxScore 记录最大得分
        int maxScore = 0;
        //state 记录最大得分情况下的 mask (二进制枚举状态)
        int state = 0;
        //枚举所有得分情况,用二进制枚举,0表示不得分,1表示得分
        for(int mask = 0; mask < (1 << n); mask++){
            cnt = 0;
            score = 0;
            for(int j = 0; j < n; j++){
                //当前状态下第j位为1表示Bob赢
                if((mask >> j) & 1){
                    //需要比Alice多一支箭
                    cnt += aliceArrows[n - j - 1] + 1;
                    //记录得分
                    score += (n - j - 1);
                }
            }
            //如果使用的箭小于总箭数,且得分更高就记录答案
            if(cnt <= numArrows && score > maxScore){
                maxScore = score;
                state = mask;
            }
        }
        //输出的答案数组
        vector<int> ans;
        ans.clear();
        ans.resize(n);
        //处理最后得到的最大分数所对应的答案
        for(int i = 0; i < n; i++){
            //该得分区域Bob获胜得分
            if((state >> i) & 1){
                //记录答案并将总的箭数减去相应的值
                ans[n - i - 1] = aliceArrows[n - i - 1] + 1;
                numArrows -= ans[n - i - 1];
            }
            //不得分区域不射箭
            else ans[n - i - 1] = 0;
        }
        //如果有剩余的箭就全部放在任意一块区域都可
        ans[0] += numArrows;
        return ans;
    }
};

总结

该题核心思路在于 枚举状态 ,采用二进制枚举的方法可以使得空间复杂度降为 O(1),在情况数量较小的情况下,我们可以使用更优的二进制枚举方法进行枚举。更一般的,二进制枚举是在状态为 2 的情况下使用,即选或不选、得分或不得分等,我们可以推广到三进制、四进制……等更多的情况下可应对更多不同的情形。


结束语

每个人的心里,都藏着一个了不起的自己。只要你不颓废、不消极,一直酝酿着乐观,培养着豁达,坚持着善良,始终朝着梦想前行,就没有到达不了的远方。