2344. Minimum Deletions to Make Array Divisible


Problem Description

This problem presents us with two arrays of positive integers, nums and numsDivide. The goal is to perform the minimum number of deletions in nums to ensure that the smallest number in nums divides all numbers in numsDivide. If no element in nums can be the divisor for every element in numsDivide, the function should return -1.

The division rule here is that an integer x divides another integer y if the remainder when y is divided by x is zero, denoted as y % x == 0.

To solve this problem, you need to consider two main steps:

  1. Identify the smallest number in nums that can be a divisor for all elements in numsDivide.
  2. Find the minimum number of deletions in nums required to make this number the smallest in nums.

Intuition

To begin, since we want a number from nums that divides all elements in numsDivide, we need to find the Greatest Common Divisor (GCD) of numsDivide. All elements of numsDivide must be divisible by this GCD, so our potential divisor in nums must also divide the GCD of numsDivide. Therefore, the smallest number in nums that also divides the GCD of numsDivide will serve as the required divisor.

With that in mind, the first step is to calculate the GCD of all elements in numsDivide using a built-in function.

Next, we search for the smallest number in nums that can divide this GCD, which can be done using a generator expression within the min function. If no such number exists in nums (when min returns the default=0), we cannot perform the desired operation, therefore we return -1.

If a divisor is found, we count the number of elements in nums that are smaller than this divisor since any such elements must be deleted for the divisor to be the smallest element in nums. The count of these elements gives us the minimum number of deletions needed. The summing using a generator expression in the return statement calculates this count and returns it.

Learn more about Math, Sorting and Heap (Priority Queue) patterns.

Solution Approach

The implementation of the solution for this problem follows a straightforward approach, with the primary focus being the calculation of the Greatest Common Divisor (GCD) and the minimization of deletions.

Here's the step-by-step breakdown of the solution:

  1. Calculate the GCD of elements in numsDivide array:

    • The gcd function from the Python standard library can compute GCD of two numbers. Leveraging the unpacking operator *, we can pass all elements of numsDivide to this function to find their collective GCD.
    x = gcd(*numsDivide)
  2. Identify the smallest number in nums that divides the GCD:

    • We use a generator expression to iterate over all the values v in nums.
    • The condition x % v == 0 ensures we only consider those elements that properly divide the GCD x.
    • The min function is used to find the smallest of such elements.
    • The default=0 is used to handle the scenario where no such divisibility is possible, which will lead to the min function returning 0.
    y = min((v for v in nums if x % v == 0), default=0)
  3. Count and return the minimum deletions:

    • If we found a divisor y, then we count the number of elements in nums that are less than y. These elements would prevent y from being the smallest element if not deleted.
    • This count is computed with the sum of a generator expression.
    • For every element v in nums, if v < y, it adds 1 to the sum.
    return sum(v < y for v in nums)
  4. Handle the case where no suitable divisor is found:

    • If no element in nums can divide the GCD, which means y is 0, we cannot perform the operation, so we return -1.
    if y else -1

In summary, the solution leverages mathematical properties (divisibility and GCD), along with Python's built-in gcd function, generator expressions for memory-efficient iteration, and conditional logic to directly return the minimum number of deletions or -1 if the problem conditions cannot be met.

Ready to land your dream job?

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

Start Evaluator

Example Walkthrough

Let's consider an example to illustrate the solution approach. Suppose we have the following arrays:

nums = [4, 3, 6]
numsDivide = [24, 48]

Calculating the GCD of Elements in numsDivide

First, we find the GCD of all elements in the numsDivide array:

The GCD of 24 and 48 can be found as:

GCD(24, 48) = 24

This means any divisor in nums needs to divide 24 to be a valid divisor for all numbers in numsDivide.

Identifying the Smallest Number in nums Dividing the GCD

Now, we look for the smallest number in nums that can divide the GCD 24:

  • For 4, we check if 24 % 4 == 0, which is True.
  • For 3, we check 24 % 3 and find it's also True as 24 is divisible by 3.
  • For 6, we check 24 % 6 and see that this is True as well.

All numbers [4, 3, 6] in nums can divide 24, but we need the smallest one; hence we find the min which is 3.

Counting and Returning Minimum Deletions

To make 3 the smallest number in nums, we must delete all numbers that are smaller than 3. In nums, there are no such numbers.

Therefore, the minimum number of deletions is 0.

This means no numbers from nums need to be deleted for the smallest number within it to divide all numbers in numsDivide.

Handling the Case of No Appropriate Divisor Found

Since we did find the number 3 as a suitable divisor, we did not encounter the scenario of the min function returning 0, which would have led us to return -1.

Using the aforementioned steps, the solution successfully calculates that no deletions are needed from nums so that the smallest number in nums can divide all elements in numsDivide.

Solution Implementation

1from math import gcd
2from typing import List
3
4class Solution:
5    def minOperations(self, nums: List[int], numsDivide: List[int]) -> int:
6        # Calculate the greatest common divisor (GCD) of all elements in numsDivide
7        common_divisor = gcd(*numsDivide)
8      
9        # Find the smallest element in nums that is a divisor of the GCD
10        # If there is no such element, the default value will be 0
11        min_divisible = min((value for value in nums if common_divisor % value == 0), default=0)
12      
13        # If the smallest element is not found, return -1 as it's not possible
14        # to make all numsDivide elements divisible by any number in nums
15        if min_divisible == 0:
16            return -1
17      
18        # Calculate the number of operations needed by counting elements in nums
19        # that are smaller than the smallest valid divisor (min_divisible)
20        operations = sum(value < min_divisible for value in nums)
21      
22        # Return the count of operations needed
23        return operations
24
1class Solution {
2    // This method finds the minimum number of operations required 
3    // to make every number in numsDivide divisible by some number 
4    // in nums by removing the smallest numbers in nums.
5    public int minOperations(int[] nums, int[] numsDivide) {
6        // Initialize the greatest common divisor (gcd) of all numbers in numsDivide.
7        int gcdValue = 0;
8        for (int value : numsDivide) {
9            gcdValue = gcd(gcdValue, value);
10        }
11      
12        // Set an initial high value to find the minimum value in nums that divides the gcd without remainder.
13        int minDivisibleValue = Integer.MAX_VALUE;
14        for (int value : nums) {
15            if (gcdValue % value == 0) {
16                minDivisibleValue = Math.min(minDivisibleValue, value);
17            }
18        }
19      
20        // If no number was found, return -1 as it's not possible to satisfy the condition with any deletions.
21        if (minDivisibleValue == Integer.MAX_VALUE) {
22            return -1;
23        }
24      
25        // Count the numbers of operations (number of elements smaller than the minDivisibleValue to be deleted).
26        int operations = 0;
27        for (int value : nums) {
28            if (value < minDivisibleValue) {
29                operations++;
30            }
31        }
32      
33        // Return the number of operations required.
34        return operations;
35    }
36
37    // Helper method to compute the gcd of two numbers using the Euclidean algorithm.
38    private int gcd(int a, int b) {
39        // If b is zero, a is the gcd by definition.
40        return b == 0 ? a : gcd(b, a % b);
41    }
42}
43
1#include <vector>  // Required to include vector
2#include <algorithm> // Required for std::min function
3using namespace std;
4
5class Solution {
6public:
7    int minOperations(vector<int>& nums, vector<int>& numsDivide) {
8        // Initializing gcdValue with 0 to calculate GCD of all values in numsDivide
9        int gcdValue = 0;
10        // Calculating GCD of all elements in numsDivide
11        for (int& value : numsDivide) {
12            gcdValue = gcd(gcdValue, value);
13        }
14        // Setting the minimum possible value greater than all elements in nums
15        int minValueGreaterThanAll = 1 << 30; // Large value as upper limit.
16        // Finding the smallest number in nums that divides the gcdValue without remainder
17        for (int& value : nums) {
18            if (gcdValue % value == 0) {
19                minValueGreaterThanAll = min(minValueGreaterThanAll, value);
20            }
21        }
22      
23        // If minValueGreaterThanAll is not changed, it means no such number is found. Return -1.
24        if (minValueGreaterThanAll == 1 << 30) {
25            return -1;
26        }
27        // Counting the number of operations to remove numbers smaller than minValueGreaterThanAll
28        int operationsCount = 0;
29        for (int& value : nums) {
30            operationsCount += value < minValueGreaterThanAll;
31        }
32      
33        return operationsCount; // Returning the minimum number of operations.
34    }
35  
36    // Function to calculate the gcd of two numbers
37    int gcd(int a, int b) {
38        return b == 0 ? a : gcd(b, a % b);
39    }
40};
41
1// Importing necessary functionalities from external libraries
2import { min } from 'lodash';
3
4// Function to calculate the greatest common divisor (GCD) of two numbers
5function calculateGCD(a: number, b: number): number {
6    return b === 0 ? a : calculateGCD(b, a % b);
7}
8
9// Function to find the minimum number of operations required
10function minOperations(nums: number[], numsDivide: number[]): number {
11    // Initialize gcdValue with 0 to calculate GCD of all values in numsDivide
12    let gcdValue: number = 0;
13
14    // Calculate GCD of all elements in numsDivide
15    for (const value of numsDivide) {
16        gcdValue = calculateGCD(gcdValue, value);
17    }
18
19    // Initialize minValueGreaterThanAll with a large number as an upper limit.
20    let minValueGreaterThanAll: number = 1 << 30;
21
22    // Find the smallest number in nums that divides the gcdValue without a remainder
23    for (const value of nums) {
24        if (gcdValue % value === 0) {
25            minValueGreaterThanAll = Math.min(minValueGreaterThanAll, value);
26        }
27    }
28  
29    // If minValueGreaterThanAll is not changed, no such number is found; return -1.
30    if (minValueGreaterThanAll === 1 << 30) {
31        return -1;
32    }
33
34    // Count the number of operations to remove numbers smaller than minValueGreaterThanAll
35    let operationsCount: number = 0;
36    for (const value of nums) {
37        operationsCount += value < minValueGreaterThanAll ? 1 : 0;
38    }
39
40    // Return the minimum number of operations.
41    return operationsCount;
42}
43

Time and Space Complexity

The given Python code aims to find the minimum number of operations to delete elements from nums so that the greatest common divisor (GCD) of numsDivide can divide all elements in the modified nums list. It finds the GCD of the elements of numsDivide, then finds the minimum element y in nums that is a divisor of the GCD, and counts elements in nums less than y.

Time Complexity

Let n be the length of the nums list and m be the length of the numsDivide list.

  1. gcd(*numsDivide): The function computes the GCD of all elements in numsDivide. The time complexity of the gcd function for two numbers is O(log(min(a, b))), where a and b are the two numbers. The GCD function will be called for every element in numsDivide beyond the first two. Therefore, the time complexity for this portion can be considered O(m * log (A)), where A is the average of the numsDivide list.

  2. min((v for v in nums if x % v == 0), default=0): This generator expression iterates over all elements v in nums and checks if v divides x without remainder. In the worst case, it will iterate through the entire nums list, resulting in a time complexity of O(n).

  3. sum(v < y for v in nums): This expression iterates over the list nums, and for each element, it increments the sum if the element is less than y. This operation also has a time complexity of O(n) because it goes through all n elements.

Combining these, the overall worst-case time complexity is: O(m * log (A) + 2n). Since m * log(A) and n are not related by a constant factor, we can't simplify it further. However, typically m and log(A) are much smaller than n in practical scenarios, such that we can consider it as O(n) for practical purposes.

Space Complexity

Let's analyze the space complexity of the algorithm:

  1. gcd(*numsDivide): The gcd function itself uses O(1) additional space as it only requires a constant space for the computation.

  2. min((v for v in nums if x % v == 0), default=0): The generator expression used here does not store intermediate results and computes the minimum on the fly. Therefore, it also uses O(1) space.

  3. sum(v < y for v in nums): Similar to the min function call, this sum leverages a generator expression and does not store intermediate results, so it uses O(1) additional space.

The space complexity of the entire algorithm is O(1) since all additional space used is constant and does not scale with the size of the input nums or numsDivide.

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


Discover Your Strengths and Weaknesses: Take Our 2-Minute Quiz to Tailor Your Study Plan:
Question 1 out of 10

What's the output of running the following function using input [30, 20, 10, 100, 33, 12]?

1def fun(arr: List[int]) -> List[int]:
2    import heapq
3    heapq.heapify(arr)
4    res = []
5    for i in range(3):
6        res.append(heapq.heappop(arr))
7    return res
8
1public static int[] fun(int[] arr) {
2    int[] res = new int[3];
3    PriorityQueue<Integer> heap = new PriorityQueue<>();
4    for (int i = 0; i < arr.length; i++) {
5        heap.add(arr[i]);
6    }
7    for (int i = 0; i < 3; i++) {
8        res[i] = heap.poll();
9    }
10    return res;
11}
12
1class HeapItem {
2    constructor(item, priority = item) {
3        this.item = item;
4        this.priority = priority;
5    }
6}
7
8class MinHeap {
9    constructor() {
10        this.heap = [];
11    }
12
13    push(node) {
14        // insert the new node at the end of the heap array
15        this.heap.push(node);
16        // find the correct position for the new node
17        this.bubble_up();
18    }
19
20    bubble_up() {
21        let index = this.heap.length - 1;
22
23        while (index > 0) {
24            const element = this.heap[index];
25            const parentIndex = Math.floor((index - 1) / 2);
26            const parent = this.heap[parentIndex];
27
28            if (parent.priority <= element.priority) break;
29            // if the parent is bigger than the child then swap the parent and child
30            this.heap[index] = parent;
31            this.heap[parentIndex] = element;
32            index = parentIndex;
33        }
34    }
35
36    pop() {
37        const min = this.heap[0];
38        this.heap[0] = this.heap[this.size() - 1];
39        this.heap.pop();
40        this.bubble_down();
41        return min;
42    }
43
44    bubble_down() {
45        let index = 0;
46        let min = index;
47        const n = this.heap.length;
48
49        while (index < n) {
50            const left = 2 * index + 1;
51            const right = left + 1;
52
53            if (left < n && this.heap[left].priority < this.heap[min].priority) {
54                min = left;
55            }
56            if (right < n && this.heap[right].priority < this.heap[min].priority) {
57                min = right;
58            }
59            if (min === index) break;
60            [this.heap[min], this.heap[index]] = [this.heap[index], this.heap[min]];
61            index = min;
62        }
63    }
64
65    peek() {
66        return this.heap[0];
67    }
68
69    size() {
70        return this.heap.length;
71    }
72}
73
74function fun(arr) {
75    const heap = new MinHeap();
76    for (const x of arr) {
77        heap.push(new HeapItem(x));
78    }
79    const res = [];
80    for (let i = 0; i < 3; i++) {
81        res.push(heap.pop().item);
82    }
83    return res;
84}
85

Recommended Readings

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


Load More