Time complexity and Gauss Sum Identity in Java Nested Loops - java

public static int[] intSum(int[] arr2) {
int[] arr1 = new int[arr2.length];
for (int i = 0; i < arr2.length; i++) {
for (int j = 0; j <= i; j++) {
arr1[i] += arr2[j];
}
}
return arr1;
}
Given the method, we were told that the inner loop can be summarized using Gauss's sum identity, and as such, the complexity in polynomial form is
T(n) = c_0 + c_1 * n + c_2 * n + (1 + 2 + ... + (n-1)) * c_3
=> T(n) = c_0 + n * c_1 * n * c_2 * (n(n+1))/2 * c_3
I don't really understand this calculation. I get that c_1 * n is the array initialization which is O(n) in Java, and that c_2 * n would be the outer loop, but how does the sum up from 1 to n-1 work here and how is that related to the inner loop?

Ignoring the syntax errors in your code snippet, the inner loop runs from 0 to i, where i increases each time the for is executed again. This gives the sum 0 + 1 + 2 + ... + n.
Now this sum was calculated by the young Gauss in the following way (for even n, but a similar construction works for odd n):
1 + n = n+1
2 + (n-1) = n+1
3 + (n-2) = n+1
...
n/2 + n/2+1 = n+1
So in total there are n/2 lines with a sum of n+1 each. This gives the result n*(n+1)/2

Related

What is supposed to be the Time Complexity of this loop

What should be the time complexity of this code?
I am thinking (n^2 -n) as the loop will run n(n-1)/2 times.
for (int i = 0; i < n-1; i++) {
for (int j = i + 1; j < n; j++) {
System.out.println(i + "i " + j + " j");
}
}
i: outer loop runs n - 1 times
j: inner loop runs n - (i + 1) times
Analyze it with a small number, say n = 10.
The outer loop will run 9 times.
The inner loop will run as follows
i = 0: 9 times
i = 1: 8 times
i = 2: 7 times
...
i = 8: 1 time
Therefore
n * ( n - 1) = n ^ 2 - n
For very large values of n, n is insignificant when compared to n squared.
See also wikipedia asymtotic analysis.

Time complexity of while inside for loop?

I have the following algorithm:
for(int i=0; i<list.size(); i++){
String cur = list.get(i);
int cnt =0;
while(output.contains(cur)){
cur= cur.substring(0,5) + String.valueOf(cnt);
cnt++;
}
output.add(cur);
}
Not that "list" and "output" are ArrayLists.
I'm thinking that the Time complexity is O(n^2). But what about the while loop that has a output.contains(cur) inside ?
The complexity of this algorithm seems to be depending on the initial contents of output list:
output is empty, while loop is not executed, complexity is O(N), where N is the size of list
list and output are crafted to comply with output.contains(cur) condition
For example,
List<String> list = Arrays.asList("abcde0", "abcde1", "abcde2", "abcde3", "abcde4", "abcde5", "abcde6");
List<String> output = new ArrayList<>(list);
The size of output will be growing thus the number of iterations will be like this:
1) n+1
2) n+1 + n+2 = 2 (n+1) + 1
3) n+1 + n+2 + n+3 = 3 (n+1) + 3
4) n+1 + n+2 + n+3 + n+4 = 4 (n+1) + 6
...
n) n (n+1) + (1 + n-1)*(n-1)/2 = n (n+1) + n (n - 1)/2 = n (3n + 1)/2
Thus, in this (possibly not the worst case) the complexity can be O(N^2).

Order of growth in two loops

What is the order of growth in the following code?
int result = 0;
for (int i = 1; i <= n; i *= 2)
for (int j = 0; j <= i; j++)
result++;
The answer is supposed to be n, but I'm not sure how to figure that out.
How can I calculate the order of growth in these loops?
My assumption would be that the outer loop runs (log n + 1) times. The inner loop is dependant of the outer loop, since it uses i. But how is this equal to n?
Each time the inner loop executes for a given i, it has i+1 iterations.
Since i has the values 2^0, 2^1, 2^2, 2^3, 2^4, ...
The total number of iterations of all the executions of the inner loop is bound by
2^0 + 1 + 2^1 + 1 + 2^2 + 1 + ... + 2^k + 1 where 2^k <= n
that's bound by
2^0 + 2^1 + ... + 2^k + logn
Now we have the sum of a geometric series:
2^0 + 2^1 + ... + 2^k = 2^(k+1) - 1 <= 2n - 1 since 2^k <= n
Therefore you get a bound of:
2n - 1 + logn
which is O(n).

Find the maximal sum of any double slice

I'm trying to solve a problem from the Codility that I already have a solution. The problem description is provided below,
A non-empty array A consisting of N integers is given.
A triplet (X, Y, Z), such that 0 ≤ X < Y < Z < N, is called a double slice.
The sum of double slice (X, Y, Z) is the total of A[X + 1] + A[X + 2] + ... + A[Y − 1] + A[Y + 1] + A[Y + 2] + ... + A[Z − 1].
For example, array A such that:
A[0] = 3
A[1] = 2
A[2] = 6
A[3] = -1
A[4] = 4
A[5] = 5
A[6] = -1
A[7] = 2
contains the following example double slices:
double slice (0, 3, 6), sum is 2 + 6 + 4 + 5 = 17,
double slice (0, 3, 7), sum is 2 + 6 + 4 + 5 − 1 = 16,
double slice (3, 4, 5), sum is 0.
The goal is to find the maximal sum of any double slice.
Write a function:
class Solution { public int solution(int[] A); }
that, given a non-empty array A consisting of N integers, returns the maximal sum of any double slice.
For example, given:
A[0] = 3
A[1] = 2
A[2] = 6
A[3] = -1
A[4] = 4
A[5] = 5
A[6] = -1
A[7] = 2
the function should return 17, because no double slice of array A has a sum of greater than 17.
Assume that:
N is an integer within the range [3..100,000];
each element of array A is an integer within the range [−10,000..10,000].
Complexity:
expected worst-case time complexity is O(N);
expected worst-case space complexity is O(N) (not counting the storage required for input arguments)
The solution is provided below,
public static int solution(int[] A) {
int max = 0;
int N = A.length;
int[] A1 = new int[N];
int[] A2 = new int[N];
for (int i = 1; i < N - 1; i++) {
A1[i] = Math.max(A1[i - 1] + A[i], 0);
}
for (int i = N - 2; i >= 1; i--) {
A2[i] = Math.max(A2[i + 1] + A[i], 0);
}
for (int i = 1; i < N - 1; i++) {
max = Math.max(max, A1[i - 1] + A2[i + 1]);
}
return max;
}
I understand what was done in the initial 2 loops, however, the intention was not clear. My thoughts get disjointed at the time I approached the last for loop. Anyone kindly please explain the solution to me briefly?
I will base my explanation on the code from here, as it uses clearer variable names. Other than that, it's basically the same code as in your question:
class Solution {
public int solution(int[] A) {
int[] maxStartingHere = new int[A.length];
int[] maxEndingHere = new int[A.length];
int maxSum = 0, len = A.length;
for(int i = len - 2; i > 0; --i ) {
maxSum = Math.max(0, A[i] + maxSum);
maxStartingHere[i] = maxSum;
}
maxSum = 0;
for(int i = 1; i < len - 1; ++i ) {
maxSum = Math.max(0, A[i] + maxSum);
maxEndingHere[i] = maxSum;
}
int maxDoubleSlice = 0;
for(int i = 0; i < len - 2; ++i) {
maxDoubleSlice = Math.max(maxDoubleSlice, maxEndingHere[i] + maxStartingHere[i+2]);
}
return maxDoubleSlice;
}
}
The key here is that the code does not look for the maximum slice, only for its sum. The array maxStartingHere records at index i what maximum sum you'd reach if you combine contiguous items starting at i+1; maxEndingHere does the same in reverse. Let's look at an example for that:
i: 0 1 2 3 4
A: 1 -3 2 -1 ...
maxEndingHere: 0 1 0 2 1
Note that:
i=0: there are no elements left of i, so the sum is 0.
i=2: Taking A[0..1] is suboptimal, so the maximum of 0 is achieved by not summing anything at all.
i=4: Another negative element, but 2 + -1 is still better than 0. We're not considering 1 + -3 + 2 + -1 because we already know that the maximum we can reach left of the 2 is negative.
I hope you see that this array shows what can be achieved by choosing different X, but the concrete choice of X is not recorded - just its consequence. Every i corresponds to a Y, and maxEndingHere[i-1] corresponds to the consequence of choosing X optimally for a particular Y.
So we know what sums choosing X and Z optimally, for a particular Y, result in. That means it only remains to choose the best Y (or more precisely: the sum resulting from the best Y). And that is what happens in the third loop.
To reiterate:
What is the maximum you can get, ending anywhere, when starting from a particular item? That's maxStartingHere.
What is the maximum you can get, starting anywhere, when ending at a particular item? That's maxEndingHere.
What is the maximum you can get when ending/starting at a particular item? That's maxDoubleSlice.

Find complexity of algorithm using step count

First of all - yes, I have read several posts here on SO, as well as other places about estimating the complexity of an algorithm.
I have read this, this and this, as well as others
I want to try with an algorithm I wrote that finds the largest rectangle, to see if I understood anything at all from what I've read.
public static long getLargestRectangle(long[] arr, int index, long max) {
int count = 1; //1 step * 1
int i = index - 1; //1 step * 1
int j = index + 1; //1 step * 1
if(index == arr.length-1) return max; //1 step * 2
while(i > -1 && arr[index] <= arr[i]) { //1 step * (N+1)
count++; //1 step * N
i--; //1 step * N
}
while(j <= arr.length-1 && arr[index] <= arr[j]) { //1 step * (N+1)
count++; //1 step * N
j++; //1 step * N
}
max = (max < (arr[index] * count) ? (arr[index] * count) : max); //1 step * 7
return getLargestRectangle(arr, index + 1, max); //1 step * 1
}
//total number of steps: 1 + 1 + 1 + (N + 1) + N + N + (N + 1) + N + N + 7
//=> 6N + 12 = O(N) ?
Am I way off here? I'd love some insight.
EDIT
Like this?
T(n) = O(N) + T(n+1)
T(n) = O(N) + O(N) + T(n+2)
T(n) = O(N) + O(N) + O(N) + T(n+3)
T(n) = i * O(N) + (n+i)
T(n) = n * O(N) + (n+n)
= O(N^2)
If this is wrong, I'd really appreciate if you could update your answer and show me.
Am I way off here? I'd love some insight.
I am afraid so :(
return getLargestRectangle(arr, index + 1, max); //1 step * 1
This above is NOT 1 step, it is a recursive invokation of the method. This method "shrinks" the array by 1 element, so this step actually costs T(n-1), where T(.) is the time complexity of the algorithm.
Combining this with what you already have, you get
T(n) = T(n-1) + O(N)
Solving this recurrence formula will give you the algorithm's complexity.
Note: T(n) = T(n-1) + O(N) is a syntatic sugar, it should actually have been T(n) <= T(n-1) + CONST*N for some constant CONST, since you cannot add a set (O(N)) to a scalar (T(n-1)).
Also note: N!=n. n changes over time, N is the initial length of the array. It is so because your algorithm is actually traversing from n (index) to 0, and from n to N.
This does not change the time complexity in terms of big O notation, however.

Categories