mirror of
https://github.com/TheAlgorithms/Java.git
synced 2025-07-08 10:15:51 +08:00
Generalize NthUglyNumber (#4209)
This commit is contained in:
@ -1,57 +1,82 @@
|
||||
// Ugly numbers are numbers whose only prime factors are 2, 3 or 5. The sequence 1, 2, 3, 4, 5, 6, 8, 9, 10, 12, 15, … shows the first 11 ugly numbers.
|
||||
// By convention, 1 is included.
|
||||
// A program to find the nth Ugly number
|
||||
// Algorithm :
|
||||
// Initialize three-pointers two, three, and five pointing to zero.
|
||||
// Take 3 variables nm2, nm3, and nm5 to keep track of next multiple of 2,3 and 5.
|
||||
// Make an array of size n to store the ugly numbers with 1 at 0th index.
|
||||
// Initialize a variable next which stores the value of the last element in the array.
|
||||
// Run a loop n-1 times and perform steps 6,7 and 8.
|
||||
// Update the values of nm2, nm3, nm5 as ugly[two]*2, ugly[three]*3, ugly[5]*5 respectively.
|
||||
// Select the minimum value from nm2, nm3, and nm5 and increment the pointer related to it.
|
||||
// Store the minimum value in variable next and array.
|
||||
// Return next.
|
||||
package com.thealgorithms.maths;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.HashMap;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.lang.IllegalArgumentException;
|
||||
|
||||
class NthUglyNumber {
|
||||
|
||||
/* Function to get the nth ugly number*/
|
||||
public long getNthUglyNo(int n) {
|
||||
long[] ugly = new long[n];
|
||||
int two = 0, three = 0, five = 0;
|
||||
long nm2 = 2, nm3 = 3, nm5 = 5;
|
||||
long next = 1;
|
||||
/**
|
||||
* @brief class computing the n-th ugly number (when they are sorted)
|
||||
* @details the ugly numbers with base [2, 3, 5] are all numbers of the form 2^a*3^b^5^c,
|
||||
* where the exponents a, b, c are non-negative integers.
|
||||
* Some properties of ugly numbers:
|
||||
* - base [2, 3, 5] ugly numbers are the 5-smooth numbers, cf. https://oeis.org/A051037
|
||||
* - base [2, 3, 5, 7] ugly numbers are 7-smooth numbers, cf. https://oeis.org/A002473
|
||||
* - base [2] ugly numbers are the non-negative powers of 2,
|
||||
* - the base [2, 3, 5] ugly numbers are the same as base [5, 6, 2, 3, 5] ugly numbers
|
||||
*/
|
||||
public class NthUglyNumber {
|
||||
ArrayList<Long> uglyNumbers = new ArrayList<>(Arrays.asList(1L));
|
||||
final int[] baseNumbers;
|
||||
HashMap<Integer, Integer> positions = new HashMap<>();
|
||||
|
||||
ugly[0] = 1;
|
||||
/**
|
||||
* @brief initialized the object allowing to compute ugly numbers with given base
|
||||
* @param baseNumbers the given base of ugly numbers
|
||||
* @exception IllegalArgumentException baseNumber is empty
|
||||
*/
|
||||
NthUglyNumber(int[] baseNumbers) {
|
||||
if (baseNumbers.length == 0) {
|
||||
throw new IllegalArgumentException("baseNumbers must be non-empty.");
|
||||
}
|
||||
|
||||
for (int i = 1; i < n; i++) {
|
||||
next = Math.min(nm2, Math.min(nm3, nm5));
|
||||
this.baseNumbers = baseNumbers;
|
||||
for (final var baseNumber : baseNumbers) {
|
||||
this.positions.put(baseNumber, 0);
|
||||
}
|
||||
}
|
||||
|
||||
ugly[i] = next;
|
||||
if (next == nm2) {
|
||||
two = two + 1;
|
||||
nm2 = ugly[two] * 2;
|
||||
}
|
||||
if (next == nm3) {
|
||||
three = three + 1;
|
||||
nm3 = ugly[three] * 3;
|
||||
}
|
||||
if (next == nm5) {
|
||||
five = five + 1;
|
||||
nm5 = ugly[five] * 5;
|
||||
/**
|
||||
* @param n the zero-based-index of the queried ugly number
|
||||
* @exception IllegalArgumentException n is negative
|
||||
* @return the n-th ugly number (starting from index 0)
|
||||
*/
|
||||
public Long get(int n) {
|
||||
if (n < 0) {
|
||||
throw new IllegalArgumentException("n must be non-negative.");
|
||||
}
|
||||
|
||||
while (uglyNumbers.size() <= n) {
|
||||
addUglyNumber();
|
||||
}
|
||||
|
||||
return uglyNumbers.get(n);
|
||||
}
|
||||
|
||||
private void addUglyNumber() {
|
||||
uglyNumbers.add(computeMinimalCandidate());
|
||||
updatePositions();
|
||||
}
|
||||
|
||||
private void updatePositions() {
|
||||
final var lastUglyNumber = uglyNumbers.get(uglyNumbers.size() - 1);
|
||||
for (final var baseNumber : baseNumbers) {
|
||||
if (computeCandidate(baseNumber) == lastUglyNumber) {
|
||||
positions.put(baseNumber, positions.get(baseNumber) + 1);
|
||||
}
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
Scanner sc = new Scanner(System.in);
|
||||
System.out.println("Enter the value of n : ");
|
||||
int n = sc.nextInt();
|
||||
NthUglyNumber ob = new NthUglyNumber();
|
||||
long ugly = ob.getNthUglyNo(n);
|
||||
System.out.println("nth Ugly number is : " + ugly);
|
||||
private long computeCandidate(int candidateBase) {
|
||||
return candidateBase * uglyNumbers.get(positions.get(candidateBase));
|
||||
}
|
||||
|
||||
private long computeMinimalCandidate() {
|
||||
long res = Long.MAX_VALUE;
|
||||
for (final var baseNumber : baseNumbers) {
|
||||
res = Math.min(res, computeCandidate(baseNumber));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
87
src/test/java/com/thealgorithms/maths/NthUglyNumberTest.java
Normal file
87
src/test/java/com/thealgorithms/maths/NthUglyNumberTest.java
Normal file
@ -0,0 +1,87 @@
|
||||
package com.thealgorithms.maths;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class NthUglyNumberTest {
|
||||
@Test
|
||||
public void testGetWithNewObject() {
|
||||
HashMap<Integer, Long> testCases = new HashMap<>();
|
||||
testCases.put(0, 1L);
|
||||
testCases.put(1, 2L);
|
||||
testCases.put(2, 3L);
|
||||
testCases.put(3, 4L);
|
||||
testCases.put(4, 5L);
|
||||
testCases.put(5, 6L);
|
||||
testCases.put(9, 12L);
|
||||
testCases.put(19, 36L);
|
||||
testCases.put(52, 270L);
|
||||
testCases.put(1078, 84934656L);
|
||||
testCases.put(1963, 6973568802L);
|
||||
|
||||
for (final var tc : testCases.entrySet()) {
|
||||
var uglyNumbers = new NthUglyNumber(new int[] {2, 3, 5});
|
||||
assertEquals(uglyNumbers.get(tc.getKey()), tc.getValue());
|
||||
|
||||
var otherUglyNumbers = new NthUglyNumber(new int[] {5, 25, 6, 2, 3, 5});
|
||||
assertEquals(otherUglyNumbers.get(tc.getKey()), tc.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetWithSameObject() {
|
||||
HashMap<Integer, Long> testCases = new HashMap<>();
|
||||
testCases.put(0, 1L);
|
||||
testCases.put(1, 2L);
|
||||
testCases.put(2, 3L);
|
||||
testCases.put(3, 4L);
|
||||
testCases.put(4, 5L);
|
||||
testCases.put(5, 6L);
|
||||
testCases.put(6, 7L);
|
||||
testCases.put(1499, 1984500L);
|
||||
testCases.put(1572, 2449440L);
|
||||
testCases.put(1658, 3072000L);
|
||||
testCases.put(6625, 4300800000L);
|
||||
|
||||
var uglyNumbers = new NthUglyNumber(new int[] {7, 2, 5, 3});
|
||||
for (final var tc : testCases.entrySet()) {
|
||||
assertEquals(uglyNumbers.get(tc.getKey()), tc.getValue());
|
||||
}
|
||||
|
||||
assertEquals(uglyNumbers.get(999), 385875);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetWithBase1() {
|
||||
var uglyNumbers = new NthUglyNumber(new int[] {1});
|
||||
assertEquals(uglyNumbers.get(10), 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetWithBase2() {
|
||||
var uglyNumbers = new NthUglyNumber(new int[] {2});
|
||||
assertEquals(uglyNumbers.get(5), 32);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testGetThrowsAnErrorForNegativeInput() {
|
||||
var uglyNumbers = new NthUglyNumber(new int[] {1, 2});
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> uglyNumbers.get(-1)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConstructorThrowsAnErrorForEmptyInput() {
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> new NthUglyNumber(new int[] {})
|
||||
);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user