Code optimization - BigInteger - java

The code works correctly until I give it a big value - it takes too much time to execute.
Can you give me some advice how to optimize it?
BigInteger type of n parameter is a must, it's a part of the task ;)
public static String oddity(BigInteger n) {
List<BigInteger> list = new ArrayList<BigInteger>();
String result = null;
for (BigInteger bi = BigInteger.valueOf(1);
bi.compareTo(n) <= 0;
bi = bi.add(BigInteger.ONE)) {
if (n.mod(bi).equals(BigInteger.ZERO))
list.add(bi);
}
if (list.size() % 2 == 0)
result = "even";
else result = "odd";
return result;
}
The purpose of this is to return 'odd' if the number of "n" divisors is odd. Otherwise return 'even'.

Thinking rather than just programming would help a lot. You don't need to find all divisors. You don't even need to count them. All you need is to find out if the count is odd.
But divisors always come in pairs: For every divisor i also n/i is a divisor.
So the count is always even, except when there's a divisor i equal to n/i. Use Guava sqrt ...

As you don't use the list except to get its final size, you could use an integer as a counter, i.e: do n++ instead of list.add(bi).
This is going to save huge amount of memory. Hence save time used to manage its allocation.

// Lambda
long counter = IntStream
.range(1, (int) Math.sqrt(n.longValue())+1)
.filter(i -> n.longValue() % i == 0 && n.longValue() / i == i)
.count();
return (counter % 2 == 0) ? "even" : "odd";

int counter = 0;
for (long i = 1; i <= Math.sqrt(n.longValue()); i++) {
if(n.longValue() % i == 0 && n.longValue()/ i == i){
counter++;
}
}
return (counter % 2 == 0) ? "even" : "odd";

Related

What are good practices of making code faster?

Problem statement: Sherlock and the Valid String
This code passes all tests for correctness, 15/20, however, some tests are failing because of the time limits.
What are good practices about making code faster? How can for loops be avoided?
static String isValid(String s) {
String yesOrNo;
//Step 1: count the frequency of each char and out in a map <char, number>
Map<Character, Integer> map = new HashMap<>();
for (int i = 0; i < s.length(); i++) {
int count = 0;
for (int c = 0; c < s.length(); c++) {
if (s.charAt(c) == s.charAt(i))
count++;
}
map.put(s.charAt(i), count);
}
//Step2: add all the numbers of occurrences of each char into a list
List<Integer> values = new ArrayList<>();
for (Map.Entry<Character, Integer> kv : map.entrySet()
) {
values.add(kv.getValue());
}
//Step 3: find the benchmark number
Map<Integer, Integer> occurPairs = new TreeMap<>();
for (int i = 0; i < values.size(); i++) {
occurPairs.put(values.get(i), Collections.frequency(values, values.get(i)));
}
Map.Entry<Integer, Integer> popVal = Collections.max(occurPairs.entrySet(), Map.Entry.comparingByValue());
Map.Entry<Integer, Integer> smallest = Collections.min(occurPairs.entrySet(), Map.Entry.comparingByValue());
//Step 4: compare each value with the benchmark
int numOfWrong = 0;
for (Integer value : values) {
if (!value.equals(popVal.getKey()))
numOfWrong += Math.abs(popVal.getKey() - value);
}
if (occurPairs.size() == 2 && smallest.getValue() == 1 && smallest.getKey() == 1)
yesOrNo = "YES";
else if (numOfWrong > 1)
yesOrNo = "NO";
else
yesOrNo = "YES";
System.out.println(yesOrNo);
return yesOrNo;
}
I won't tell you outright what's wrong but I'll give you a conceptual framework by which you can analyze your code.
Think about the number of steps you perform per letter in the input string.
If you perform a fixed number of steps then you'll get linear scaling. Let's say you do three steps per letter. If there are n letters and you perform 3n operations, you're in good shape.
However, if you perform n operations per letter then you'll get quadratic scaling with n² or 3n² or operations in total. (The constant in front is not important. It's the exponent that matters.) Let's say you have 1,000 letters. 3n scaling would mean 3 thousand operations. 3n² scaling would mean 3 million.
Quadratic basically means "doesn't scale". Instead of scaling in proportion to the input length, quadratic algorithms blow up when the input gets large. They work fine for normal workloads but fall apart when under pressure. Hacker Rank is very likely throwing really long input strings at your algorithm to detect quadratic blowup.
I talked about n above. In Java lingo n is s.length(). Can you spot the quadratic step in your code where you are performing s.length() * s.length() operations?
Yes, in step 1 I'm iterating twice over the s to count the frequency of each char.
That's right. Good. Now, how could you do step 1 in a single pass?
Think about how you'd do it on paper. You wouldn't scan the string over and over and over and over and over, right? You'd just look at each letter once and keep a running count of all the letters as you go. You'd probably have a table with letters and tally marks like:
A ||||
B
C |
D ||
E |||||||
F
...
Do the same in code and it'll cut the n² down to n.
In addition to #JohnKugelman answer, here is what you can do.
First of all, you are going through your string twice (with inner loop) to count the occurrences which is O(n^2). You already found a O(n) solution for that
Map<Character, Integer> occurences = new HashMap<>();
s.chars()
.forEach(e-> occurences.put((char)e, occurences.getOrDefault((char)e, 0) + 1));
Now we need to find a simple iteration to find out the answer.
Here are what we know about the "YES" cases.
All of the letters have same frequency e.g: aabbccddeeff
All of the letters have same frequency but a single letter which occurs 1 time more. e.g: aabbccddd
All of the letters have same frequency but a single letter with 1 occurrence. e.g: aaaabbbbcccce
So what we need to do is to go through values of our map and count the number of occurrences.
First, let's get our iterator
Iterator<Map.Entry<Character, Integer>> iterator = occurences
.entrySet()
.iterator();
Then choose first number as "benchmark" and define a variable and a count for first different value
int benchmark = iterator.next().getValue();
int benchmarkCount = 1;
int firstDifferent = -1;
int differentCount = 0;
Iterate through the numbers
while(iterator.hasNext()) {
int next = iterator.next().getValue();
if (next == benchmark) { // if the next number is same
benchmarkCount++; // just update our count
} else { // if it is different
// if we haven't found a different one yet or it is the same different value from earlier
if (firstDifferent == -1 || firstDifferent == next) {
firstDifferent = next;
differentCount++;
}
}
}
Now all we need to do is to analyze our numbers
int size = occurences.size();
if (benchmarkCount == size) return "YES"; // if all of the numbers are the same
if (benchmarkCount == size - 1) { // if we hit only single different
// either the diffent number is 1 or it is greater than our benchmark by value of 1
if (firstDifferent == 1 || firstDifferent - benchmark == 1) {
return "YES";
}
}
// same case with above
if (differentCount == size - 1) {
if (benchmark == 1 || benchmark - firstDifferent == 1) {
return "YES";
}
}
Full solution
static String isValid(String s) {
Map<Character, Integer> occurences = new HashMap<>();
s.chars().forEach(e-> occurences.put((char)e, occurences.getOrDefault((char)e, 0) + 1));
Iterator<Map.Entry<Character, Integer>> iterator = occurences
.entrySet()
.iterator();
int benchmark = iterator.next().getValue();
int benchmarkCount = 1;
int firstDifferent = -1;
int differentCount = 0;
while(iterator.hasNext()) {
int next = iterator.next().getValue();
if (next == benchmark) {
benchmarkCount++;
} else {
if (firstDifferent == -1 || firstDifferent == next) {
firstDifferent = next;
differentCount++;
}
}
}
int size = occurences.size();
if (benchmarkCount == size) return "YES";
if (benchmarkCount == size - 1) {
if (firstDifferent == 1 || firstDifferent - benchmark == 1) {
return "YES";
}
}
if (differentCount == size - 1) {
if (benchmark == 1 || benchmark - firstDifferent== 1) {
return "YES";
}
}
return "NO";
}
I find the other solutions far too complicated. Using the number of different characters (counts.length below) and their minimum frequency (min below), we know that the string length is at least counts.length * min.
When a single character occurs once more than min, we have a string longer by one.
When there are more such characters, the string gets longer than counts.length * min + 1. When any character occurs even more often than min + 1, the string gets longer than counts.length * min + 1, too.
As noted by Bunyamin Coskuner, my solution missed cases like "aabbc". This one works but it's no more as simple as wanted:
static String isValid(String s) {
if (s.isEmpty()) return "YES";
final int[] counts = s.chars()
.mapToObj(c -> c)
.collect(Collectors.groupingBy(c -> c, Collectors.counting()))
.values()
.stream()
.mapToInt(n -> n.intValue())
.toArray();
final int min = Arrays.stream(counts).min().getAsInt();
// Accounts for strings like "aabb" and "aabbb".
if (s.length() <= counts.length * min + 1) return "YES";
// Here, strings can be only valid when the minimum is one, like for "aabbc".
if (min != 1) return "NO";
final int minButOne = Arrays.stream(counts).filter(n -> n>1).min().getAsInt();
return s.length() == (counts.length - 1) * minButOne + 1 ? "YES" : "NO";
}

Getting last 2 digits after multiplication

I have an array of integer numbers, my task is to get the last 2 digits after multiplying all these numbers.
I have come up with the below code:
static void process(int array[]) {
if (array.length <= 0) {
System.out.println("-1");
return;
}
int answer = array[0] % 100;
for (int i = 1; i < array.length; i++) {
answer = (answer * array[i] % 100) % 100;
}
System.out.println(answer);
}
I felt this is a better approach, but when I used this during one my exams it passed only 2 out of 4 test cases. The test cases failed due to performance issues. The failed test cases were hidden, so not able to see them.
I even tried the alternate approach like initializing a long variable to 1 then using a for loop and multiplying the long variable with array element. Finally getting the last two digits from long variable, even that failed with 2 test cases.
Is there any better approach to solve this problem.
There are some potential shortcuts. Multiplying by a number ending in 0 or two numbers ending in 2 and 5 respectively would guarantee the last digit is a 0. Doing that twice makes your last two digits 00 and you can print the result early. You could check whether your answer is 00 and break out of the loop if that happens.
This would slow down your algorithm against data that is tailored against this check. However, a large (>1000) randomized set of numbers would be be almost guaranteed to end in 00 early and be faster than your initial approach.
Sample code:
static void process(int array[]) {
if (array.length <= 0) {
System.out.println("-1");
return;
}
int answer = array[0] % 100;
for (int i = 1; i < array.length; i++) {
if(answer == 0) {
break;
}
answer = (answer * array[i] % 100) % 100;
}
System.out.println(answer);
}
Are you sure that it is due to performance issues? If yes, then I think it is kind of a stupid question, because O(n) is the best you can get.
My guess is that it was because your answer wasn't correct. E.g. if your answer is "1", then it could actually be "01". So a correct implementation would take that into account.
boolean atLeast10 = false;
int answer = 1;
int i = 0;
for (; i < array.length && !atLeast10; i++) {
if (array[i] == 0) {
System.out.println(0);
return;
}
answer = answer * array[i];
if (answer >= 10)
atLeast10 = true;
}
answer = answer % 100;
for (; i < array.length; i++) {
if (array[i] == 0) {
System.out.println(0);
return;
}
answer = (answer * array[i] % 100) % 100;
}
if (!atLeast10 || answer >= 10)
System.out.println(answer);
else
System.out.println("0" + answer);
Btw. shortcut is only possible if an element is 0, this is again because even if the number ends in "00" there could follow a 0 much later in the array and then the answer is "0" and not "00", although I like the idea.

Recursive method to count figures in a number with boolean as return

I recently has a test in university and I was struggling with a problem. The task was defined very specifically as following:
Write a recursive method (don't change the signature, or parameters; no global variables allowed; don't use Strings or the method Stringbuffer; no loops) which returns "true" if the number of zeros in number "n" is odd and "false" if the number of zeros is even.
Signature and Parameter:
public static boolean oddZeros(int n) {
}
So for:
n = 10 //true
n = 100 //false
n = 1402050 //true
n = 0 // true
n = 12 // false
you get the idea..
I understand the concept of recursion but i fail to see how i can count something, given only booleans. I tried adding a counter variable inside the method but whenever i make a recursive call, obviously the variable would be reset to its initialization.
Since this is a very specific problem, i didn't find any solutions so far. How would a method like this look like?
public static boolean oddZeroes(int n) {
if (n < 10) {
return n == 0;
}
return (n % 10 == 0) ^ oddZeroes(n / 10);
}
You can even make it one-liner:
public static boolean oddZeroes(int n) {
return n < 10 ? n == 0 : (n % 10 == 0) ^ oddZeroes(n / 10);
}
And if you want to process negative inputs as well, add something like if (n < 0) {return oddZeroes(-n);} in the beginning, i.e.:
public static boolean oddZeroes(int n) {
if (n < 0) {
return oddZeroes(-n);
}
if (n < 10) {
return n == 0;
}
return (n % 10 == 0) ^ oddZeroes(n / 10);
}
You don't have to count anything.
You only have to observe that:
if you remove a 0 digit from a number that has an odd number of zeroes, the resulting (smaller) number does not have an odd number of zeroes.
if you remove a non 0 digit from a number that has an odd number of zeroes, the resulting (smaller) number also has an odd number of zeroes.
Finally, as the base of the recursion, if 0 < number < 10, it has an even number of 0s (0 0s), so your method should return false.
You can write a shorter implementation, but I preferred readability:
public static boolean oddZeros(int n) {
if (n == 0)
return true;
else if (n < 10)
return false;
else if (oddZeros (n / 10)) {
return n % 10 != 0; // removed digit is not 0
} else {
return n % 10 == 0; // removed digit is 0
}
}
EDIT:
This assumes the input is non-negative. If you need to support negative input, you can add an initial condition of:
if (n < 0) {
return oddZeros (-n);
}

Power of two failed to pass the test

Isn't this one way to test whether a number is a power of 2?
boolean isPowerOfTwo(int n) {
return (n%2==0);
}
Are you sure that you are checking power of 2? Rather, being checked divisible with 2. If you are serious, use the piece of cake snippet
return n > 0 && ((n & -n) == n);
The second way,
private static boolean powerOf2(int num)
{
if(num <= 0){
return false;
}
while(num > 1)
{
if(num % 2 != 0)
{
return false;
}
num = num / 2;
}
return true;
}
That function checks whether or not an integer is even (i.e divisible by 2), not whether or not the integer is a power of 2.
To check if a number is a power of 2, you should look at its bits. ints that are a power of 2 will have one bit set to 1 and all others set to 0.
boolean isPowerOf2(int i) {
return Integer.bitCount(i) == 1;
}
The % is the modulo operator, meaning the remainder after division. your method checks if the int n is divisible by 2

Java program taking forever to run with large numbers

I am writing a Java program that calculates the largest prime factor of a large number. But I have an issue with the program's complexity, I don't know what has caused the program to run forever for large numbers, it works fine with small numbers.
I have proceeded as follow :
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
public class Largest_prime_factor {
public static void main(String[] args)
{
//ArrayList primesArray = new ArrayList();
ArrayList factorArray = new ArrayList();
long largest = 1;
long number = 600851475143L ;
long i, j, k;
//the array list factorArray will have all factors of number
for (i = 2; i < number; i++)
{
if( number % i == 0)
{
factorArray.add(i);
}
}
Here, the Array List will have all the factors of the number.
So I'll need to get only the prime ones, for that, I used a method that checks if a number is prime or not, if it's not a prime number, I remove it from the list using the following method :
java.util.ArrayList.remove()
So the next part of the code is as follow :
for (i = 2; i < number; i++)
{
if (!isPrime(i))
{
factorArray.remove(i);
System.out.println(factorArray);
}
}
System.out.println(Collections.max(factorArray));
}
The last line prints the largest number of factorArray, which is what I am looking for.
public static boolean isPrime(long n)
{
if(n > 2 && (n & 1) == 0)
return false;
for(int i = 3; i * i <= n; i += 2)
if (n % i == 0)
return false;
return true;
}
}
The function above is what I used to determine if the number is a prime or not before removing it from the list.
This program works perfectly for small numbers, but it takes forever to give an output for large numbers, although the last function is pretty fast.
At first, I used to check if a number is prime or not inside of the first loop, but it was even slower.
You are looping over 600851475143 numbers.
long number = 600851475143L ;
for (i = 2; i < number; i++)
Even if we assume that each iteration takes very very small time (as small as 1 microsecond), it'll still take days before the loop finishes.
You need to optimise your prime-finding logic in order for this program to run faster.
One way to reduce the iterations to reasonable number is to loop until square root of number.
for (i = 2; i < Math.sqrt(number); i++)
or
for (i = 2; i*i < number; i++)
The calculation of the prime factors of 600851475143L should take less than a milli-second (with a not totally inefficient algorithm). The main parts your code is currently missing:
The border should be sqrt(number) and not number.
The current value should be checked in a while-loop (to prevent that non-prime-factors are added to the list, reduces range to check).
The max. value should be decreased (as well as the border) to number/factor after finding a factor.
Further improvements are possible, e.g. to iterate only over non-even numbers (or only iterate over numbers that are neither a multiple of 2 and 3) etc.
An example implementation for the same question on codereview (link):
public static long largestPrimeFactor(
final long input) {
////
if (input < 2)
throw new IllegalArgumentException();
long n = input;
long last = 0;
for (; (n & 1) == 0; n >>= 1)
last = 2;
for (; n % 3 == 0; n /= 3)
last = 3;
for (long v = 5, add = 2, border = (long) Math.sqrt(n); v <= border; v += add, add ^= 6)
while (n % v == 0)
border = (long) Math.sqrt(n /= last = v);
return n == 1 ? last : n;
}
for (i = 2; i < number; i++)
{
if( number % i == 0)
{
factorArray.add(i);
}
}
For an large input size, you will be visiting up to the value of the number. Same for the loop of removing factors.
long number = 600851475143L ;
this is a huge number, and you're looping through this twice. Try putting in a count for every 10,000 or 100,000 (if i%10000 print(i)) and you'll get an idea of how fast it's moving.
One of the possible solutions is to only test if the the prime numbers smaller than the large number divide it.
So I checked
for (i=2; i < number; i++)
{
if(isPrime(i))
{
if( number % i == 0)
{
factorArray.add(i);
}
}
}
So here I'll only be dividing by prime numbers instead of dividing by all numbers smaller than 600851475143.
But this is still not fast, a complete modification of the algorithm is necessary to obtain an optimal one.
#Balkrishna Rawool suggestion is the right way to go. For that I would suggest to change the iteration like this: for (i = 3; i < Math.sqrt(number); i+=2) and handle the 2 manually. That will decrease your looping because none of the even numbers except 2 are prime.

Categories