Mathematical Logic Problems
Solve one Java logic problem at a time, then flip for the complete explanation and program.
Solve one Java logic problem at a time, then flip for the complete explanation and program.
Java Logic Development · Chapter 05 Companion Article
Fifteen formula-driven problems — factorial, Fibonacci, GCD/HCF/LCM, number-system conversions, percentage, average, interest, and leap years — focused on converting a mathematical rule into correct, overflow-aware Java logic.
Mathematical logic problems are common in Java programming practice because they build confidence with loops, conditions, arithmetic operators, methods, number systems, and formula-based problem solving.
The main goal is not to memorize programs. Understand how the mathematical rule is converted into Java logic.
The factorial of a non-negative integer n is the product of all positive integers from 1 to n. Formula: n! = n × (n - 1) × (n - 2) × ... × 1. Examples: 0! = 1, 1! = 1, 5! = 5 × 4 × 3 × 2 × 1 = 120. Factorial is defined only for non-negative integers in this basic problem.
Start with factorial = 1 and multiply it by every number from 1 through n. For n = 5:
| i | factorial |
|---|---|
| 1 | 1 |
| 2 | 2 |
| 3 | 6 |
| 4 | 24 |
| 5 | 120 |
public class FactorialExample {
public static void main(String[] args) {
int number = 5;
long factorial = 1;
for (int i = 1; i <= number; i++) {
factorial *= i;
}
System.out.println("Factorial = " + factorial);
}
}
Factorial = 120
The loop visits every integer required by the factorial definition. The running value stored in factorial contains the product calculated so far. Starting with 1 is essential because 1 is the multiplicative identity. Starting with 0 would make every factorial result 0.
For large values, use BigInteger.
O(n)O(1)i < number instead of i <= number.If the interviewer asks for very large factorials, mention BigInteger rather than only changing int to long.
The Fibonacci sequence starts with 0, 1. Every next number is the sum of the previous two numbers. Sequence: 0, 1, 1, 2, 3, 5, 8, 13, 21, .... Formula: F(n) = F(n - 1) + F(n - 2).
Maintain two variables: first as the current Fibonacci number, and second as the next Fibonacci number. Calculate next = first + second, then move the values forward.
public class FibonacciExample {
public static void main(String[] args) {
int terms = 10;
int first = 0;
int second = 1;
for (int i = 1; i <= terms; i++) {
System.out.print(first + " ");
int next = first + second;
first = second;
second = next;
}
}
}
0 1 1 2 3 5 8 13 21 34
For the first few iterations:
| first | second | next |
|---|---|---|
| 0 | 1 | 1 |
| 1 | 1 | 2 |
| 1 | 2 | 3 |
| 2 | 3 | 5 |
The old second becomes the new first, while next becomes the new second.
Only the previous two Fibonacci numbers are needed to calculate the next value. Therefore, storing the complete sequence is unnecessary when the requirement is only to print it.
terms = 0 produces no numbers.A basic recursive implementation repeatedly recalculates the same Fibonacci values. Naive recursive complexity is approximately O(2^n). The iterative solution runs in O(n) and is normally preferred.
O(n)O(1)first before calculating next.next instead of the correct current term.Be ready to explain why iterative Fibonacci is significantly more efficient than the straightforward recursive implementation.
GCD means Greatest Common Divisor. It is the largest positive integer that divides both numbers without leaving a remainder. Example: for 48 and 18, factors of 48 include 1, 2, 3, 4, 6, 8, 12, 16, 24, 48, factors of 18 include 1, 2, 3, 6, 9, 18. The greatest common factor is 6. Therefore GCD(48, 18) = 6.
The efficient method repeatedly applies gcd(a, b) = gcd(b, a % b). Continue until b becomes 0. The remaining value of a is the GCD.
public class GCDExample {
public static void main(String[] args) {
int a = 48;
int b = 18;
while (b != 0) {
int remainder = a % b;
a = b;
b = remainder;
}
System.out.println("GCD = " + a);
}
}
GCD = 6
| a | b | a % b |
|---|---|---|
| 48 | 18 | 12 |
| 18 | 12 | 6 |
| 12 | 6 | 0 |
When b becomes 0, a contains 6.
If a number divides both a and b, it also divides the remainder produced by a % b. Therefore, replacing (a, b) with (b, a % b) preserves their GCD while progressively reducing the numbers.
O(log(min(a, b)))O(1)Euclid's algorithm is generally the expected optimized answer for GCD problems.
HCF means Highest Common Factor. HCF and GCD refer to the same mathematical value. For example, HCF(24, 36) = 12. To demonstrate another problem-solving technique, HCF can be found by checking possible common factors.
A common factor cannot be greater than the smaller input number. Therefore: find the smaller number, check numbers from 1 through that value, and if both numbers are divisible by the current value, store it as the current HCF.
public class HCFExample {
public static void main(String[] args) {
int a = 24;
int b = 36;
int limit = Math.min(a, b);
int hcf = 1;
for (int i = 1; i <= limit; i++) {
if (a % i == 0 && b % i == 0) {
hcf = i;
}
}
System.out.println("HCF = " + hcf);
}
}
HCF = 12
Every integer up to the smaller number is tested. Whenever a common divisor is found, hcf is updated. Because the loop runs in increasing order, the final stored common divisor is the highest one.
O(min(a, b))O(1)For production code or large values, prefer the Euclidean algorithm used in the GCD example.
If asked for optimization, move from factor scanning to Euclid's algorithm.
LCM means Least Common Multiple. It is the smallest positive number divisible by both input numbers. Example: multiples of 12 are 12, 24, 36, 48, ..., multiples of 18 are 18, 36, 54, .... Therefore LCM(12, 18) = 36.
For positive integers: LCM(a, b) = (a × b) / GCD(a, b). A safer implementation divides before multiplying: (a / gcd) * b. This reduces overflow risk.
public class LCMExample {
public static void main(String[] args) {
int a = 12;
int b = 18;
int x = a;
int y = b;
while (y != 0) {
int remainder = x % y;
x = y;
y = remainder;
}
int gcd = x;
int lcm = (a / gcd) * b;
System.out.println("LCM = " + lcm);
}
}
LCM = 36
The GCD represents the common factor already shared by both numbers. Multiplying the numbers counts that common factor twice. Dividing by the GCD removes the duplication and produces the least common multiple.
If either input is 0, the usual programming convention is LCM = 0. A production implementation should handle this case explicitly before division.
The dominant operation is GCD calculation.
O(log(min(a, b)))O(1)Mention the relationship GCD(a, b) × LCM(a, b) = |a × b| for non-zero integers.
Decimal uses base 10. Binary uses base 2. Example: 13 in decimal is 1101 in binary.
Repeatedly divide the decimal number by 2. Store each remainder. The remainders appear in reverse order, so they must be reversed at the end. For 13:
| Number | Quotient | Remainder |
|---|---|---|
| 13 | 6 | 1 |
| 6 | 3 | 0 |
| 3 | 1 | 1 |
| 1 | 0 | 1 |
Remainders bottom-to-top: 1101
public class DecimalToBinary {
public static void main(String[] args) {
int number = 13;
if (number == 0) {
System.out.println("Binary = 0");
return;
}
StringBuilder binary = new StringBuilder();
while (number > 0) {
binary.append(number % 2);
number /= 2;
}
binary.reverse();
System.out.println("Binary = " + binary);
}
}
Binary = 1101
Every remainder tells whether the current binary position contains 0 or 1. Repeated division by the base extracts digits from least significant to most significant order.
Java also provides Integer.toBinaryString(number). The manual approach is more useful when learning number-system logic.
For decimal value n:
O(log₂ n)O(log₂ n)Each binary digit represents a power of 2. For 1101: calculation 1 × 2³ + 1 × 2² + 0 × 2¹ + 1 × 2⁰, result 8 + 4 + 0 + 1 = 13.
Instead of manually calculating powers, process each digit using decimal = decimal * 2 + digit.
public class BinaryToDecimal {
public static void main(String[] args) {
String binary = "1101";
int decimal = 0;
for (int i = 0; i < binary.length(); i++) {
int digit = binary.charAt(i) - '0';
if (digit != 0 && digit != 1) {
System.out.println("Invalid binary number");
return;
}
decimal = decimal * 2 + digit;
}
System.out.println("Decimal = " + decimal);
}
}
Decimal = 13
| Digit | Previous Decimal | New Decimal |
|---|---|---|
| 1 | 0 | 1 |
| 1 | 1 | 3 |
| 0 | 3 | 6 |
| 1 | 6 | 13 |
Multiplying the accumulated value by 2 shifts its binary place value one position left. Adding the current digit inserts the new least significant bit.
Java provides Integer.parseInt(binary, 2).
For n binary digits:
O(n)O(1) excluding input storageOctal is a base-8 number system. Its valid digits are 0 through 7. To convert decimal to octal, repeatedly divide by 8 and collect remainders. Example: decimal 83:
| Number | Quotient | Remainder |
|---|---|---|
| 83 | 10 | 3 |
| 10 | 1 | 2 |
| 1 | 0 | 1 |
Reverse the remainders: 123
public class DecimalToOctal {
public static void main(String[] args) {
int number = 83;
if (number == 0) {
System.out.println("Octal = 0");
return;
}
StringBuilder octal = new StringBuilder();
while (number > 0) {
octal.append(number % 8);
number /= 8;
}
octal.reverse();
System.out.println("Octal = " + octal);
}
}
Octal = 123
Dividing by 8 extracts one base-8 digit at a time. The remainder is always between 0 and 7, which matches the valid octal digit range.
Integer.toOctalString(number)
O(log₈ n)O(log₈ n)Each octal digit represents a power of 8. For octal 123: 1 × 8² + 2 × 8¹ + 3 × 8⁰, 64 + 16 + 3 = 83.
Process digits from left to right: decimal = decimal * 8 + digit.
public class OctalToDecimal {
public static void main(String[] args) {
String octal = "123";
int decimal = 0;
for (int i = 0; i < octal.length(); i++) {
int digit = octal.charAt(i) - '0';
if (digit < 0 || digit > 7) {
System.out.println("Invalid octal number");
return;
}
decimal = decimal * 8 + digit;
}
System.out.println("Decimal = " + decimal);
}
}
Decimal = 83
Multiplication by 8 moves the accumulated number one octal position to the left. The current digit is then added at the lowest position.
Integer.parseInt(octal, 8)
For n octal digits:
O(n)O(1) excluding input storageHexadecimal uses base 16. It uses these symbols:
| Decimal | Hexadecimal |
|---|---|
| 0-9 | 0-9 |
| 10 | A |
| 11 | B |
| 12 | C |
| 13 | D |
| 14 | E |
| 15 | F |
Example: decimal 255 is hexadecimal FF.
Divide repeatedly by 16. For 255: 255 % 16 = 15 → F, 15 % 16 = 15 → F. Reverse the extracted digits: FF.
public class DecimalToHexadecimal {
public static void main(String[] args) {
int number = 255;
if (number == 0) {
System.out.println("Hexadecimal = 0");
return;
}
String digits = "0123456789ABCDEF";
StringBuilder hexadecimal = new StringBuilder();
while (number > 0) {
int remainder = number % 16;
hexadecimal.append(digits.charAt(remainder));
number /= 16;
}
hexadecimal.reverse();
System.out.println("Hexadecimal = " + hexadecimal);
}
}
Hexadecimal = FF
The remainder of division by 16 is always between 0 and 15. The string 0123456789ABCDEF provides the correct hexadecimal character for every possible remainder.
Integer.toHexString(number). The built-in method normally returns alphabetic hexadecimal digits in lowercase.
O(log₁₆ n)O(log₁₆ n)Percentage represents a value out of 100. For marks: percentage = (obtained marks / total marks) × 100. Suppose obtained marks = 425, total marks = 500. Percentage: 425 / 500 × 100 = 85%.
public class PercentageExample {
public static void main(String[] args) {
double obtainedMarks = 425;
double totalMarks = 500;
double percentage = obtainedMarks / totalMarks * 100;
System.out.printf("Percentage = %.2f%%%n", percentage);
}
}
Percentage = 85.00%
Consider integer division 425 / 500. When both operands are integers, Java produces 0, not 0.85. Using double allows fractional values to be preserved.
In realistic applications: total marks should be greater than 0, obtained marks should normally not be negative, and depending on the system, obtained marks should not exceed total marks.
O(1)O(1)Questions involving percentage often test Java numeric types and integer division rather than the formula itself.
The arithmetic mean is calculated as average = sum of values / number of values. For 10, 20, 30, 40, 50: sum 150, count 5, average 30.
public class AverageExample {
public static void main(String[] args) {
int[] numbers = {10, 20, 30, 40, 50};
int sum = 0;
for (int number : numbers) {
sum += number;
}
double average = (double) sum / numbers.length;
System.out.println("Average = " + average);
}
}
Average = 30.0
sum and numbers.length are integers. Casting sum to double forces floating-point division: (double) sum / numbers.length. Without the cast, a fractional part could be lost before the result is stored in a double.
Values 10, 20, 25: average 55 / 3 = 18.333.... Integer division would incorrectly produce 18.
An empty array has length 0. Attempting to divide by its length is invalid, so production code should verify that the array contains at least one element.
For n values:
O(n)O(1)For very large datasets, discuss numeric overflow in the sum and consider using long or a streaming calculation where appropriate.
Simple interest is calculated only on the original principal amount. Formula: SI = (P × R × T) / 100, where P is principal amount, R is annual interest rate in percent, and T is time in years.
Principal 10000, rate 5%, time 2 years. Interest: 10000 × 5 × 2 / 100 = 1000. Final amount: 11000.
public class SimpleInterestExample {
public static void main(String[] args) {
double principal = 10000;
double rate = 5;
double time = 2;
double interest = principal * rate * time / 100;
double totalAmount = principal + interest;
System.out.printf("Simple Interest = %.2f%n", interest);
System.out.printf("Total Amount = %.2f%n", totalAmount);
}
}
Simple Interest = 1000.00
Total Amount = 11000.00
Simple interest does not add previous interest back into the principal during the calculation. The same original principal is used for every year.
O(1)O(1)/ 100 when the rate is expressed as a percentage.Real financial applications may use periods in months or days and have specific rounding rules. The exact calculation should follow the financial product's terms.
Compound interest calculates interest on both the original principal and previously accumulated interest. For annual compounding: Amount = P × (1 + R / 100)^T. Compound interest: CI = Amount - P.
Principal = 10000, rate = 10%, time = 2 years. Amount: 10000 × (1.10)² = 12100. Compound interest: 12100 - 10000 = 2100.
public class CompoundInterestExample {
public static void main(String[] args) {
double principal = 10000;
double rate = 10;
double time = 2;
double amount = principal * Math.pow(1 + rate / 100, time);
double compoundInterest = amount - principal;
System.out.printf("Compound Interest = %.2f%n", compoundInterest);
System.out.printf("Total Amount = %.2f%n", amount);
}
}
Compound Interest = 2100.00
Total Amount = 12100.00
The compounding factor 1 + rate / 100 must be raised to the number of compounding periods. Math.pow(base, exponent) performs this exponentiation.
When interest compounds n times per year: Amount = P × (1 + R / (100 × n))^(n × T). Examples of n: annually → 1, half-yearly → 2, quarterly → 4, monthly → 12.
| Simple Interest | Compound Interest |
|---|---|
| Calculated on original principal | Calculated on growing balance |
| Linear growth | Exponential growth |
| No interest-on-interest | Includes interest-on-interest |
| Formula is simpler | Uses exponentiation |
O(1) for normal application-level use of the formulaO(1)rate / 100.For real monetary systems, double can introduce floating-point rounding behavior. Financial software commonly uses BigDecimal when exact decimal arithmetic and defined rounding rules are required.
A leap year normally contains 366 days instead of 365. February has 29 days during a leap year. A year is a leap year when it is divisible by 400, or it is divisible by 4 but not divisible by 100. Equivalent condition: year % 400 == 0 || (year % 4 == 0 && year % 100 != 0).
| Year | Result | Reason |
|---|---|---|
| 2024 | Leap year | Divisible by 4 and not 100 |
| 2025 | Not leap year | Not divisible by 4 |
| 1900 | Not leap year | Divisible by 100 but not 400 |
| 2000 | Leap year | Divisible by 400 |
public class LeapYearExample {
public static void main(String[] args) {
int year = 2024;
boolean leapYear = year % 400 == 0 || (year % 4 == 0 && year % 100 != 0);
if (leapYear) {
System.out.println(year + " is a leap year");
} else {
System.out.println(year + " is not a leap year");
}
}
}
2024 is a leap year
A simplified rule such as year % 4 == 0 fails for century years. For example, 1900 % 4 == 0 but 1900 is not a leap year because it is divisible by 100 and not by 400. 2000 is a leap year because it is divisible by 400.
For 2024: 2024 % 400 != 0, 2024 % 4 == 0, 2024 % 100 != 0. Result: true.
For 1900: 1900 % 400 != 0, 1900 % 4 == 0, 1900 % 100 == 0. Result: false.
O(1)O(1)&& where || is required.Century years such as 1900, 2000, and 2100 are useful test cases because they expose incorrect leap-year logic.
These problems use a small number of reusable programming techniques.
Used in factorial and compound interest through exponentiation. The key question is whether the problem requires repeated multiplication or a direct mathematical formula.
Used in Fibonacci series. Only a small amount of previous state may be required instead of storing every generated value.
Used heavily in GCD, HCF, LCM, decimal-to-binary conversion, decimal-to-octal conversion, decimal-to-hexadecimal conversion, and leap-year checking. The modulus operator % answers questions such as: is a number divisible, what is the remainder, and what is the next digit in a base conversion.
For a number in base b, a left-to-right conversion can use result = result × b + currentDigit. Examples: binary base 2, octal base 8, hexadecimal base 16. This pattern avoids repeatedly calculating powers.
Used in percentage, average, simple interest, and compound interest. The major Java concern is often numeric type selection rather than the formula itself.
| Number System | Base | Valid Digits |
|---|---|---|
| Binary | 2 | 0, 1 |
| Octal | 8 | 0-7 |
| Decimal | 10 | 0-9 |
| Hexadecimal | 16 | 0-9, A-F |
A useful general rule for decimal-to-base conversion is: divide by the target base, store the remainder, continue using the quotient, and reverse the collected digits. For base-to-decimal conversion: result = result × base + digit.
Suitable for ordinary integer arithmetic when values remain within -2,147,483,648 to 2,147,483,647. Useful for small GCD/HCF problems, leap years, small Fibonacci values, and small conversion exercises.
Provides a larger integer range and is useful for larger factorials, larger Fibonacci numbers, and arithmetic where int could overflow. It still has a fixed maximum size.
Useful when fractional results are expected: percentage, average, interest calculations. It uses floating-point representation, so some decimal values cannot be represented exactly.
Useful when integer values can become extremely large: large factorials, large Fibonacci numbers.
Useful when exact decimal arithmetic and controlled rounding are required, especially for monetary calculations.
Mathematical logic can be mathematically correct but still produce an incorrect Java result because of numeric overflow. Example: a factorial grows rapidly — 5! = 120, 10! = 3,628,800, 20! = 2,432,902,008,176,640,000. A program should select a numeric type based on the expected input range. The same concern applies to Fibonacci numbers, LCM calculations, large base conversions, and financial calculations.
Validation should match the actual problem rather than being added mechanically. Useful validations include:
Java provides several utilities for number conversion. Examples:
Integer.toBinaryString(number);
Integer.toOctalString(number);
Integer.toHexString(number);
Integer.parseInt(value, 2);
Integer.parseInt(value, 8);
Integer.parseInt(value, 16);
Built-in methods are appropriate in application code when manual implementation provides no additional value. During logic-development exercises and interviews, manual conversion is useful because it demonstrates understanding of division, remainders, base systems, string construction, and digit validation. A strong answer can explain both approaches and choose the one suitable for the requirement.
Common variations include: factorial using loop, factorial using recursion, factorial using BigInteger, trailing zeros in factorial, and factorial of multiple numbers.
Possible questions include: print first n Fibonacci numbers, find the nth Fibonacci number, Fibonacci using recursion, Fibonacci using dynamic programming, check whether a number belongs to the Fibonacci sequence, and sum Fibonacci terms.
Interview problems may ask for: GCD using loops, GCD using Euclid's algorithm, recursive GCD, GCD of an array, and GCD without library methods.
Common extensions include: LCM using repeated multiples, LCM using GCD, LCM of multiple numbers, and GCD and LCM in the same program.
Be prepared for: binary to decimal, decimal to binary, octal to decimal, decimal to octal, hexadecimal to decimal, decimal to hexadecimal, binary to octal, binary to hexadecimal, conversion without built-in methods, and input validation for each number system.
This is one of the most frequent Java mistakes. Example: int value = 5 / 2; results in 2, not 2.5. Use floating-point arithmetic when fractions are required. Example: double value = 5.0 / 2; results in 2.5.
A factorial loop stopping before n produces an incorrect result. Always verify initial value, loop start, loop condition, and increment or decrement.
This commonly affects Fibonacci programs. Calculate the next state before overwriting values required to calculate it.
Every number system has a restricted digit set: binary 0-1, octal 0-7, decimal 0-9, hexadecimal 0-9 and A-F.
Zero frequently requires special handling. Examples: 0! = 1, decimal zero converts to "0", LCM involving zero is conventionally zero, and division formulas must protect against zero denominators.
Correct mathematical logic does not guarantee correct machine arithmetic. Know whether the expected result fits inside int, long, double, BigInteger, or BigDecimal.
| Problem | Typical Time | Extra Space |
|---|---|---|
| Factorial | O(n) | O(1) |
| Fibonacci Series | O(n) | O(1) |
| GCD using Euclid | O(log min(a,b)) | O(1) |
| HCF using factor scan | O(min(a,b)) | O(1) |
| LCM using GCD | O(log min(a,b)) | O(1) |
| Decimal to Binary | O(log₂ n) | O(log n) |
| Binary to Decimal | O(n) | O(1) |
| Decimal to Octal | O(log₈ n) | O(log n) |
| Octal to Decimal | O(n) | O(1) |
| Decimal to Hexadecimal | O(log₁₆ n) | O(log n) |
| Percentage | O(1) | O(1) |
| Average | O(n) | O(1) |
| Simple Interest | O(1) | O(1) |
| Compound Interest | O(1) for formula-level analysis | O(1) |
| Leap Year Check | O(1) | O(1) |
| Topic | Main Logic |
|---|---|
| Factorial | Repeated multiplication |
| Fibonacci | Previous two values |
| GCD | Euclidean algorithm |
| HCF | Common-factor checking |
| LCM | GCD-based formula |
| Decimal to Binary | Repeated division by 2 |
| Binary to Decimal | Multiply accumulated value by 2 |
| Decimal to Octal | Repeated division by 8 |
| Octal to Decimal | Multiply accumulated value by 8 |
| Decimal to Hexadecimal | Repeated division by 16 |
| Percentage | Part ÷ total × 100 |
| Average | Sum ÷ count |
| Simple Interest | P × R × T ÷ 100 |
| Compound Interest | P × (1 + R/100)^T |
| Leap Year | Divisibility by 4, 100 and 400 |
After completing this chapter, a learner should be able to: