Nested loops into mathematical model to count number of operations - java

I'm reading the book 'Algorithms - Fourth edition' by Sedgewick and Wayne and I must admit that some parts in the "Analysis of Algorithms" chapter are confusing me! This is probably caused by my lack of mathematical knowledge... Anyways!
Somewhere in the book, there is an example of a program where the inner loop is said to be executed exactly N(N-1)(N-2)/6 times. Here it is:
public static int count(int[] a) {
int count = 0;
for (int i = 0; i < a.length; i++) {
for (int j = i + 1; i < a.length; j++) {
for (int k = j + 1; k < a.length; k++) {
if (a[i] + a[j] + a[k] == 0) {
count++;
}
}
}
}
return count;
}
I am familiar with the big O notation but when it comes to counting the exact number of opreations in loops, I'm lost. I understand the N(N-1)(N-2) part but why do we have to divide by 6? What is the logic behind it?
Any help would be greatly appreciated!

If you can understand the N(N-1)(N-2) part, here's a thought:
Take a combination of 3 numbers, i, j, k, whatever 3 that fall into the range 0 <= i,j,k < N and different one from another (this is also taken care in the code and that's why the formula is N(N-1)(N-2) and not N^3.
Now, lets say the numbers are 13, 17, 42. It doesn't really matters whoch numbers they are. In how many ways can you put them in line?
13-17-42
13-42-17
17-13-42
17-42-13
42-13-17
42-17-13
Six!
How many of these ways can appear in the code? Only one! (that's taken care in the initializaton of j and k).
So, the total number of N(N-1)(N-2) should be divided by 6.

You can use Sigma notation and discover how to come up with the formula mentioned in your book:

As we know...
1+2+3+4...+N => N(N-1)/2
Similarly, the innermost loop works something like
1.n+2(N-1)+3(N-2)+...N.1 => N(N-1)(N-2)/6
Here is a proof for this.

The 6 is derived from 3! (three factorial derived from three loops).
Consider the outer-most loop on, say, an array of 5 elements. You're counting N=5 for that part of the equation, but you'll only reach your inner loop for values i=0, i=1 or i=2. Similarly, you're representing the next loop by (N-1), but you'll only reach your inner loop for values j=1, j=2, or j=3 and not the four values implied by (N-1).
Dividing by 6 (for three loops) compensates for the values that would run out of values in the array before reaching the inner-most loop.

Related

Basic for loop explanation needed

for (int i =0; i<5;i++) {
for(int j=0; j<5; j++) {
System.out.print(i*j%5);
}
System.out.println();
}
I understand the output for this Java program will look like this:
00000
01234
02413
03142
04321
but what I don't understand is how the function (i*j%5) can return any number at all since j will always be less than five, so shouldn't all j%5 = 0 therefore making i * 0=0 ?
update:
ok so now i know
3%5 = 3
2%5= 2
4%5 = 4
according to java. I originally assumed that since 5 does not go into 3 at all (3%5 for example) then the modulo would be 0. But I was wrong then, java must read it as just the original number? also thank you to everyone who responded so quickly <3
You seem to confuse the modulo operator with the division operator: j%5 is the same as j if j is from 0 to 4.
But, since multiplication operator and modulo operator have the same precedence, the expression i*j%5 grouped from left to right, i.e. it is evaluated as (i * j) % 5 and not, as you seem to assume, as i * (j % 5).
as per the order of precedence, the multiplication will take place before the division, so that's why you will not get zero.

From 1 to N, print all the pairs of number that has sum larger than 50

I'm new to Java and I have an exercise like this. From 1 to N, print all the pairs of number that has sum larger than 50.
For example if N = 60, then the result will be something like (1,50);(1,51);...;(1,60);...;(2,49);(2,50);...;(2,60);....;(58,59);(58,60);(59,60). I'm thinking about some nested while loop with a run from 1 to N and b run from N to 1, then the condition is a+b>50. I think if a+b<50 it will be easier since I can set b = 50-a or something like that. Still I'm quite of confusing right now.
Take a look:
public static void main(String[] args) {
int N = 60;
for (int i=1; i<=N; i++) { //1st loop
for (int j=i+1; j<=N; j++) { //2nd loop
if (i+j > 50) {
System.out.printf("(%d, %d);", i, j);
}
}
System.out.println();
}
}
Explaination:
The first loop (index i) is going over all of the numbers in allowed range N. The second loop (index j) is going over all of the other numbers in said range, that are bigger than i. We're taking only bigger numbers so we don't go over the same pair twice. Finally only the pairs that are qualified by your condition (bigger than 50) are printed.
On a side note: I implore that you work on your "googling" skills. Knowing how to search info online is an essential skill. By a search and a quick read, you could find links that while not directly solve your problem, does get you a step in the right direction.
Edit: Worth noting that I'm not sure this code prints the pairs the exact way you want it to. It also doesn't consider pairs of the same number (e.g. (26, 26)). It wasn't clear to me if you're intreseted in these cases or not by your question.

What is the time complexity of an iteration through all possible sequences of an array

An algorithm that goes through all possible sequences of indexes inside an array.
Time complexity of a single loop and is linear and two nested loops is quadratic O(n^2). But what if another loop is nested and goes through all indexes separated between these two indexes? Does the time complexity rise to cubic O(n^3)? When N becomes very large it doesn't seem that there are enough iterations to consider the complexity cubic yet it seems to big to be quadratic O(n^2)
Here is the algorithm considering N = array length
for(int i=0; i < N; i++)
{
for(int j=i; j < N; j++)
{
for(int start=i; start <= j; start++)
{
//statement
}
}
}
Here is a simple visual of the iterations when N=7(which goes on until i=7):
And so on..
Should we consider the time complexity here quadratic, cubic or as a different size complexity?
For the basic
for (int i = 0; i < N; i++) {
for (int j = i; j < N; j++) {
// something
}
}
we execute something n * (n+1) / 2 times => O(n^2). As to why: it is the simplified form of
sum (sum 1 from y=x to n) from x=1 to n.
For your new case we have a similar formula:
sum (sum (sum 1 from z=x to y) from y=x to n) from x=1 to n. The result is n * (n + 1) * (n + 2) / 6 => O(n^3) => the time complexity is cubic.
The 1 in both formulas is where you enter the cost of something. This is in particular where you extend the formula further.
Note that all the indices may be off by one, I did not pay particular attention to < vs <=, etc.
Short answer, O(choose(N+k, N)) which is the same as O(choose(N+k, k)).
Here is the long answer for how to get there.
You have the basic question version correct. With k nested loops, your complexity is going to be O(N^k) as N goes to infinity. However as k and N both vary, the behavior is more complex.
Let's consider the opposite extreme. Suppose that N is fixed, and k varies.
If N is 0, your time is constant because the outermost loop fails on the first iteration.. If N = 1 then your time is O(k) because you go through all of the levels of nesting with only one choice and only have one choice every time. If N = 2 then something more interesting happens, you go through the nesting over and over again and it takes time O(k^N). And in general, with fixed N the time is O(k^N) where one factor of k is due to the time taken to traverse the nesting, and O(k^(N-1)) being taken by where your sequence advances. This is an unexpected symmetry!
Now what happens if k and N are both big? What is the time complexity of that? Well here is something to give you intuition.
Can we describe all of the times that we arrive at the innermost loop? Yes!
Consider k+N-1 slots With k of them being "entered one more loop" and N-1 of them being "we advanced the index by 1". I assert the following:
These correspond 1-1 to the sequence of decisions by which we reached the innermost loop. As can be seen by looking at which indexes are bigger than others, and by how much.
The "entered one more loop" entries at the end is work needed to get to the innermost loop for this iteration that did not lead to any other loop iterations.
If 1 < N we actually need one more that that in unique work to get to the end.
Now this looks like a mess, but there is a trick that simplifies it quite unexpectedly.
The trick is this. Suppose that we took one of those patterns and inserted one extra "we advanced the index by 1" somewhere in that final stretch of "entered one more loop" entries at the end. How many ways are there to do that? The answer is that we can insert that last entry in between any two spots in that last stretch, including beginning and end, and there is one more way to do that than there are entries. In other words, the number of ways to do that matches how much unique work there was getting to this iteration!
And what that means is that the total work is proportional to O(choose(N+k, N)) which is also O(choose(N+k, k)).
It is worth knowing that from the normal approximation to the binomial formula, if N = k then this turns out to be O(2^(N+k)/sqrt(N+k)) which indeed grows faster than polynomial. If you need a more general or precise approximation, you can use Stirling's approximation for the factorials in choose(N+k, N) = (N+k)! / ( N! k! ).

java: Big-Oh order of this do-while code fragment? Plus a tight upper bound

I know this is easy but my textbook doesn't talk about Big-Oh order with do-while loops, and neither do any of my other algorithm sources.
This problem states that the following code fragment is parameterized on the variable "n", and that a tight upper bound is also required.
int i=0, j=0;
do {
do {
System.out.println("...looping..."); //growth should be measured in calls to println.
j=j+5;
} while (j < n);
i++;
j = 0;
} while (i < n);
Can anyone help me with this and explain Big-Oh order in terms of do-while loops? Are they just the same as for loops?
A good maxim for working with nested loops and big-O is
"When in doubt, work from the inside out!"
Here's the code you have posted:
int i=0, j=0;
do {
do {
Do something
j=j+5;
} while (j < n);
i++;
j = 0;
} while (i < n);
Let's look at that inner loop. It runs roughly n / 5 times, since j starts at 0 and grows by five at each step. (We also see that j is always reset back to 0 before the loop begins, either outside the loop or at the conclusion of an inner loop). We can therefore replace that inner loop with something that basically says "do Θ(n) operations that we care about," like this:
int i=0;
do {
do Θ(n) operations that we care about;
i++;
} while (i < n);
Now we just need to see how much work this does. Notice that this will loop Θ(n) times, since i counts 0, 1, 2, ..., up to n. The net effect is that this loop is run Θ(n) times, and since we do Θ(n) operations that we care about on each iteration, the net effect is that this does Θ(n2) of the printouts that you're trying to count.

Time complexity for two pieces of code

We've got 2 pieces of code:
int a = 3;
while (a <= n) {
a = a * a;
}
And:
public void foo(int n, int m) {
int i = m;
while (i > 100)
i = i / 3;
for (int k = i ; k >= 0; k--) {
for (int j = 1; j < n; j*=2)
System.out.print(k + "\t" + j);
System.out.println();
}
}
What is the time complexity of them?
I think that the first one is: O(logn), because it's progressing to N with power of 2.
So maybe it's O(log2n) ?
And the second one I believe is: O(nlog2n), because it's progressing with jumps of 2, and also running on the outer loop.
Am I right?
I believe, that first code will run in O(Log(LogN)) time. It's simple to understand in this way
Before first iteration you have 3 in power 1
After first iteration you have 3 in power 2
After second iteration you have 3 in power 4
After third iteration you have 3 in power 8
After fourth iteration you have 3 in power 16
and so on.
In the second code first piece of code will work in O(LogM) time, because you divide i by 3 every time. The second piece of code C times (C equals 100 in your case) will perform O(LogN) operations, because you multiply j by 2 every time, so it runs in O(CLogN), and you have complexity O(LogM + CLogN)
For the first one, it is indeed O(log(log(n))). Thanks to #MarounMaroun for the hint, I could find this:
l(k) = l(k-1)^2
l(0) = 3
Solving this system yields:
l(k) = 3^(2^k)
So, we are looking for such a k that satisfies l(k) = n. So simply solve that:
This means we found:
The second code is seems misleading. It looks like O(nlog(n)), but the outer loop limited to 100. So, if m < 100, then it obviously is O(mlog(n)). Otherwise, it kind of depends on where exactly m is. Consider these two:
m: 305 -> 101 -> 33
m: 300 -> 100
In the first case, the outer loop would run 33 times. Whereas the second case would cause 100 iterations. I'm not sure, but I think you can write this as being O(log(n)).

Categories