Facebook Pixel

2212. Maximum Points in an Archery Competition

MediumBit ManipulationArrayBacktrackingEnumeration
Leetcode Link

Problem Description

Alice and Bob are competing in an archery competition where they take turns shooting arrows at a target with 12 scoring sections labeled from 0 to 11.

Competition Rules:

  1. Alice shoots numArrows arrows first, then Bob shoots the same number of arrows
  2. The scoring works as follows:
    • For each section k (from 0 to 11), if Alice shot a_k arrows and Bob shot b_k arrows:
      • If a_k >= b_k, Alice wins that section and gets k points
      • If a_k < b_k, Bob wins that section and gets k points
      • If both shot 0 arrows (a_k == b_k == 0), nobody gets the points for that section

Your Task: Given numArrows (the total number of arrows Bob has) and aliceArrows (an array of length 12 showing how many arrows Alice shot in each section), determine how Bob should distribute his arrows to maximize his total score.

Return an array bobArrows of length 12 where bobArrows[i] represents the number of arrows Bob should shoot in section i. The sum of all values in bobArrows must equal numArrows.

Example:

  • If Alice shot 2 arrows in section 11 and Bob also shoots 2 arrows there, Alice wins (since ties go to Alice) and gets 11 points
  • If Alice shot 0 arrows in section 11 and Bob shoots 2 arrows there, Bob wins and gets 11 points

Note: If there are multiple ways for Bob to achieve the maximum score, return any valid solution.

Flowchart Walkthrough

First, let's pin down the algorithm using the Flowchart. Here's a step-by-step walkthrough:

Is it a graph?

  • No: The problem doesn't involve nodes and edges or graph traversal. It's about distributing arrows across scoring sections.

Need to solve for kth smallest/largest?

  • No: We're looking for the maximum total score Bob can achieve, not the kth smallest or largest element.

Involves Linked Lists?

  • No: The problem uses arrays to represent arrow distributions, not linked lists.

Does the problem have small constraints?

  • Yes: The problem has very small constraints - only 12 scoring sections (0 to 11). This means we only need to consider 2^12 = 4096 possible combinations of which sections Bob should try to win.

Brute force / Backtracking?

  • Yes: Given the small constraints, we can use brute force to enumerate all possible ways Bob can choose which sections to compete for. For each combination, we check if Bob has enough arrows and calculate his score.

Conclusion: The flowchart correctly leads us to a brute force/backtracking approach. Since there are only 12 sections, we can use binary enumeration (trying all 2^12 possible subsets) to determine which sections Bob should target. This is feasible because we're exploring a small search space where each section can either be "won by Bob" or "conceded to Alice."

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The key insight is that Bob needs to be strategic about which sections to compete for. Since Bob shoots after Alice, he knows exactly how many arrows Alice used for each section. This gives Bob a huge advantage - he can choose his battles wisely.

For any section k, Bob has two choices:

  1. Compete for it: Shoot aliceArrows[k] + 1 arrows to win k points
  2. Ignore it: Shoot 0 arrows and let Alice have those points

Why would Bob ever shoot exactly aliceArrows[k] arrows? That would waste arrows without gaining any points (ties go to Alice). So Bob should either outbid Alice by exactly 1 arrow or not compete at all.

Since there are only 12 sections, Bob has 2^12 = 4096 possible strategies - each represented by which subset of sections he chooses to win. This is small enough to check every possibility!

We can think of each strategy as a binary number where bit i indicates whether Bob competes for section i. For example, if the binary representation is 101000000000, Bob competes for sections 0 and 2 only.

For each strategy:

  1. Calculate how many arrows Bob needs: sum of (aliceArrows[i] + 1) for all sections Bob wants to win
  2. If Bob has enough arrows, calculate his total score: sum of all section values he wins
  3. Track the strategy that gives the maximum score

After finding the best strategy, we distribute Bob's arrows accordingly. Any leftover arrows can be placed anywhere - we choose section 0 for simplicity since it doesn't affect the score calculation (Bob already decided which sections to win).

This brute force approach works perfectly here because the small problem size makes it computationally feasible to explore the entire solution space.

Learn more about Backtracking patterns.

Solution Approach

The implementation uses binary enumeration to explore all possible strategies for Bob. Here's how the solution works:

1. Binary Enumeration Setup

  • We iterate through all numbers from 1 to 2^m - 1 (where m = 12 is the number of sections)
  • Each number mask represents a strategy where bit i indicates if Bob competes for section i
  • We use st to store the best strategy found and mx to track the maximum score

2. Evaluating Each Strategy For each mask, we calculate:

  • cnt: Total arrows needed for this strategy
  • s: Total score Bob would get
for i, x in enumerate(aliceArrows):
    if mask >> i & 1:  # Check if bit i is set
        s += i         # Add section value to score
        cnt += x + 1   # Need Alice's arrows + 1 to win

The bit operation mask >> i & 1 checks if the i-th bit is set:

  • mask >> i shifts the bits right by i positions
  • & 1 checks if the least significant bit is 1

3. Finding the Best Strategy

  • Only consider strategies where cnt <= numArrows (Bob has enough arrows)
  • Update mx and st when we find a better score

4. Constructing the Final Answer Once we have the optimal strategy st:

ans = [0] * m
for i, x in enumerate(aliceArrows):
    if st >> i & 1:
        ans[i] = x + 1      # Shoot just enough to win
        numArrows -= ans[i] # Deduct used arrows
ans[0] += numArrows        # Place remaining arrows in section 0

Why start enumeration from 1?

  • mask = 0 means Bob doesn't compete for any section (scores 0 points)
  • Starting from 1 ensures Bob tries to win at least one section

Time Complexity: O(2^m × m) where m = 12. We check 2^12 strategies, and for each, we iterate through 12 sections.

Space Complexity: O(m) for storing the answer array.

This brute force approach is optimal for this problem size - it guarantees finding the maximum score while remaining efficient due to the small constraints.

Ready to land your dream job?

Unlock your dream job with a 3-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through a small example with only 4 sections (0-3) to illustrate the solution approach:

Input:

  • numArrows = 6 (Bob has 6 arrows)
  • aliceArrows = [1, 0, 2, 1] (Alice shot 1 arrow in section 0, 0 in section 1, 2 in section 2, and 1 in section 3)

Step 1: Enumerate all strategies using binary representation

With 4 sections, we have 2^4 = 16 possible strategies. Let's examine a few key ones:

Binary (mask)Sections Bob competes forArrows neededBob's scoreValid?
0001[0]1+1=20
0010[1]0+1=11
0100[2]2+1=32
1000[3]1+1=23
0110[1,2](0+1)+(2+1)=41+2=3
1010[1,3](0+1)+(1+1)=31+3=4
1100[2,3](2+1)+(1+1)=52+3=5
1110[1,2,3](0+1)+(2+1)+(1+1)=61+2+3=6
1111[0,1,2,3](1+1)+(0+1)+(2+1)+(1+1)=80+1+2+3=6✗ (needs 8 arrows, Bob only has 6)

Step 2: Find the maximum score

From valid strategies, mask = 1110 (binary) gives the maximum score of 6 points.

  • Bob competes for sections 1, 2, and 3
  • Uses exactly 6 arrows: 1 for section 1, 3 for section 2, 2 for section 3
  • Wins 1+2+3 = 6 points

Step 3: Construct the answer

bobArrows = [0, 0, 0, 0]  # Initialize

For mask = 1110:
- Bit 0 is 0: Bob doesn't compete → bobArrows[0] = 0
- Bit 1 is 1: Bob competes → bobArrows[1] = aliceArrows[1] + 1 = 0 + 1 = 1
- Bit 2 is 1: Bob competes → bobArrows[2] = aliceArrows[2] + 1 = 2 + 1 = 3  
- Bit 3 is 1: Bob competes → bobArrows[3] = aliceArrows[3] + 1 = 1 + 1 = 2

Result: bobArrows = [0, 1, 3, 2]

Verification:

  • Section 0: Alice shot 1, Bob shot 0 → Alice wins, gets 0 points
  • Section 1: Alice shot 0, Bob shot 1 → Bob wins, gets 1 point
  • Section 2: Alice shot 2, Bob shot 3 → Bob wins, gets 2 points
  • Section 3: Alice shot 1, Bob shot 2 → Bob wins, gets 3 points

Alice's total: 0 points
Bob's total: 1 + 2 + 3 = 6 points ✓

This example shows how the algorithm systematically checks all possible strategies, calculates the required arrows and potential score for each, and selects the strategy that maximizes Bob's score while staying within his arrow budget.

Solution Implementation

1class Solution:
2    def maximumBobPoints(self, numArrows: int, aliceArrows: List[int]) -> List[int]:
3        # Initialize variables to track the best configuration
4        best_mask = 0  # Bitmask representing which sections Bob should win
5        max_score = 0  # Maximum score Bob can achieve
6        num_sections = len(aliceArrows)
7      
8        # Try all possible combinations of sections Bob could win (2^n possibilities)
9        # Start from 1 to skip the case where Bob wins no sections
10        for mask in range(1, 1 << num_sections):
11            arrows_needed = 0  # Total arrows Bob needs for this combination
12            total_score = 0    # Total score Bob gets for this combination
13          
14            # Check each section to see if Bob should win it (based on current mask)
15            for section_index, alice_arrows_in_section in enumerate(aliceArrows):
16                # Check if the bit at position section_index is set in the mask
17                if mask >> section_index & 1:
18                    # Bob wins this section, so he gets the points
19                    total_score += section_index
20                    # Bob needs one more arrow than Alice to win this section
21                    arrows_needed += alice_arrows_in_section + 1
22          
23            # Check if this combination is valid and better than previous best
24            if arrows_needed <= numArrows and total_score > max_score:
25                max_score = total_score
26                best_mask = mask
27      
28        # Build the result array based on the best configuration found
29        result = [0] * num_sections
30        for section_index, alice_arrows_in_section in enumerate(aliceArrows):
31            # Check if Bob should win this section according to best_mask
32            if best_mask >> section_index & 1:
33                # Bob shoots one more arrow than Alice to win this section
34                result[section_index] = alice_arrows_in_section + 1
35                numArrows -= result[section_index]
36      
37        # Put any remaining arrows in section 0 (doesn't affect score)
38        result[0] += numArrows
39      
40        return result
41
1class Solution {
2    public int[] maximumBobPoints(int numArrows, int[] aliceArrows) {
3        // Initialize variables to track the best strategy
4        int bestMask = 0;  // Bitmask representing which sections Bob should win
5        int maxPoints = 0;  // Maximum points Bob can achieve
6        int numSections = aliceArrows.length;  // Number of scoring sections (typically 12)
7      
8        // Try all possible combinations of sections Bob could win (2^n possibilities)
9        // Start from 1 to skip the case where Bob wins no sections
10        for (int mask = 1; mask < (1 << numSections); ++mask) {
11            int arrowsNeeded = 0;  // Total arrows needed for this combination
12            int totalPoints = 0;   // Total points earned with this combination
13          
14            // Check each section to see if it's included in this combination
15            for (int section = 0; section < numSections; ++section) {
16                // Check if the current section is set in the bitmask
17                if ((mask >> section & 1) == 1) {
18                    // Add the section's point value to total
19                    totalPoints += section;
20                    // Bob needs one more arrow than Alice to win this section
21                    arrowsNeeded += aliceArrows[section] + 1;
22                }
23            }
24          
25            // Update the best strategy if this combination is valid and better
26            if (arrowsNeeded <= numArrows && totalPoints > maxPoints) {
27                maxPoints = totalPoints;
28                bestMask = mask;
29            }
30        }
31      
32        // Build the result array based on the best strategy found
33        int[] result = new int[numSections];
34      
35        // Allocate arrows to win the sections in the best strategy
36        for (int section = 0; section < numSections; ++section) {
37            if ((bestMask >> section & 1) == 1) {
38                // Bob shoots one more arrow than Alice to win this section
39                result[section] = aliceArrows[section] + 1;
40                numArrows -= result[section];
41            }
42        }
43      
44        // Put any remaining arrows in section 0 (doesn't affect scoring)
45        result[0] += numArrows;
46      
47        return result;
48    }
49}
50
1class Solution {
2public:
3    vector<int> maximumBobPoints(int numArrows, vector<int>& aliceArrows) {
4        int bestMask = 0;           // Bitmask representing the best strategy for Bob
5        int maxScore = 0;            // Maximum score Bob can achieve
6        int numSections = aliceArrows.size();  // Number of scoring sections (typically 12, from 0 to 11)
7      
8        // Try all possible combinations of sections Bob can win (2^n possibilities)
9        // Each bit in mask represents whether Bob should win that section
10        for (int mask = 1; mask < (1 << numSections); ++mask) {
11            int arrowsNeeded = 0;    // Total arrows needed for this strategy
12            int currentScore = 0;    // Score achieved with this strategy
13          
14            // Check each section to see if Bob should win it (based on current mask)
15            for (int section = 0; section < numSections; ++section) {
16                if ((mask >> section) & 1) {  // If bit is set, Bob wins this section
17                    currentScore += section;  // Add section value to score
18                    arrowsNeeded += aliceArrows[section] + 1;  // Need 1 more arrow than Alice
19                }
20            }
21          
22            // Update best strategy if this one is valid and better
23            if (arrowsNeeded <= numArrows && currentScore > maxScore) {
24                maxScore = currentScore;
25                bestMask = mask;
26            }
27        }
28      
29        // Construct Bob's arrow distribution based on the best strategy
30        vector<int> bobArrows(numSections, 0);
31      
32        // Allocate arrows to win the sections in the best strategy
33        for (int section = 0; section < numSections; ++section) {
34            if ((bestMask >> section) & 1) {  // If Bob should win this section
35                bobArrows[section] = aliceArrows[section] + 1;  // Use 1 more than Alice
36                numArrows -= bobArrows[section];  // Deduct from remaining arrows
37            }
38        }
39      
40        // Place all remaining arrows in section 0 (doesn't affect score)
41        bobArrows[0] += numArrows;
42      
43        return bobArrows;
44    }
45};
46
1/**
2 * Calculates the optimal arrow distribution for Bob to maximize his points against Alice
3 * @param numArrows - Total number of arrows Bob has
4 * @param aliceArrows - Array where aliceArrows[i] is the number of arrows Alice shot at section i
5 * @returns Array representing the number of arrows Bob should shoot at each section
6 */
7function maximumBobPoints(numArrows: number, aliceArrows: number[]): number[] {
8    // Track the best state (bitmask) and maximum score achievable
9    let bestStateMask: number = 0;
10    let maxScore: number = 0;
11    const numSections: number = aliceArrows.length;
12  
13    // Iterate through all possible combinations of sections Bob can win
14    // Using bitmask: 1 means Bob wins that section, 0 means he doesn't compete
15    for (let mask = 1; mask < (1 << numSections); mask++) {
16        let arrowsNeeded: number = 0;
17        let currentScore: number = 0;
18      
19        // Check each section in the current combination
20        for (let section = 0; section < numSections; section++) {
21            // Check if current section is included in this combination (bit is set)
22            if ((mask >> section) & 1) {
23                // To win section i, Bob needs aliceArrows[i] + 1 arrows
24                arrowsNeeded += aliceArrows[section] + 1;
25                // Add the section value to the score
26                currentScore += section;
27            }
28        }
29      
30        // Update best solution if this combination is valid and better
31        if (arrowsNeeded <= numArrows && currentScore > maxScore) {
32            maxScore = currentScore;
33            bestStateMask = mask;
34        }
35    }
36  
37    // Build the result array based on the best state found
38    const result: number[] = Array(numSections).fill(0);
39  
40    // Distribute arrows according to the best strategy
41    for (let section = 0; section < numSections; section++) {
42        if ((bestStateMask >> section) & 1) {
43            // Bob wins this section by shooting exactly one more arrow than Alice
44            result[section] = aliceArrows[section] + 1;
45            numArrows -= result[section];
46        }
47    }
48  
49    // Place any remaining arrows in section 0 (worth 0 points, so doesn't affect score)
50    result[0] += numArrows;
51  
52    return result;
53}
54

Time and Space Complexity

Time Complexity: O(2^m × m), where m is the length of aliceArrows.

The algorithm uses a bitmask approach to enumerate all possible subsets of sections Bob can win. The outer loop iterates through 2^m - 1 different masks (from 1 to 2^m - 1). For each mask, the inner loop iterates through all m sections to:

  • Calculate the total score s if Bob wins those sections
  • Count the minimum arrows cnt needed to win those sections

This gives us O(2^m) iterations for the outer loop, and each iteration performs O(m) work in the inner loop, resulting in O(2^m × m) total time complexity.

Additionally, after finding the optimal mask, there's another O(m) loop to construct the answer array, but this doesn't change the overall complexity.

Space Complexity: O(1) (excluding the output array).

The algorithm uses only a constant amount of extra space:

  • Variables st, mx, m for tracking the best solution
  • Variables mask, cnt, s for iteration and calculation
  • Variable i, x for loop iterations

The answer array ans of size m is created for the output, but as per convention, we don't count the space required for the output in space complexity analysis. Therefore, the space complexity is O(1).

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Forgetting to Handle Remaining Arrows

One of the most common mistakes is not properly distributing leftover arrows after allocating the minimum needed to win selected sections.

Pitfall Example:

# WRONG: Forgetting to place remaining arrows
result = [0] * num_sections
for section_index, alice_arrows_in_section in enumerate(aliceArrows):
    if best_mask >> section_index & 1:
        result[section_index] = alice_arrows_in_section + 1
        numArrows -= result[section_index]
# Missing: result[0] += numArrows
return result  # Sum of arrows might be less than numArrows!

Why This Matters:

  • The problem requires Bob to use exactly numArrows arrows
  • If Bob's optimal strategy uses fewer arrows than available, the remaining must still be shot
  • Placing them in section 0 is conventional (doesn't affect the score since 0 points)

2. Starting Binary Enumeration from 0

Another subtle issue is including mask = 0 in the enumeration, which represents Bob not competing for any section.

Pitfall Example:

# POTENTIALLY PROBLEMATIC: Starting from 0
for mask in range(0, 1 << num_sections):  # Includes mask = 0
    # When mask = 0, Bob wins no sections

Why This Could Be an Issue:

  • When mask = 0, Bob doesn't compete for any section and scores 0 points
  • While technically valid, it adds an unnecessary iteration
  • More importantly, if you don't initialize max_score = 0 properly, this could cause logic errors

3. Incorrect Bit Manipulation

Misunderstanding the bit operations can lead to checking wrong sections.

Pitfall Example:

# WRONG: Using wrong bit operation
if mask & (1 << section_index):  # Different but equivalent - be consistent
    # vs
if mask >> section_index & 1:    # The approach used in solution

# WRONG: Off-by-one in bit shifting
if mask >> (section_index + 1) & 1:  # Checks wrong section!

4. Not Validating Arrow Count Before Updating Best Solution

Forgetting to check if Bob has enough arrows for a strategy before considering it as the best.

Pitfall Example:

# WRONG: Not checking arrow constraint
for mask in range(1, 1 << num_sections):
    arrows_needed = 0
    total_score = 0
  
    for section_index, alice_arrows_in_section in enumerate(aliceArrows):
        if mask >> section_index & 1:
            total_score += section_index
            arrows_needed += alice_arrows_in_section + 1
  
    # WRONG: Updates best without checking if arrows_needed <= numArrows
    if total_score > max_score:  # Missing arrow count validation!
        max_score = total_score
        best_mask = mask

Correct Approach: Always ensure arrows_needed <= numArrows before considering a strategy as valid.

Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

What's the output of running the following function using input 56?

1KEYBOARD = {
2    '2': 'abc',
3    '3': 'def',
4    '4': 'ghi',
5    '5': 'jkl',
6    '6': 'mno',
7    '7': 'pqrs',
8    '8': 'tuv',
9    '9': 'wxyz',
10}
11
12def letter_combinations_of_phone_number(digits):
13    def dfs(path, res):
14        if len(path) == len(digits):
15            res.append(''.join(path))
16            return
17
18        next_number = digits[len(path)]
19        for letter in KEYBOARD[next_number]:
20            path.append(letter)
21            dfs(path, res)
22            path.pop()
23
24    res = []
25    dfs([], res)
26    return res
27
1private static final Map<Character, char[]> KEYBOARD = Map.of(
2    '2', "abc".toCharArray(),
3    '3', "def".toCharArray(),
4    '4', "ghi".toCharArray(),
5    '5', "jkl".toCharArray(),
6    '6', "mno".toCharArray(),
7    '7', "pqrs".toCharArray(),
8    '8', "tuv".toCharArray(),
9    '9', "wxyz".toCharArray()
10);
11
12public static List<String> letterCombinationsOfPhoneNumber(String digits) {
13    List<String> res = new ArrayList<>();
14    dfs(new StringBuilder(), res, digits.toCharArray());
15    return res;
16}
17
18private static void dfs(StringBuilder path, List<String> res, char[] digits) {
19    if (path.length() == digits.length) {
20        res.add(path.toString());
21        return;
22    }
23    char next_digit = digits[path.length()];
24    for (char letter : KEYBOARD.get(next_digit)) {
25        path.append(letter);
26        dfs(path, res, digits);
27        path.deleteCharAt(path.length() - 1);
28    }
29}
30
1const KEYBOARD = {
2    '2': 'abc',
3    '3': 'def',
4    '4': 'ghi',
5    '5': 'jkl',
6    '6': 'mno',
7    '7': 'pqrs',
8    '8': 'tuv',
9    '9': 'wxyz',
10}
11
12function letter_combinations_of_phone_number(digits) {
13    let res = [];
14    dfs(digits, [], res);
15    return res;
16}
17
18function dfs(digits, path, res) {
19    if (path.length === digits.length) {
20        res.push(path.join(''));
21        return;
22    }
23    let next_number = digits.charAt(path.length);
24    for (let letter of KEYBOARD[next_number]) {
25        path.push(letter);
26        dfs(digits, path, res);
27        path.pop();
28    }
29}
30

Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More