2344. Minimum Deletions to Make Array Divisible
Problem Description
You have two arrays of positive integers: nums
and numsDivide
.
Your task is to delete elements from nums
so that the smallest remaining element in nums
can divide every element in numsDivide
. You want to minimize the number of deletions.
Here's what you need to find:
- Delete some (possibly zero) elements from
nums
- After deletion, the smallest element in
nums
must divide all elements innumsDivide
- Return the minimum number of deletions needed
- If it's impossible (no element in
nums
can divide all elements innumsDivide
), return-1
Remember that an integer x
divides y
if y % x == 0
(remainder is zero).
For example:
- If
nums = [2, 3, 2, 4, 3]
andnumsDivide = [9, 6, 9, 3, 15]
- The smallest element that can divide all elements in
numsDivide
is3
(since3
divides9
,6
,9
,3
, and15
) - We need to delete the two
2
s fromnums
to make3
the smallest element - So the answer is
2
deletions
Intuition
The key insight is that if a number needs to divide all elements in numsDivide
, it must divide their Greatest Common Divisor (GCD). This is because any common divisor of a set of numbers must also divide their GCD.
Think about it this way: if a number x
divides both a
and b
, then x
must also divide gcd(a, b)
. Conversely, any divisor of gcd(a, b)
will divide both a
and b
. This property extends to multiple numbers - any divisor of the GCD of all numbers will divide each individual number.
So instead of checking if each element in nums
divides every single element in numsDivide
(which would be inefficient), we can:
- First find the GCD of all elements in
numsDivide
- Then find the smallest element in
nums
that divides this GCD
To minimize deletions, we want to keep the smallest valid element from nums
. By sorting nums
first, we can iterate through elements from smallest to largest. The first element that divides the GCD is our answer - we delete everything before it.
For example, if numsDivide = [6, 12, 18]
, their GCD is 6
. Any element from nums
that we keep must divide 6
(possible values: 1
, 2
, 3
, or 6
). If nums = [4, 3, 2, 6]
, after sorting we get [2, 3, 4, 6]
. The first element that divides 6
is 2
, so we delete 0
elements.
Learn more about Math, Sorting and Heap (Priority Queue) patterns.
Solution Approach
The implementation follows these steps:
-
Calculate the GCD of
numsDivide
: We start by finding the GCD of all elements innumsDivide
. Initializex
with the first element, then iteratively compute the GCD with each subsequent element usinggcd(x, v)
. This gives us the number that any valid element fromnums
must divide. -
Sort the
nums
array: By sortingnums
in ascending order, we ensure that we encounter potential candidates from smallest to largest. This is crucial because we want to minimize deletions, which means keeping the smallest valid element. -
Find the first valid divisor: Iterate through the sorted
nums
array with both indexi
and valuev
. For each element, check if it divides the GCD using the conditionx % v == 0
.- If we find such an element, return its index
i
- this represents the number of smaller elements we need to delete - The index directly gives us the deletion count because all elements before index
i
need to be removed
- If we find such an element, return its index
-
Handle the impossible case: If we iterate through the entire
nums
array without finding any element that divides the GCD, return-1
to indicate it's impossible.
The algorithm efficiently reduces the problem from checking divisibility against all elements in numsDivide
to checking against just their GCD. The time complexity is O(n log n + m)
where n
is the length of nums
(for sorting) and m
is the length of numsDivide
(for GCD calculation). The space complexity is O(1)
if we sort in-place.
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 a concrete example to illustrate the solution approach.
Example: nums = [4, 3, 6, 2, 3]
and numsDivide = [12, 9, 6]
Step 1: Calculate GCD of numsDivide
- Start with
x = 12
(first element) x = gcd(12, 9) = 3
x = gcd(3, 6) = 3
- The GCD of all elements in
numsDivide
is3
This means any element we keep from nums
must divide 3
.
Step 2: Sort nums array
- Original:
[4, 3, 6, 2, 3]
- Sorted:
[2, 3, 3, 4, 6]
Step 3: Find first valid divisor
Iterate through sorted nums
with index and value:
- Index 0, value 2: Check if
3 % 2 == 0
→1 ≠ 0
(doesn't divide, continue) - Index 1, value 3: Check if
3 % 3 == 0
→0 == 0
(divides! Found our answer)
Result: Return index 1
This means we need to delete 1 element (the 2
at index 0) from the sorted array. After deletion, the smallest element would be 3
, which divides all elements in numsDivide
since it divides their GCD.
Verification:
12 % 3 = 0
✓9 % 3 = 0
✓6 % 3 = 0
✓
The minimum number of deletions needed is 1.
Solution Implementation
1from typing import List
2from math import gcd
3
4class Solution:
5 def minOperations(self, nums: List[int], numsDivide: List[int]) -> int:
6 # Calculate the GCD of all elements in numsDivide array
7 # This gives us the number that all elements in nums must divide
8 target_gcd = numsDivide[0]
9 for value in numsDivide[1:]:
10 target_gcd = gcd(target_gcd, value)
11
12 # Sort nums array to check smallest elements first
13 nums.sort()
14
15 # Find the first element in sorted nums that divides the GCD
16 # The index represents minimum operations needed (removing all smaller elements)
17 for index, value in enumerate(nums):
18 if target_gcd % value == 0:
19 return index
20
21 # If no element in nums can divide the GCD, return -1
22 return -1
23
1class Solution {
2 /**
3 * Finds the minimum number of operations (deletions from the beginning)
4 * needed to make the smallest element in nums divide all elements in numsDivide.
5 *
6 * @param nums Array of integers to potentially delete from
7 * @param numsDivide Array of integers that must all be divisible by the result
8 * @return Minimum number of deletions needed, or -1 if impossible
9 */
10 public int minOperations(int[] nums, int[] numsDivide) {
11 // Calculate the GCD of all elements in numsDivide
12 // Any valid divisor must divide this GCD
13 int gcdOfNumsDivide = 0;
14 for (int value : numsDivide) {
15 gcdOfNumsDivide = gcd(gcdOfNumsDivide, value);
16 }
17
18 // Sort nums array to check elements from smallest to largest
19 Arrays.sort(nums);
20
21 // Find the first element in sorted nums that divides the GCD
22 for (int i = 0; i < nums.length; ++i) {
23 if (gcdOfNumsDivide % nums[i] == 0) {
24 // Return the index, which represents the number of elements
25 // that need to be removed before this valid element
26 return i;
27 }
28 }
29
30 // No element in nums can divide all elements in numsDivide
31 return -1;
32 }
33
34 /**
35 * Calculates the Greatest Common Divisor (GCD) of two integers
36 * using Euclidean algorithm.
37 *
38 * @param a First integer
39 * @param b Second integer
40 * @return GCD of a and b
41 */
42 private int gcd(int a, int b) {
43 return b == 0 ? a : gcd(b, a % b);
44 }
45}
46
1class Solution {
2public:
3 int minOperations(vector<int>& nums, vector<int>& numsDivide) {
4 // Calculate the GCD of all elements in numsDivide array
5 int gcdValue = 0;
6 for (int& value : numsDivide) {
7 gcdValue = gcd(gcdValue, value);
8 }
9
10 // Sort the nums array in ascending order to check smallest values first
11 sort(nums.begin(), nums.end());
12
13 // Find the first element in sorted nums that divides the GCD
14 // The index represents the number of elements to remove
15 for (int i = 0; i < nums.size(); ++i) {
16 if (gcdValue % nums[i] == 0) {
17 return i; // Return number of operations (elements removed)
18 }
19 }
20
21 // If no element in nums can divide the GCD, return -1
22 return -1;
23 }
24};
25
1function minOperations(nums: number[], numsDivide: number[]): number {
2 // Helper function to calculate GCD of two numbers
3 const gcd = (a: number, b: number): number => {
4 while (b !== 0) {
5 const temp = b;
6 b = a % b;
7 a = temp;
8 }
9 return a;
10 };
11
12 // Calculate the GCD of all elements in numsDivide array
13 let gcdValue = 0;
14 for (const value of numsDivide) {
15 gcdValue = gcd(gcdValue, value);
16 }
17
18 // Sort the nums array in ascending order to check smallest values first
19 nums.sort((a, b) => a - b);
20
21 // Find the first element in sorted nums that divides the GCD
22 // The index represents the number of elements to remove
23 for (let i = 0; i < nums.length; i++) {
24 if (gcdValue % nums[i] === 0) {
25 return i; // Return number of operations (elements removed)
26 }
27 }
28
29 // If no element in nums can divide the GCD, return -1
30 return -1;
31}
32
Time and Space Complexity
Time Complexity: O(m + n log n)
where n
is the length of nums
and m
is the length of numsDivide
.
- Computing the GCD of all elements in
numsDivide
takesO(m * log(min(numsDivide)))
time, where we iterate throughm
elements and each GCD operation takesO(log(min(a,b)))
time. Since the logarithmic factor is typically much smaller than the array size, we can simplify this toO(m)
. - Sorting the
nums
array takesO(n log n)
time. - Iterating through the sorted
nums
array takesO(n)
time in the worst case. - Overall:
O(m) + O(n log n) + O(n) = O(m + n log n)
Space Complexity: O(1)
or O(n)
depending on the sorting algorithm implementation.
- The variable
x
usesO(1)
space. - The sorting operation may use
O(1)
space if an in-place sorting algorithm like heapsort is used, orO(n)
space if the implementation uses a sorting algorithm like mergesort or if Python's Timsort requires auxiliary space. - In Python's case, the
sort()
method sorts in-place but may use up toO(n)
auxiliary space in the worst case. - Overall:
O(n)
in the worst case for Python's implementation.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Not Understanding Why GCD Works
A common misconception is thinking you need to check if an element from nums
divides every single element in numsDivide
. The key insight is that if a number divides the GCD of all elements in numsDivide
, it will divide every element in that array. This is because the GCD is the largest common divisor, and any divisor of the GCD is also a divisor of all the original numbers.
Solution: Remember that GCD(a, b, c) divides a, b, and c. So if x divides GCD(a, b, c), then x also divides a, b, and c.
2. Modifying the Original Array
The code sorts nums
in-place with nums.sort()
. If the problem requires preserving the original array or if you need to track original indices, this approach will fail.
Solution: Create a copy before sorting:
sorted_nums = sorted(nums)
for index, value in enumerate(sorted_nums):
if target_gcd % value == 0:
return index
3. Edge Case: Single Element in numsDivide
When numsDivide
has only one element, the loop for value in numsDivide[1:]
won't execute, leaving target_gcd
as just numsDivide[0]
. While this is actually correct behavior, it might not be immediately obvious.
Solution: The current implementation handles this correctly, but for clarity, you could use:
target_gcd = numsDivide[0]
for i in range(1, len(numsDivide)):
target_gcd = gcd(target_gcd, numsDivide[i])
4. Duplicate Values Confusion
When nums
contains duplicates of the smallest valid divisor, you might think you need special handling. For example, if nums = [3, 3, 3, 6]
and the smallest valid divisor is 3, all three 3's are at indices 0, 1, and 2, but returning index 0 is correct since we don't need to delete any of them.
Solution: The current approach handles this correctly - it returns the index of the first occurrence, which means all duplicates of that value remain in the array.
Which data structure is used to implement priority queue?
Recommended Readings
Math for Technical Interviews How much math do I need to know for technical interviews The short answer is about high school level math Computer science is often associated with math and some universities even place their computer science department under the math faculty However the reality is that you
Sorting Summary Comparisons We presented quite a few sorting algorithms and it is essential to know the advantages and disadvantages of each one The basic algorithms are easy to visualize and easy to learn for beginner programmers because of their simplicity As such they will suffice if you don't know any advanced
https assets algo monster cover_photos heap svg Priority Queue and Heap What is the relationship between priority queue and heap Priority Queue is an Abstract Data Type and Heap is the concrete data structure we use to implement a priority queue Priority Queue A priority queue is a data structure
Want a Structured Path to Master System Design Too? Don’t Miss This!