2453. Destroy Sequential Targets
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.
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 targetsmx
: 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
:
- We look up how many targets have the same remainder as
v
usingcnt[v % space]
- If this count
t
is greater than our current maximummx
, we update bothans
andmx
- If the count equals the maximum but
v
is smaller than the current answer, we updateans
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 withv
- 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 EvaluatorExample 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
:
-
v = 3:
- Remainder: 3 % 2 = 1
- Count: cnt[1] = 5
- Since 5 > mx (0), update: ans = 3, mx = 5
-
v = 7:
- Remainder: 7 % 2 = 1
- Count: cnt[1] = 5
- Since 5 = mx (5) but 7 > ans (3), no update
-
v = 8:
- Remainder: 8 % 2 = 0
- Count: cnt[0] = 1
- Since 1 < mx (5), no update
-
v = 1 (first occurrence):
- Remainder: 1 % 2 = 1
- Count: cnt[1] = 5
- Since 5 = mx (5) and 1 < ans (3), update: ans = 1
-
v = 1 (second occurrence):
- Remainder: 1 % 2 = 1
- Count: cnt[1] = 5
- Since 5 = mx (5) but 1 = ans (1), no update
-
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 computev % space
for each element:O(n)
- The second loop iterates through all
n
elements innums
to find the answer:O(n)
- Inside the second loop, accessing the count value
cnt[v % space]
isO(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 mostn
unique values (in the worst case where allv % 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
, andt
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.
How does quick sort divide the problem into subproblems?
Recommended Readings
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!