Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 7 years ago.
Improve this question
This is my task:
Use recursion to find 13th member of an array where every member is multiplication of last two members minus the second member. First two members are 3 and 3.
This is what I came up with:
public class Zadatak2 {
int array[] = {3,3,0,0,0,0,0,0,0,0,0,0,0};
public Zadatak2(){
find13(2);
}
public int find13(int index){
if (index == 13){
System.out.println("Member 13: " + array[index-1]);
return 0;
}
else{
array[index] = array[index-1] * array[index-2] - array[1];
System.out.println(index + " : " + array[index]);
return find13(index + 1);
}
}
}
Console output where I see index in array : value :
2 : 6
3 : 15
4 : 87
5 : 1302
6 : 113271
7 : 147478839
8 : 1947758222
9 : 465247871
10 : 818773103
11 : -459621106
12 : 383828239
Member 13: 383828239
But I am pretty sure I made a mistake or there is a better solution. Any help is appreciated.
As Kevin W. said in a comment, in the future, "if you want someone to review your code... use the codereview stackexchange". However, here are a few suggestions.
Firstly, as something to keep in mind, the problem you have is a lot like calculating the fibonacci sequence, and there are probably plenty of examples of using recursion to calculate members of that sequence.
Secondly, the way you built your recursive function makes it limited to being used only to find the 13th number in the sequence. You begin at the start of the sequence and work your way up to the 13th number, and what you are doing is basically an iterative solution to the problem with minor tweaks to make it work via recursion.
A better approach would be to generalize your function so that you can pass the sequence member number as a parameter, and the function will calculate it via recursion. The way to do this is to start at your target member number and through recursion, get the members required to make that member. This allows the function to be used to calculate any number in the sequence, not just the 13th number. It also has the added benefit that your code can be both shrunk and do more.
This is the code:
// index is the member number; it is 1 based e.g. index of 1 gives the first number in the sequence
int find(int index)
{
if (index == 1 || index == 2)
return 3;
return (find(index - 1) * find(index - 2)) - find(2);
}
When solving problems with recursion, the method generally used is to start with the problem you want to solve and break it down (as shown in my code above), rather than start with subproblems to find the larger problem (as your code shows).
When applying recursion to a sequence, write out the mathematical definition of the sequence first, and that is what must be returned from the recursive function. For example, in your problem, the definition is
a[n] = (a[n-1] * a[n-2]) - a[2]
Now take a look at the solution I wrote. What I am returning is precisely this sequence definition, just in terms of the recursive function. The base case at the beginning of the function is simply the initial member(s) required to calculate the rest of the sequence. I encourage you to work the algorithm through on paper and play with it to see exactly what is happening.
As a final note, this algorithm is horrendous in terms of run time. There are three recursive calls per call to find(), which means that finding the 13th member is on the order of 3^13, which is exponential. Exponential algorithms are terrible algorithms, and should always be avoided.
If the recursion is examined closely you can see that in order to calculate a[n], the code calculates a[n-1] and a[n-2]. But in order to calculate a[n-1], a[n-2] and a[n-3] are both calculated, meaning that a[n-2] is calculated TWICE. This observation is very important because we only went down one level of recursion. There are a total of about 3^13 member calculations occurring when all there should be are 13 (for the 13 members). All of that time performing the same calculations millions of times is a horrendous waste and is what makes exponential algorithms so awful.
So what if we stored each of the members that the function calculates? This technique is called dynamic programming, and is where answers to subproblems are stored on the way to solving a larger problem so calculations are not performed multiple times. The solution implementing dynamic programming is:
// a variable that persists across function calls such as an instance field
int[] array = new int[20]; // just giving some extra memory in case you want to calculate other members
array[0] = -1; //invalid member of the sequence since it is 1-based
array[1] = 3;
array[2] = 3;
//set the rest of the numbers to values letting you know they have not been set/found/calculated yet
for (int i = 3; i < 20; i++)
{
array[i] = -1;
}
// index is the member number; it is 1 based e.g. index of 1 gives the first number in the sequence
int find(int index)
{
if (array[index] != -1) //if already calculated that member, just return it
return array[index];
//store the answer
array[index] = (find(index - 1) * find(index - 2)) - find(2);
return array[index];
}
With this code, you can call find() for any number and it will calculate it for you, instead of just the 13th number.
Lastly, and most importantly, as Kevin W. pointed out in a comment, the presence of a negative number as a member means that you are getting numbers too big for ints. Luka Milosevic says that the 13th member is actually a number x10^90, which is too big for a long even. Doubles can work as long as you don't need more than 20 or so digits of precision, but because of at least 90 digits in the answer, doubles are not accurate enough. Fortunately Java has a class called BigInteger, which can store as large of numbers as you want, regardless of size. In order to obtain your answer, you probably have to use them, unless you want to do the math manually. The documentation for BigInteger is here.
As this is a task you were assigned to do I would like to give you a hint only. In our case a recursive function would call itself with a lower argument and finally end with some defined values.
In our case the lower values f(1) and f(2) are already defined as 3.
So f(n) = f(n-1) * f(n-2) - f(2) where f(1) = 3 and f(2) = 3.
(If I understood the task right)
Now your job is to do the coding and find f(13).
Does it help?
Related
I have created the following simple algorithm in Java that computes the Pascal triangle in a recursive way, in the form of a 2D list of ints:
public class PascalTriangleRec{
private final int[][] points;
public PascalTriangleRec(int size){
points = new int[size][];
for (int i =0;i<size;i++){
int[] row = new int[i+1];
for (int j = 0;j<=i;j++){
row[j]=getValueAtPoint(i,j);
}
points[i]=row;
}
}
public static int getValueAtPoint(int row, int col){
if (col == 0 || col == row) return 1;
else return getValueAtPoint(row-1,col-1) + getValueAtPoint(row-1,col);
}
}
I need to know the time complexity of this algorithm. I found another question on StackOverflow that gives the time complexity of the getValueAtPoint function as O(2^n/sqrt(n)). I figured that since this function is embedded in two nested for loops, the time complexity of the entire Pascal triangle is O(sqrt(n^3)*2^n). I am pretty sure this reasoning is correct.
On the other hand I devised a completely different way to think about this problem, which goes as follows:
There is a certain property of Pascal triangles called Pascal's Corollary 8. This property states that the sum of all the coëfficients on a given row r is equal to 2^r, with r starting at 0.
One can also note that the getValueAtPoint function from my code sample will keep recursively calling itself until it returns 1 at some point. This means that all the coëfficients in the Pascal triangle are formed by adding 1 as many times as the value of that coëfficient.
Since adding 1s takes a constant time, one can say that the time needed to compute a given row in the triangle is equal to some constant time multiplied by the combined value of all the coëfficients in that row. This means that the time complexity of a given row r in the triangle must be 2^r.
The time needed to compute the entire triangle is equal to the sum of the time needed to calculate all the rows in the triangle. This results in a geometric series, which computes the sum of all 2^r for r going from 0 to n-1.
Using the summation property of the geometric series, this series can be rewritten in the following form.
This means that the time complexity of the algorithm according to this last derivation is O(2^n).
These two approaches yield different results, even though they both seem logical and correct to me. My question is in the first place if both these approaches are correct, and if both can be seen as correct at the same time? As I view it both of them are correct, but the second one is more accurate since for the first one the worst-case scenario is taken for the getValueAtPoint function, and applied to all coëfficients, which is clearly not the case in reality. Does this mean that the first one becomes incorrect, even though the logic behind it is correct, just because a better approach exists?
The simple answer is "too many variables". First of all, your analysis is exactly correct: the complexity depends on the sum of all the values computed. The same logic underlies the answer that got you O(2^n/sqrt(n)).
There are two problems:
Little problem: Stirling's approximation is just that: some terms are elided. I think they fall out when you combine all the loops, but I'd have to work through the nasty details to be sure.
Big problem: the values of n you combine are not the same n. That last n value you incorporated is i running from 0 to size; each value of i becomes n for an initial call to getValueAtPoint.
Try doing the sum from 0 to n on your previous complexity, and see what you get?
.
Basically what I am doing it taking a string of integers (e.g. "1234"), and I am able to insert a + or - anywhere in this string, as much or little as I want. For example, I can do "1 + 2 + 3 + 4", "12 + 34", "123 - 4", etc. It is required to use all integers of the string, I cannot exclude any.
What I am trying to do is take another array of integers, and find if it was possible to get that number using the permutations mentioned in the first paragraph. I am somewhat lost on where to start looking for this. I could possibly create a recursive loop function to create every possible combination of the string and see if each result matches but this seems like it will be terribly slow. Another thought was to index them into an array - that way I could simply look up the answers after calculating them once.
Anyone have any suggestions?
I could possibly create a recursive loop function to create every possible combination of the string and see if each result matches but this seems like it will be terribly slow.
Doing an exhaustive search is your only option here. Fortunately, the timing isn't going to be too bad even for moderately long strings of up to 7..10 characters, because you do not need to "redo" additions and subtractions of a prior string when you process the "tail".
An outline of a possible implementation could be as follows:
Put all desired results from your array of integers in a hash set
Make a recursive method that takes the result so far, the string, and the position of the next "cut"
When the next "cut" is at the end of the string, check the result so far against the hash set from step 1
Otherwise, try these three possibilities in a loop on k
Use a k-digit number from the "cut" as a positive number, and make a recursive invocation with the "cut" moved by k digits. This is equivalent to inserting a + at the cut
Use a k-digit number from the "cut" as a negative number, and make a recursive invocation with the "cut" moved by k digits. This is equivalent to inserting a - at the cut
I'll give start help, with the approach for such a solution.
formal problem statement;
data model;
algorithm;
heuristics, cleverness.
For N digits there are some 3^N possibilities.
The solution must model the running data as:
the digits, as int[]
the sum
index from which to advance, prior digits were done.
number partalready tried, plus sign. Sign must come separate (as -1, +1) as the coming digit may be 0;
(What I leave out is the collecting of the entire result.)
The brute force solution then could be:
boolean solve(int[] digits, int sum) {
return solve(digits, sum, 1, 0, 0);
}
boolean solve(int[] digits, int sum, int signum, int part, int index) {
if (index >= digits.length) {
return signum * part == sum;
}
// Before the digit at index do either nothing, +, or -
return solve(digits, sum, signum, part * 10 + digits[index], index + 1)
|| solve(digits, sum - signum * part, 1, 0, index + 1)
|| solve(digits, sum - signum * part, -1, 0, index + 1);
}
Mind you could also split the digits in half and try to insert (nothing, +, -) there.
There are pruning opportunities, to diminish the number of tries. First the above can be done in a loop, the alternatives need not all to be tried. The order of evaluation might favor more likely candidates:
if digit 0 ...
if part > sum first - then +
...
Unfortunately +/- make a number theoretical approach AFAIK for me illusory.
#dasblinkenlight mentions even better data models, allowing to not
repeat evaluation in the alternatives. That would be even more
interesting. But might fail miserably due to time constraints. And I
wanted to come with something concrete. Without providing an entirely
ready made solution.
It is reasonable to take a brute force approach if you can rely on the input string not to be too long. If it contains n digits then you can construct 3n-1 formulae from it (between each pair of digits you can insert '+', '-', or nothing, for n-1 internal positions). For a 12-digit input string that's roughly 270000 formulae, which should be computable quite quickly. Of course, you would build and compute each one once, and compare the result to all the alternatives. Don't redo the computation for each array element.
It may be that there's a dynamic programming approach to this, but I'm not immediately seeing it, at least not one that would be substantially better than brute force.
Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 9 years ago.
Improve this question
While it is common knowledge that recursion is "a method that calls itself", I am inclined to wonder what is actually happening. Take the classic factorial example:
public static int fact(int n) {
if(n == 0)
return 1;
else
return n * fact(n - 1);
}
fact(5);
I understand that it goes a little something like this: (the equals sign indicates what's happening when the function is called for that value)
http://postimg.org/image/4yjalakcb/
Why does recursion function like this? Which aspect of a computer makes it work through itself backwards like so? What's going on behind the scenes?
As a student I feel like the recursion we are taught is shallow and general. I want the excellent community here to help me understand it on the level of the machine itself. Thank you!
Here's is the brief overview of what happens, whenever you call a method:
A frame for that method is allocated from the stack.
The frame contains, all the local variables, parameters, return values of the method.
That frame is placed over the top of the frame of current method, that invokes this method.
When the method returns, the frame related to that method is popped off the stack, and the calling method comes back into action, taking the return value if any from the previous method.
You can learn more about frames here - JVM Spec - Frames.
In case of recursion, same thing happens. Just for the time being, forget that you are dealing with recursion, and take each recursion call as a call to different method. So, in factorial case, the stack would grow like this:
fact(5)
5 * fact(4)
4 * fact(3)
3 * fact(2)
2 * fact(1)
1 * fact(0) // Base case reached. Stack starts unwinding.
2 * 1 * 1
3 * 2 * 1 * 1
4 * 3 * 2 * 1 * 1
5 * 4 * 3 * 2 * 1 * 1 == Final result
If you trace the function calls, you will see how it works.
E.g.
fact(3) will return 3 * fact(2). So java will call fact(2).
fact(2) will return 2 * fact(1). So java will call fact(1).
fact(1) will return 1 * fact(0). So java will call fact(0).
fact(0) will return 1.
Then fact(1) will return 1 * 1 = 1.
Then fact(2) will return 2 * 1 = 2.
Then fact(3) will return 3 * 2 = 6.
Java calls the recursive method like it would any other method.
You might have heard of something called "The Stack" It's what's used to store method states.
I believe it also stores the calling line, so that the function can go back to it's caller
So suppose you have your call to a recursive function
- int $input = 5
- stack.Push L
- GOTO FOO
- Label L
your recursive function(without a base case) might look similar to the following
- Label FOO
- int in = $input
- input = in - 1
- stack.Push in
- stack.Push L2
- goto FOO
- Label L2
- in = stack.Pop in
- output *= in
- goto stack.POP
Maybe the following will help you understand. The computer does not care whether he calls the same function it is just computing. There is nothing magical about recursion once you understand what it is and why it works with many things, like lists, natural numbers, etc., who are themselves recursive by structure.
Definition: The factorial of 0 is 1
Definition: The factorial of a number n greater than 0 is the product of that number and the factorial of its predecessor.
Hence
5! = 5*4! = 5*4*3! = 5*4*3*2! = 5*4*3*2*1! = 5*4*3*2*1*0! = 5*4*3*2*1*1 = 120
So, if you ever heard of proof by induction, it goes like this:
We proof some property for a base case.
We proof that, if the property is true for n, then it will be true for the successor of n.
We conclude that this is the proof that the property holds for the base case and all successive cases.
Example: Proof by induction that the square of an even number is a multiple of 4!
The square of 0 is 0, and it is a multiple of 4.
Let n be an even number, whose square n² is a multiple of 4. Then (2+n)*(2+n) = 4+2n+2n+n². This is a muliple of 4, because n² was by our assumption, 4 is one and 2n+2n = 4n is also a multiple of 4 and the sum of multiples of 4 is a multiple of 4 by distributive law: 4a + 4b = 4(a+b)
Q.E.D. The property holds for 0 (our base case), and for (2+n), provided it holds for n. Hence it holds for 2, 4, 6, 8 .... and all other even numbers.
(An easier proof woud be to observe that (2a)² = 4*a*a, which is a multiple of 4.)
Writing a recursive program is very similar to doing a proof by induction:
We write the computation for the base case.
For the non base case, we know how we can compute the result (for example, we know that n! = n * (n-1)!, so we write it just down, since the function we need is the one we are just writing!
We can conclude that our program will compute the correct value for the base case and any successor of the base case. If 678! nevertheless does not compute the correct answer, then it has to do with the fact that we used a data type like int that is not suited very well for big numbers (or, to put it differently, computes everything moulo 2^32) and in addition, with a software that insists on interpreting half of the available numbers as negative ones.
The reason this works has nothing to do with the computer hardware or the programming language: it is, as I said before, a consequence of the recursive structure of the items (lists, trees, sets, natural numbers) at hand.
A common error newcomers make is to ignore the base case and get lost in complexity. I always suggest to start with the base case, once you have this you can assume that the function exists, and can just use it in the more complex cases.
I am a starting programmer and need to program an application in java which ask for a number and then prints the first n number of Lucas Numbers. for example when 7 is put in, it outputs: 2, 1, 3, 4, 7, 11, 18.
Just to be clear, lucas numbers are defined as:
2 if n = 0
1 if n = 1
L(n-1) + L(n-2) if n>1
I am really not sure howver how to programm this in java. Because I cannot translate this into java code. I've been thinking for a while now but still can't figure it out. Also when i would have the code for calculating the Nth lucas number, i would now how to output all the first Lucan Numbers up until the Nth number. Can some of you guys help me gte on the right way or give me tips? Thanks a lot!
The definition you have for the Lucas number is recursive, i.e., to calculate the Nth lucas number, you already need to know the N-1th and the N-2nd.
A naive way to do this would be
public int lucas(int N) {
if( N == 0 ) return 2;
if( N == 1 ) return 1;
return lucas(N-1) + lucas(N-2);
}
However, you only have to print the numbers, don't you? Then it's quite easy, actually.
int L2 = 2;
int L1 = 1;
for( int i = 2; i <= N; i++ ) {
int L = L1 + L2;
print(L); //or whatever output function you have
L2 = L1;
L1 = L;
}
The idea is to keep the last two numbers, which you need to compute the next two numbers, always at hand.
PS: These Lucas numbers are just like the Fibonacci numbers with different starting values, so any algorithm for the Fibonacci numbers will do. If you're really good at math, you can even try to find a closed formula for the Lucas numbers, but it's definitely beyond high school math (search tag would be "linear difference equation with constant coefficients").
If you're not sure how to do the whole thing, break the problem down into smaller pieces until you can start.
Try taking each case, one at a time. Implement the '2 if n=0' case, test that it works. The calculation is trivial, but you will have to write the code that calls your implementation, too. The trivial case helps you check that the code around it works properly.
Then implement the next one, check it still works, implement the final one. It'll become clearer as you go.
Just in case someone is looking for a formula and happens on this page then maybe this will help. In Windows take the following line and replace each N with the desired integer, copy the modified line and paste it into Windows' calculator:
(5#/2+0.5)yN+(-(5#/2+0.5))y(-N)=
For example if you want to find the Lucus number for 7 you would paste this line into Windows' calculator:
(5#/2+0.5)y7+(-(5#/2+0.5))y(-7)=
And the result will be 29.
You are given a list of n numbers L=<a_1, a_2,...a_n>. Each of them is
either 0 or of the form +/- 2k, 0 <= k <= 30. Describe and implement an
algorithm that returns the largest product of a CONTINUOUS SUBLIST
p=a_i*a_i+1*...*a_j, 1 <= i <= j <= n.
For example, for the input <8 0 -4 -2 0 1> it should return 8 (either 8
or (-4)*(-2)).
You can use any standard programming language and can assume that
the list is given in any standard data structure, e.g. int[],
vector<int>, List<Integer>, etc.
What is the computational complexity of your algorithm?
In my first answer I addressed the OP's problem in "multiplying two big big numbers". As it turns out, this wish is only a small part of a much bigger problem which I'm going to address now:
"I still haven't arrived at the final skeleton of my algorithm I wonder if you could help me with this."
(See the question for the problem description)
All I'm going to do is explain the approach Amnon proposed in little more detail, so all the credit should go to him.
You have to find the largest product of a continuous sublist from a list of integers which are powers of 2. The idea is to:
Compute the product of every continuous sublist.
Return the biggest of all these products.
You can represent a sublist by its start and end index. For start=0 there are n-1 possible values for end, namely 0..n-1. This generates all sublists that start at index 0. In the next iteration, You increment start by 1 and repeat the process (this time, there are n-2 possible values for end). This way You generate all possible sublists.
Now, for each of these sublists, You have to compute the product of its elements - that is come up with a method computeProduct(List wholeList, int startIndex, int endIndex). You can either use the built in BigInteger class (which should be able to handle the input provided by Your assignment) to save You from further trouble or try to implement a more efficient way of multiplication as described by others. (I would start with the simpler approach since it's easier to see if Your algorithm works correctly and first then try to optimize it.)
Now that You're able to iterate over all sublists and compute the product of their elements, determining the sublist with the maximum product should be the easiest part.
If it's still to hard for You to make the connections between two steps, let us know - but please also provide us with a draft of Your code as You work on the problem so that we don't end up incrementally constructing the solution and You copy&pasting it.
edit: Algorithm skeleton
public BigInteger listingSublist(BigInteger[] biArray)
{
int start = 0;
int end = biArray.length-1;
BigInteger maximum;
for (int i = start; i <= end; i++)
{
for (int j = i; j <= end; j++)
{
//insert logic to determine the maximum product.
computeProduct(biArray, i, j);
}
}
return maximum;
}
public BigInteger computeProduct(BigInteger[] wholeList, int startIndex,
int endIndex)
{
//insert logic here to return
//wholeList[startIndex].multiply(wholeList[startIndex+1]).mul...(
// wholeList[endIndex]);
}
Since k <= 30, any integer i = 2k will fit into a Java int. However the product of such two integers might not necessarily fit into a Java int since 2k * 2k = 22*k <= 260 which fill into a Java long. This should answer Your question regarding the "(multiplication of) two numbers...".
In case that You might want to multiply more than two numbers, which is implied by Your assignment saying "...largest product of a CONTINUOUS SUBLIST..." (a sublist's length could be > 2), have a look at Java's BigInteger class.
Actually, the most efficient way of multiplication is doing addition instead. In this special case all you have is numbers that are powers of two, and you can get the product of a sublist by simply adding the expontents together (and counting the negative numbers in your product, and making it a negative number in case of odd negatives).
Of course, to store the result you may need the BigInteger, if you run out of bits. Or depending on how the output should look like, just say (+/-)2^N, where N is the sum of the exponents.
Parsing the input could be a matter of switch-case, since you only have 30 numbers to take care of. Plus the negatives.
That's the boring part. The interesting part is how you get the sublist that produces the largest number. You can take the dumb approach, by checking every single variation, but that would be an O(N^2) algorithm in the worst case (IIRC). Which is really not very good for longer inputs.
What can you do? I'd probably start from the largest non-negative number in the list as a sublist, and grow the sublist to get as many non-negative numbers in each direction as I can. Then, having all the positives in reach, proceed with pairs of negatives on both sides, eg. only grow if you can grow on both sides of the list. If you cannot grow in both directions, try one direction with two (four, six, etc. so even) consecutive negative numbers. If you cannot grow even in this way, stop.
Well, I don't know if this alogrithm even works, but if it (or something similar) does, its an O(N) algorithm, which means great performance. Lets try it out! :-)
Hmmm.. since they're all powers of 2, you can just add the exponent instead of multiplying the numbers (equivalent to taking the logarithm of the product). For example, 2^3 * 2^7 is 2^(7+3)=2^10.
I'll leave handling the sign as an exercise to the reader.
Regarding the sublist problem, there are less than n^2 pairs of (begin,end) indices. You can check them all, or try a dynamic programming solution.
EDIT: I adjusted the algorithm outline to match the actual pseudo code and put the complexity analysis directly into the answer:
Outline of algorithm
Go seqentially over the sequence and store value and first/last index of the product (positive) since the last 0. Do the same for another product (negative) which only consists of the numbers since the first sign change of the sequence. If you hit a negative sequence element swap the two products (positive and negative) along with the associagted starting indices. Whenever the positive product hits a new maximum store it and the associated start and end indices. After going over the whole sequence the result is stored in the maximum variables.
To avoid overflow calculate in binary logarithms and an additional sign.
Pseudo code
maxProduct = 0
maxProductStartIndex = -1
maxProductEndIndex = -1
sequence.push_front( 0 ) // reuses variable intitialization of the case n == 0
for every index of sequence
n = sequence[index]
if n == 0
posProduct = 0
negProduct = 0
posProductStartIndex = index+1
negProductStartIndex = -1
else
if n < 0
swap( posProduct, negProduct )
swap( posProductStartIndex, negProductStartIndex )
if -1 == posProductStartIndex // start second sequence on sign change
posProductStartIndex = index
end if
n = -n;
end if
logN = log2(n) // as indicated all arithmetic is done on the logarithms
posProduct += logN
if -1 < negProductStartIndex // start the second product as soon as the sign changes first
negProduct += logN
end if
if maxProduct < posProduct // update current best solution
maxProduct = posProduct
maxProductStartIndex = posProductStartIndex
maxProductEndIndex = index
end if
end if
end for
// output solution
print "The maximum product is " 2^maxProduct "."
print "It is reached by multiplying the numbers from sequence index "
print maxProductStartIndex " to sequence index " maxProductEndIndex
Complexity
The algorithm uses a single loop over the sequence so its O(n) times the complexity of the loop body. The most complicated operation of the body is log2. Ergo its O(n) times the complexity of log2. The log2 of a number of bounded size is O(1) so the resulting complexity is O(n) aka linear.
I'd like to combine Amnon's observation about multiplying powers of 2 with one of mine concerning sublists.
Lists are terminated hard by 0's. We can break the problem down into finding the biggest product in each sub-list, and then the maximum of that. (Others have mentioned this).
This is my 3rd revision of this writeup. But 3's the charm...
Approach
Given a list of non-0 numbers, (this is what took a lot of thinking) there are 3 sub-cases:
The list contains an even number of negative numbers (possibly 0). This is the trivial case, the optimum result is the product of all numbers, guaranteed to be positive.
The list contains an odd number of negative numbers, so the product of all numbers would be negative. To change the sign, it becomes necessary to sacrifice a subsequence containing a negative number. Two sub-cases:
a. sacrifice numbers from the left up to and including the leftmost negative; or
b. sacrifice numbers from the right up to and including the rightmost negative.
In either case, return the product of the remaining numbers. Having sacrificed exactly one negative number, the result is certain to be positive. Pick the winner of (a) and (b).
Implementation
The input needs to be split into subsequences delimited by 0. The list can be processed in place if a driver method is built to loop through it and pick out the beginnings and ends of non-0 sequences.
Doing the math in longs would only double the possible range. Converting to log2 makes arithmetic with large products easier. It prevents program failure on large sequences of large numbers. It would alternatively be possible to do all math in Bignums, but that would probably perform poorly.
Finally, the end result, still a log2 number, needs to be converted into printable form. Bignum comes in handy there. There's new BigInteger("2").pow(log); which will raise 2 to the power of log.
Complexity
This algorithm works sequentially through the sub-lists, only processing each one once. Within each sub-list, there's the annoying work of converting the input to log2 and the result back, but the effort is linear in the size of the list. In the worst case, the sum of much of the list is computed twice, but that's also linear complexity.
See this code. Here I implement exact factorial of a huge large number. I am just using integer array to make big numbers. Download the code from Planet Source Code.