Facebook Pixel

2453. Destroy Sequential Targets

MediumArrayHash TableCounting
Leetcode Link

Problem Description

You have an array nums of positive integers representing targets positioned on a number line, and an integer space.

You have a machine that can destroy targets following a specific pattern. When you seed the machine with a value nums[i], it destroys all targets whose values can be expressed as nums[i] + c * space, where c is any non-negative integer (0, 1, 2, 3, ...).

For example, if you seed the machine with value 3 and space = 4, the machine will destroy targets at positions: 3 (c=0), 7 (c=1), 11 (c=2), 15 (c=3), and so on.

Your goal is to find the minimum value from nums that, when used as the seed, allows the machine to destroy the maximum possible number of targets from the array.

The key insight is that targets that have the same remainder when divided by space form a group that can be destroyed by the same seed. For instance, with space = 4, targets at positions 3, 7, 11, 15 all have remainder 3 when divided by 4, so they can all be destroyed by seeding with 3.

The solution counts how many targets belong to each remainder group using modulo operation (v % space). Then it finds the group with the most targets and returns the smallest target value from that group as the answer.

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

Intuition

The key observation is understanding what values the machine can destroy when seeded with a particular number. If we seed with value x, the machine destroys targets at positions: x, x + space, x + 2*space, x + 3*space, and so on.

Notice that all these destroyed positions follow a pattern - they all have the same remainder when divided by space. Specifically, they all have remainder x % space. This is because:

  • x % space = x % space (obviously)
  • (x + space) % space = x % space
  • (x + 2*space) % space = x % space
  • And so on...

This means we can group all targets by their remainder when divided by space. Targets in the same group can potentially be destroyed by the same seed value.

For example, with space = 5:

  • Targets 3, 8, 13, 18 all have remainder 3
  • Targets 2, 7, 12, 17 all have remainder 2

If we seed with 3, we can destroy all targets with remainder 3. If we seed with 2, we can destroy all targets with remainder 2.

Since we want to maximize the number of destroyed targets, we should find the remainder group that contains the most targets. Among all targets in that group, we choose the smallest value as our seed (since the problem asks for the minimum seed value that achieves maximum destruction).

This leads us to the solution: count targets by their value % space, find the most frequent remainder, and return the smallest target value with that remainder.

Solution Approach

The implementation uses a hash table to count the frequency of each remainder group, then finds the optimal seed value in a single pass through the array.

Step 1: Count Remainder Frequencies

We use Python's Counter to create a hash table cnt that maps each remainder to its frequency:

cnt = Counter(v % space for v in nums)

This line computes v % space for each value v in nums and counts how many times each remainder appears.

Step 2: Find the Optimal Seed

We initialize two variables:

  • ans: stores the minimum seed value that destroys the maximum targets
  • mx: tracks the maximum number of targets that can be destroyed

We then iterate through each value v in nums:

for v in nums:
    t = cnt[v % space]
    if t > mx or (t == mx and v < ans):
        ans = v
        mx = t

For each target value v:

  1. We look up how many targets have the same remainder as v using cnt[v % space]
  2. If this count t is greater than our current maximum mx, we update both ans and mx
  3. If the count equals the maximum but v is smaller than the current answer, we update ans to get the minimum seed value

Why This Works:

  • By checking cnt[v % space], we know exactly how many targets from the array can be destroyed if we seed with v
  • The condition t > mx ensures we always choose the seed that destroys the most targets
  • The condition (t == mx and v < ans) handles ties by selecting the smaller seed value
  • Since we iterate through the original array, we're guaranteed to find the actual minimum value from each remainder group

The time complexity is O(n) where n is the length of nums, and the space complexity is O(min(n, space)) for the hash table.

Ready to land your dream job?

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

Start Evaluator

Example Walkthrough

Let's walk through an example with nums = [3, 7, 8, 1, 1, 5] and space = 2.

Step 1: Group targets by remainder

First, we calculate v % space for each value:

  • 3 % 2 = 1
  • 7 % 2 = 1
  • 8 % 2 = 0
  • 1 % 2 = 1
  • 1 % 2 = 1
  • 5 % 2 = 1

So our remainder groups are:

  • Remainder 0: [8] (1 target)
  • Remainder 1: [3, 7, 1, 1, 5] (5 targets)

Using Counter, we get cnt = {1: 5, 0: 1}.

Step 2: Find the minimum seed that destroys maximum targets

Initialize ans = float('inf') and mx = 0.

Now iterate through each value in nums:

  1. v = 3:

    • Remainder: 3 % 2 = 1
    • Count: cnt[1] = 5
    • Since 5 > mx (0), update: ans = 3, mx = 5
  2. v = 7:

    • Remainder: 7 % 2 = 1
    • Count: cnt[1] = 5
    • Since 5 = mx (5) but 7 > ans (3), no update
  3. v = 8:

    • Remainder: 8 % 2 = 0
    • Count: cnt[0] = 1
    • Since 1 < mx (5), no update
  4. v = 1 (first occurrence):

    • Remainder: 1 % 2 = 1
    • Count: cnt[1] = 5
    • Since 5 = mx (5) and 1 < ans (3), update: ans = 1
  5. v = 1 (second occurrence):

    • Remainder: 1 % 2 = 1
    • Count: cnt[1] = 5
    • Since 5 = mx (5) but 1 = ans (1), no update
  6. v = 5:

    • Remainder: 5 % 2 = 1
    • Count: cnt[1] = 5
    • Since 5 = mx (5) but 5 > ans (1), no update

Result: The answer is 1.

Verification: If we seed the machine with 1:

  • The machine destroys positions: 1, 1+2=3, 1+4=5, 1+6=7, 1+8=9, ...
  • From our array [3, 7, 8, 1, 1, 5], it destroys: 1, 1, 3, 5, 7 (5 targets)
  • This is indeed the maximum possible, and 1 is the minimum seed that achieves it.

Solution Implementation

1from typing import List
2from collections import Counter
3
4class Solution:
5    def destroyTargets(self, nums: List[int], space: int) -> int:
6        # Count how many numbers have the same remainder when divided by space
7        # Numbers with the same remainder can be destroyed together by the same seed
8        remainder_count = Counter(num % space for num in nums)
9      
10        # Initialize variables to track the best answer
11        best_seed = 0  # The seed value that destroys the most targets
12        max_targets = 0  # Maximum number of targets that can be destroyed
13      
14        # Iterate through each number as a potential seed
15        for num in nums:
16            # Get the count of numbers that have the same remainder as current number
17            # These are all the targets that would be destroyed if we use this number as seed
18            targets_destroyed = remainder_count[num % space]
19          
20            # Update the best seed if:
21            # 1. Current number destroys more targets than previous best, OR
22            # 2. Current number destroys same targets but has smaller value (tiebreaker)
23            if targets_destroyed > max_targets or (targets_destroyed == max_targets and num < best_seed):
24                best_seed = num
25                max_targets = targets_destroyed
26      
27        return best_seed
28
1class Solution {
2    public int destroyTargets(int[] nums, int space) {
3        // Map to store frequency of each remainder when divided by space
4        // Key: remainder (v % space), Value: count of numbers with this remainder
5        Map<Integer, Integer> remainderFrequency = new HashMap<>();
6      
7        // First pass: Count frequency of each remainder
8        for (int num : nums) {
9            int remainder = num % space;
10            remainderFrequency.put(remainder, remainderFrequency.getOrDefault(remainder, 0) + 1);
11        }
12      
13        // Initialize result variables
14        int selectedTarget = 0;  // The seed value that can destroy maximum targets
15        int maxDestroyable = 0;  // Maximum number of targets that can be destroyed
16      
17        // Second pass: Find the seed with maximum destroyable targets
18        // If tie, choose the smaller seed value
19        for (int num : nums) {
20            int destroyableCount = remainderFrequency.get(num % space);
21          
22            // Update if current number can destroy more targets
23            // OR if it destroys same number of targets but has smaller value
24            if (destroyableCount > maxDestroyable || 
25                (destroyableCount == maxDestroyable && num < selectedTarget)) {
26                selectedTarget = num;
27                maxDestroyable = destroyableCount;
28            }
29        }
30      
31        return selectedTarget;
32    }
33}
34
1class Solution {
2public:
3    int destroyTargets(vector<int>& nums, int space) {
4        // Map to store count of numbers for each remainder when divided by space
5        // Key: remainder (v % space), Value: count of numbers with this remainder
6        unordered_map<int, int> remainderCount;
7      
8        // Count how many numbers have each remainder value
9        // Numbers with same remainder can be destroyed together as a sequence
10        for (int num : nums) {
11            remainderCount[num % space]++;
12        }
13      
14        // Find the seed that can destroy maximum targets
15        int seedValue = 0;  // The seed value to return
16        int maxTargets = 0; // Maximum number of targets that can be destroyed
17      
18        // Check each number as a potential seed
19        for (int num : nums) {
20            int targetsDestroyed = remainderCount[num % space];
21          
22            // Update seed if:
23            // 1. Current number can destroy more targets, OR
24            // 2. Can destroy same number of targets but has smaller value
25            if (targetsDestroyed > maxTargets || 
26                (targetsDestroyed == maxTargets && num < seedValue)) {
27                seedValue = num;
28                maxTargets = targetsDestroyed;
29            }
30        }
31      
32        return seedValue;
33    }
34};
35
1function destroyTargets(nums: number[], space: number): number {
2    // Map to store count of numbers for each remainder when divided by space
3    // Key: remainder (v % space), Value: count of numbers with this remainder
4    const remainderCount: Map<number, number> = new Map();
5  
6    // Count how many numbers have each remainder value
7    // Numbers with same remainder can be destroyed together as a sequence
8    for (const num of nums) {
9        const remainder = num % space;
10        remainderCount.set(remainder, (remainderCount.get(remainder) || 0) + 1);
11    }
12  
13    // Find the seed that can destroy maximum targets
14    let seedValue: number = 0;  // The seed value to return
15    let maxTargets: number = 0;  // Maximum number of targets that can be destroyed
16  
17    // Check each number as a potential seed
18    for (const num of nums) {
19        const targetsDestroyed = remainderCount.get(num % space) || 0;
20      
21        // Update seed if:
22        // 1. Current number can destroy more targets, OR
23        // 2. Can destroy same number of targets but has smaller value
24        if (targetsDestroyed > maxTargets || 
25            (targetsDestroyed === maxTargets && num < seedValue)) {
26            seedValue = num;
27            maxTargets = targetsDestroyed;
28        }
29    }
30  
31    return seedValue;
32}
33

Time and Space Complexity

The time complexity is O(n), where n is the length of the array nums. This is because:

  • Creating the Counter object requires iterating through all n elements once to compute v % space for each element: O(n)
  • The second loop iterates through all n elements in nums to find the answer: O(n)
  • Inside the second loop, accessing the count value cnt[v % space] is O(1) on average for dictionary lookup
  • Overall: O(n) + O(n) = O(n)

The space complexity is O(n). This is because:

  • The Counter object cnt stores at most n unique values (in the worst case where all v % space values are different)
  • In the best case, it could store as few as 1 unique value, but worst-case analysis gives us O(n)
  • The variables ans, mx, and t use constant space: O(1)
  • Overall: O(n) + O(1) = O(n)

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

Common Pitfalls

1. Incorrectly Finding the Minimum Value in a Remainder Group

A common mistake is to assume that after identifying the remainder group with the maximum count, you need to find ALL values in that group and then select the minimum. This leads to inefficient two-pass solutions:

Incorrect Approach:

# First pass: find the remainder with max count
max_remainder = max(remainder_count, key=remainder_count.get)
max_count = remainder_count[max_remainder]

# Second pass: find all values with that remainder and return minimum
candidates = [num for num in nums if num % space == max_remainder]
return min(candidates)

Problems with this approach:

  • Requires two full passes through the array
  • Doesn't handle ties correctly (when multiple remainders have the same maximum count)
  • More complex and error-prone

Correct Solution: The provided solution elegantly handles this in a single pass by checking each number as we iterate and updating the answer only when we find a better candidate (more targets destroyed) or an equal candidate with a smaller value.

2. Not Handling Ties Properly

When multiple remainder groups have the same maximum count, you must return the smallest seed value across ALL these groups, not just the smallest value from the first group you encounter.

Incorrect Logic:

if targets_destroyed > max_targets:  # Missing the tie-breaking condition
    best_seed = num
    max_targets = targets_destroyed

This would return an arbitrary seed when there are ties, not necessarily the minimum one.

Correct Logic:

if targets_destroyed > max_targets or (targets_destroyed == max_targets and num < best_seed):
    best_seed = num
    max_targets = targets_destroyed

3. Using the Remainder as the Answer Instead of the Actual Value

A conceptual error is returning the remainder that has the maximum count instead of an actual value from the array.

Wrong:

max_remainder = max(remainder_count, key=remainder_count.get)
return max_remainder  # This returns a remainder (0 to space-1), not a value from nums!

Right: The solution correctly returns an actual value from nums that serves as the seed, not just the remainder value.

4. Initializing Variables Incorrectly

Setting best_seed = float('inf') or best_seed = nums[0] without proper initialization of max_targets can lead to incorrect results.

Problematic Initialization:

best_seed = float('inf')
max_targets = 0

With this initialization, if all remainder groups have count 1, the condition num < best_seed would always be true for the first element, but we'd never update max_targets properly.

Better Initialization:

best_seed = 0
max_targets = 0

Since we're guaranteed to update both values together when we find our first valid candidate, the initial value of best_seed doesn't matter as long as max_targets starts at 0.

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

How does quick sort divide the problem into subproblems?


Recommended Readings

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

Load More