# Number Theory

Number Theory is a branch of mathematics that deals with the properties and relationships of numbers. It includes topics such as prime numbers, divisibility, modular arithmetic, greatest common divisor (GCD), least common multiple (LCM), and more. Let's explore each of these topics and provide an example of solving a LeetCode problem using Number Theory concepts.

Prime Numbers: Prime numbers are integers greater than 1 that have no divisors other than 1 and themselves. They play a crucial role in various number theory problems and algorithms. To check if a number is prime, we can iterate from 2 to the square root of the number and check for divisibility.

Example: Prime Number Checker
Let's write a function to check if a given number `num`

is prime:

```
def is_prime(num):
if num <= 1:
return False
for i in range(2, int(num ** 0.5) + 1):
if num % i == 0:
return False
return True
```

The time complexity of this function is O(sqrt(n)), where n is the input number. We only need to iterate up to the square root of the number to check for divisibility.

Divisibility: Divisibility refers to whether one number divides another without leaving a remainder. This concept is often used to solve problems related to factors, multiples, and divisors.

Example: Counting Divisors
Let's write a function to count the number of divisors for a given positive integer `num`

:

```
def count_divisors(num):
count = 0
for i in range(1, num + 1):
if num % i == 0:
count += 1
return count
```

The time complexity of this function is O(n), where n is the input number. We iterate from 1 to the number and check for divisibility.

Modular Arithmetic: Modular arithmetic involves performing arithmetic operations on remainders obtained when a number is divided by a specific modulus. It is used to solve problems related to congruence, remainders, and cyclic patterns.

Example: Power Modulo
Let's write a function to calculate the value of a number `base`

raised to the power `exponent`

modulo `mod`

:

```
def power_modulo(base, exponent, mod):
result = 1
base %= mod
while exponent > 0:
if exponent % 2 == 1:
result = (result * base) % mod
base = (base * base) % mod
exponent //= 2
return result
```

The time complexity of this function is O(log(exponent)), as we are performing repeated squaring to calculate the power.

Greatest Common Divisor (GCD): The GCD of two or more integers is the largest positive integer that divides each of the given numbers without leaving a remainder. It is often used to simplify fractions, find common factors, and solve linear Diophantine equations.

Example: GCD Calculation
Let's write a function to calculate the GCD of two numbers `a`

and `b`

using Euclidean Algorithm:

```
def gcd(a, b):
while b != 0:
a, b = b, a % b
return a
```

The time complexity of this function is O(log(min(a, b))), as the Euclidean algorithm has logarithmic time complexity.

Least Common Multiple (LCM): The LCM of two or more integers is the smallest positive integer that is divisible by each of the given numbers. It is often used to solve problems involving fractions, periodicity, and synchronization.

Example: LCM Calculation
Let's write a function to calculate the LCM of two numbers `a`

and `b`

using the GCD:

```
def lcm(a, b):
return abs(a * b) // gcd(a, b)
```

The time complexity of this function is O(log(min(a, b))), as we are using the GCD calculation.

These are just a few examples of how Number Theory concepts can be applied to solve problems. The time and space complexity of specific problems may vary depending on the problem statement and the approach used. It's important to analyze the problem requirements and constraints to determine the most efficient solution.

Let's solve a problem using Number Theory concepts. One such problem is "Fraction to Recurring Decimal"

Problem Description: Given two integers representing the numerator and denominator of a fraction, return the fraction in string format. If the fractional part is repeating, enclose the repeating part in parentheses.

Example: Input: numerator = 1, denominator = 2 Output: "0.5"

Input: numerator = 2, denominator = 1 Output: "2"

Input: numerator = 2, denominator = 3 Output: "0.(6)"

To solve this problem, we can use Number Theory concepts like division, remainders, and repeating patterns.

Here's the Python code to solve the problem:

```
def fractionToDecimal(numerator, denominator):
if numerator == 0:
return "0"
# Handle negative sign
sign = "-" if numerator * denominator < 0 else ""
numerator = abs(numerator)
denominator = abs(denominator)
# Calculate the integer part
integer_part = numerator // denominator
remainder = numerator % denominator
if remainder == 0:
# No fractional part
return sign + str(integer_part)
# Calculate the fractional part
fraction_part = []
remainder_map = {}
repeat_start = -1
while remainder != 0:
if remainder in remainder_map:
# Repeating pattern found
repeat_start = remainder_map[remainder]
break
remainder_map[remainder] = len(fraction_part)
quotient = remainder * 10 // denominator
fraction_part.append(str(quotient))
remainder = remainder * 10 % denominator
if repeat_start != -1:
# Insert parentheses for repeating part
fraction_part.insert(repeat_start, "(")
fraction_part.append(")")
# Combine integer, fractional, and sign parts
return sign + str(integer_part) + "." + "".join(fraction_part)
# Test the function with the given examples
print(fractionToDecimal(1, 2))
print(fractionToDecimal(2, 1))
print(fractionToDecimal(2, 3))
```

The output of the above code will be:

```
"0.5"
"2"
"0.(6)"
```

The time complexity of this solution is O(log(denominator)) since we are performing division and calculating remainders. The space complexity is O(log(denominator)) as well, considering the space required for storing remainders and the fractional part.

In this problem, we used Number Theory concepts to convert the fraction to decimal, handle repeating patterns, and return the result in the required format.