3326. Minimum Division Operations to Make Array Non Decreasing
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:
-
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. -
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.
Solution Approach
Preprocessing
- Find Smallest Prime Factors:
- Initialize an array
lpf
to store the smallest prime factor for each integer from2
to1,000,000
. - Iterate over each number
i
starting from2
. Iflpf[i]
is0
(indicating it is a prime number), assign it as its own smallest prime factor. - Update the multiples of
i
, assigningi
as the smallest prime factor for these multiples using a nested loop.
- Initialize an array
This preprocess allows quick access to the smallest divisor, thereby efficiently performing reduction operations on any element.
Main Algorithm
-
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]
, replacenums[i]
withlpf[nums[i]]
, the smallest divisor. - If after substitution,
nums[i]
is still greater thannums[i + 1]
, the array cannot be made non-decreasing using the allowed operations, thus return-1
.
- If
- Start from the second last element of the array
-
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 EvaluatorExample 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
- Initialize an array
lpf
for numbers up to the maximum value we might encounter, say10^6
. Let's focus on a smaller range for our example. - 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
- Start from the second last element and move left: Compare
nums[i]
withnums[i + 1]
. nums = [8, 5, 10, 4]
:- Check elements from right to left: start with
nums[2]
andnums[3]
:10 > 4
: Divide10
by its LPF (2),10 / 2 = 5
, thusnums = [8, 5, 5, 4]
.- Now
5 > 4
, divide5
bylpf[5]
(which is 5 itself as it is prime),5 / 5 = 1
, thusnums = [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.
- Check elements from right to left: start with
- 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.
-
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 tomx = 10^6 + 1
. - The outer loop runs for each number from
2
tomx
, which isO(M)
, whereM = 10^6
. - For each prime number
i
, the inner loop visits each multiple ofi
, collectively resulting in a complexity akin to the Sieve of Eratosthenes. - Thus, the preprocessing time complexity is
O(M \log \log M)
.
- The preprocessing step uses a sieve-like method to fill the
-
Operation in
minOperations
:- The
minOperations
function processes each element in the inputnums
array. - The main operation inside the function is a single traversal and comparison for each element, resulting in a complexity of
O(n)
, wheren
is the length of thenums
array.
- The
-
Space Complexity:
- The space is primarily consumed by the
lpf
array, which stores the least prime factors for numbers up toM
. - Hence, the space complexity is
O(M)
.
- The space is primarily consumed by the
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.
Which of the following shows the order of node visit in a Breadth-first Search?
Recommended Readings
Greedy Introduction div class responsive iframe iframe src https www youtube com embed WTslqPbj7I title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture web share allowfullscreen iframe div When do we use greedy Greedy algorithms tend to solve optimization problems Typically they will ask you to calculate the max min of some value Commonly you may see this phrased in the problem as max min longest shortest largest smallest etc These keywords can be identified by just scanning
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
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
Want a Structured Path to Master System Design Too? Don’t Miss This!