Facebook Pixel

3326. Minimum Division Operations to Make Array Non Decreasing

MediumGreedyArrayMathNumber Theory
Leetcode Link

Problem Description

You are given an integer array nums.

Any positive divisor of a natural number x that is strictly less than x is called a proper divisor of x. For example, 2 is a proper divisor of 4, while 6 is not a proper divisor of 6.

You are allowed to perform an operation any number of times on nums, where in each operation you select any one element from nums and divide it by its greatest proper divisor.

Return the minimum number of operations required to make the array non-decreasing.

If it is not possible to make the array non-decreasing using any number of operations, return -1.

Intuition

To make the array non-decreasing, we aim to transform each element in a way that maintains the ordering. If an element is larger than the subsequent element, we attempt to reduce it by using the operation defined in the problem.

A crucial insight is understanding the relationship between an integer and its divisors. For a prime number, the only proper divisor is 1, so it can't be reduced through the given operations. However, for a composite number, the greatest proper divisor will result in a quotient that is a factor of the original number, potentially reducing it.

The strategy employs this property:

  1. Preprocessing: Calculate the smallest prime factor for every number up to 10^6. This helps quickly find how to reduce any number by dividing it with its greatest proper divisor.

  2. Traverse the array: Work from right to left, checking if each element is smaller or equal to the next. If it isn't, use the smallest prime factor to reduce the current element. If this reduced value is still too large, the desired non-decreasing order cannot be achieved, and we return -1.

The solution thus focuses on transforming each number based on its smallest prime factor until the array becomes non-decreasing or determining if it's impossible.

Learn more about Greedy and Math patterns.

Solution Approach

Preprocessing

  1. Find Smallest Prime Factors:
    • Initialize an array lpf to store the smallest prime factor for each integer from 2 to 1,000,000.
    • Iterate over each number i starting from 2. If lpf[i] is 0 (indicating it is a prime number), assign it as its own smallest prime factor.
    • Update the multiples of i, assigning i as the smallest prime factor for these multiples using a nested loop.

This preprocess allows quick access to the smallest divisor, thereby efficiently performing reduction operations on any element.

Main Algorithm

  1. Traverse the Array from Right to Left:

    • Start from the second last element of the array nums and move to the beginning.
    • Compare the current element with the next:
      • If nums[i] > nums[i + 1], replace nums[i] with lpf[nums[i]], the smallest divisor.
      • If after substitution, nums[i] is still greater than nums[i + 1], the array cannot be made non-decreasing using the allowed operations, thus return -1.
  2. Count Operations:

    • Each adjustment is counted as an operation. Keep track of the number of operations performed for the necessary adjustments.

The preprocessing step ensures that during traversal, we can quickly find how to adjust any element in the array to attempt making it non-decreasing. Using the smallest prime factor enables the reduction of an element effectively if it starts as a composite number.

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 a small example to illustrate the solution approach with an array nums = [8, 5, 10, 4].

Step 1: Preprocessing - Find Smallest Prime Factors

  1. Initialize an array lpf for numbers up to the maximum value we might encounter, say 10^6. Let's focus on a smaller range for our example.
  2. Calculate the smallest prime factor (LPF) for each number:
    • lpf[2] = 2, lpf[3] = 3, since these are prime numbers.
    • For composite numbers such as 4, 6, 8, assign their smallest prime divisor:
      • lpf[4] = 2 (as 2 is a factor of 4),
      • lpf[8] = 2 similarly.

This preprocessing allows us to quickly access the smallest divisor for any number.

Step 2: Traverse the Array from Right to Left

  1. Start from the second last element and move left: Compare nums[i] with nums[i + 1].
  2. nums = [8, 5, 10, 4]:
    • Check elements from right to left: start with nums[2] and nums[3]:
      • 10 > 4: Divide 10 by its LPF (2), 10 / 2 = 5, thus nums = [8, 5, 5, 4].
      • Now 5 > 4, divide 5 by lpf[5] (which is 5 itself as it is prime), 5 / 5 = 1, thus nums = [8, 5, 5, 1].
      • At this point, we can't reduce 5 to a value ≤ 1 anymore, as it requires further operations involving divisors that don't change the sequence.
  3. Return -1 since the array cannot be made non-decreasing.

Step 3: Count Operations

In this example, we attempted adjustments for reducing the sequence in two operations. Despite this effort, achieving a non-decreasing sequence was impossible, leading to return -1.

This example demonstrates the steps in which the smallest prime factor helps decide the proper divisor for any composite number, and how the inability to handle prime numbers effectively may result in situations where a non-decreasing sequence isn't achievable.

Solution Implementation

1# Define the maximum value for which we need to compute the least prime factor (LPF)
2MAX_VAL = 10**6 + 1
3
4# Initialize a list to store the least prime factor for each number up to MAX_VAL
5least_prime_factor = [0] * (MAX_VAL + 1)
6
7# Compute the least prime factor for each number using a sieve approach
8for num in range(2, MAX_VAL + 1):
9    if least_prime_factor[num] == 0:  # num is a prime number
10        for multiple in range(num, MAX_VAL + 1, num):
11            if least_prime_factor[multiple] == 0:
12                least_prime_factor[multiple] = num
13
14class Solution:
15    def minOperations(self, nums: List[int]) -> int:
16        operations = 0  # Initialize the counter for the number of operations
17
18        # Traverse the list backwards to ensure each element is less than or equal to the next
19        for i in range(len(nums) - 2, -1, -1):
20            # If the current element is greater than the next one, adjust it
21            if nums[i] > nums[i + 1]:
22                nums[i] = least_prime_factor[nums[i]]  # Replace with its least prime factor
23              
24                # If it still is greater, it's impossible to satisfy the condition
25                if nums[i] > nums[i + 1]:
26                    return -1
27              
28                operations += 1  # Increment operations count
29
30        return operations  # Return the total number of operations performed
31
1class Solution {
2    // Define the maximum constant value as 1,000,001
3    private static final int MAX = (int) 1e6 + 1;
4
5    // Array to store the least prime factor (LPF) for numbers up to MAX
6    private static final int[] leastPrimeFactor = new int[MAX + 1];
7
8    // Static initializer block to populate the least prime factor for all numbers up to MAX
9    static {
10        // Loop through numbers starting from 2 to MAX
11        for (int i = 2; i <= MAX; ++i) {
12            // For each number, mark its multiples
13            for (int j = i; j <= MAX; j += i) {
14                // If the least prime factor for this number is not set, set it to the current i
15                if (leastPrimeFactor[j] == 0) {
16                    leastPrimeFactor[j] = i;
17                }
18            }
19        }
20    }
21
22    // Main method to calculate minimum operations
23    public int minOperations(int[] nums) {
24        int operations = 0;  // Initialize a counter for operations
25
26        // Traverse the array from second last element to the first
27        for (int i = nums.length - 2; i >= 0; i--) {
28            // Check if the current element is greater than its right neighbor
29            if (nums[i] > nums[i + 1]) {
30                // Replace current element with its least prime factor
31                nums[i] = leastPrimeFactor[nums[i]];
32
33                // If replacement still keeps the order incorrect, return -1
34                if (nums[i] > nums[i + 1]) {
35                    return -1;
36                }
37
38                // Increment operation count as a change was made
39                operations++;
40            }
41        }
42      
43        // Return the total number of operations
44        return operations;
45    }
46}
47
1#include <vector>
2using namespace std;
3
4// Maximum limit for the numbers considered.
5const int MAX = 1e6;
6
7// Array to store the Lowest Prime Factor (LPF) for each number up to MAX.
8int lowestPrimeFactor[MAX + 1];
9
10// Lambda function for initializing the LPF array.
11// This function initializes each number with its lowest prime factor.
12auto initializeLowestPrimeFactors = [] {
13    for (int i = 2; i <= MAX; i++) {
14        // If the number is not marked, it means it's a prime number.
15        if (lowestPrimeFactor[i] == 0) {
16            // Mark all multiples of the prime number i.
17            for (int j = i; j <= MAX; j += i) {
18                // Assign the prime i as the lowest prime factor of j if not already assigned.
19                if (lowestPrimeFactor[j] == 0) {
20                    lowestPrimeFactor[j] = i;
21                }
22            }
23        }
24    }
25    // Return 0 for successful initialization.
26    return 0;
27}();
28
29// Solution class containing the method to calculate the minimum operations.
30class Solution {
31public:
32    // Method to calculate the minimum operations to make the array sorted.
33    int minOperations(vector<int>& nums) {
34        int operationsCount = 0; // Counter for the operations needed.
35      
36        // Iterate through the array from second last to the first element.
37        for (int i = nums.size() - 2; i >= 0; i--) {
38            // Check if the current element is greater than the next one.
39            if (nums[i] > nums[i + 1]) {
40                // Replace the current element with its lowest prime factor.
41                nums[i] = lowestPrimeFactor[nums[i]];
42              
43                // If the condition still holds, it is not possible to sort.
44                if (nums[i] > nums[i + 1]) {
45                    return -1; // Return -1 indicating impossibility.
46                }
47                // Increment the operation count.
48                operationsCount++;
49            }
50        }
51        // Return the total operations count.
52        return operationsCount;
53    }
54};
55
1// Define the maximum limit for calculations
2const maxLimit = 10 ** 6;
3
4// Create an array to store the lowest prime factor (LPF) for each number
5const lowestPrimeFactor = Array(maxLimit + 1).fill(0);
6
7// Sieve to calculate the lowest prime factor (LPF) for every number up to maxLimit
8for (let i = 2; i <= maxLimit; ++i) {
9    for (let j = i; j <= maxLimit; j += i) {
10        // If the number j doesn't have an LPF yet, set it to i
11        if (lowestPrimeFactor[j] === 0) {
12            lowestPrimeFactor[j] = i;
13        }
14    }
15}
16
17/**
18 * Function to calculate the minimum number of operations required to make the array non-decreasing
19 * @param nums - array of numbers
20 * @returns the minimum number of operations or -1 if not possible
21 */
22function minOperations(nums: number[]): number {
23    let operationsCount = 0; // Initialize the operations count
24
25    // Traverse the array from the second last element to the start
26    for (let i = nums.length - 2; ~i; --i) {
27        // Check if the current element is greater than the next element
28        if (nums[i] > nums[i + 1]) {
29            // Update the current element to its lowest prime factor
30            nums[i] = lowestPrimeFactor[nums[i]];
31          
32            // If the updated current element is still greater than the next, return -1
33            if (nums[i] > nums[i + 1]) {
34                return -1;
35            }
36            // Increment the operations count
37            ++operationsCount;
38        }
39    }
40
41    return operationsCount; // Return the total number of operations
42}
43

Time and Space Complexity

To analyze the computational complexity of the code, two main parts should be evaluated: the preprocessing step and the operations within the Solution class's minOperations method.

  1. Preprocessing Step:

    • The preprocessing step uses a sieve-like method to fill the lpf array, which is designed for finding the least prime factor for each number up to mx = 10^6 + 1.
    • The outer loop runs for each number from 2 to mx, which is O(M), where M = 10^6.
    • For each prime number i, the inner loop visits each multiple of i, collectively resulting in a complexity akin to the Sieve of Eratosthenes.
    • Thus, the preprocessing time complexity is O(M \log \log M).
  2. Operation in minOperations:

    • The minOperations function processes each element in the input nums array.
    • The main operation inside the function is a single traversal and comparison for each element, resulting in a complexity of O(n), where n is the length of the nums array.
  3. Space Complexity:

    • The space is primarily consumed by the lpf array, which stores the least prime factors for numbers up to M.
    • Hence, the space complexity is O(M).

Combining these insights, the overall complexities are:

  • Time Complexity: O(M \log \log M) + O(n)
  • Space Complexity: O(M)

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


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

Which of the following shows the order of node visit in a Breadth-first Search?


Recommended Readings

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


Load More