Time complexity when j+=sqrt(i) - nested

I need to find the time complexity (in terms of theta) of this function:
int x = 0;
for (int i=1; i < n ; i++) {
for (double j=i; j <= n ; j+=sqrt(i)) {
++x;
}
}
I know that the outer loop does n-1 iterations and the inner loop does (n-i)/sqrt(i) iterations so I need to calculate sigma of i=1 to n-1 of (n-i)/sqrt(i). Any idea how to do that?
EDIT:
Assume sqrt() runs in O(1).

I don't know what sigma and theta mean, but sqrt is a constant time operation so it basically doesn't matter in big O notation, ie j+=sqrt(i); is the same as j+=i; is the same as j+=1;. Also (n-k) ~= n for k much less than n. This means as n gets large n-i is just n. So (n-i) * sqrt() = n * 1 = n. And you do this n times for the outer loop so n^2.
Addition:
As to your complicated series, I'm sure this is accurate, but it is not what we care about, we care about the order of the operation. So we need show your series is O(n^2) or K*n^2. So you have i + 2*i + ... (n-1)*i + n*i. Where i is constant so we can factor it out and wrap it up in K and are left with 1 + ... + n. This statement is dominated by n ie as n gets large n ~= (n-1), and (n-1) ~= (n-2) which implies that (n-2) ~= n. Now this doesn't hold as we approach zero, but n is so large we can drop the first say million terms. so we are left with some function that looks like
C*(n-k)*n + c. where C, k, and c are all constant. Since we don't care about constants we just care about growth as n grows we can drop all these constants and just save the n^2. Alternatively, you could show that your series is bounded by n^k*n where k goes to one as n approaches infinity, but a good logic argument is usually better. ~Ben

Related

Maximum Sum of XOR operation on a selected element with array elements with an optimize approach

Problem: Choose an element from the array to maximize the sum after XOR all elements in the array.
Input for problem statement:
N=3
A=[15,11,8]
Output:
11
Approach:
(15^15)+(15^11)+(15^8)=11
My Code for brute force approach:
def compute(N,A):
ans=0
for i in A:
xor_sum=0
for j in A:
xor_sum+=(i^j)
if xor_sum>ans:
ans=xor_sum
return ans
Above approach giving the correct answer but wanted to optimize the approach to solve it in O(n) time complexity. Please help me to get this.
If you have integers with a fixed (constant) number of c bites then it should be possible because O(c) = O(1). For simplicity reasons I assume unsigned integers and n to be odd. If n is even then we sometimes have to check both paths in the tree (see solution below). You can adapt the algorithm to cover even n and negative numbers.
find max in array with length n O(n)
if max == 0 return 0 (just 0s in array)
find the position p of the most significant bit of max O(c) = O(1)
p = -1
while (max != 0)
p++
max /= 2
so 1 << p gives a mask for the highest set bit
build a tree where the leaves are the numbers and every level stands for a position of a bit, if there is an edge to the left from the root then there is a number that has bit p set and if there is an edge to the right there is a number that has bit p not set, for the next level we have an edge to the left if there is a number with bit p - 1 set and an edge to the right if bit p - 1 is not set and so on, this can be done in O(cn) = O(n)
go through the array and count how many times a bit at position i (i from 0 to p) is set => sum array O(cn) = O(n)
assign the root of the tree to node x
now for each i from p to 0 do the following:
if x has only one edge => x becomes its only child node
else if sum[i] > n / 2 => x becomes its right child node
else x becomes its left child node
in this step we choose the best path through the tree that gives us the most ones when xoring O(cn) = O(n)
xor all the elements in the array with the value of x and sum them up to get the result, actually you could have built the result already in the step before by adding sum[i] * (1 << i) to the result if going left and (n - sum[i]) * (1 << i) if going right O(n)
All the sequential steps are O(n) and therefore overall the algorithm is also O(n).

Why is Time Complexity of Bucket Sort is O(n^2) and not O(log(n) * n^2)?

import math
def insertionSort(arr):
for i in range(len(arr)-1):
for j in range(i+1, len(arr)):
if arr[j] < arr[i]:
arr[j], arr[i] = arr[i], arr[j]
return arr
def bucketSort(arr):
no_of_buck = round(math.sqrt(len(arr)))
bucketArr = [[] for _ in range(no_of_buck)]
n = len(bucketArr)
maximumVal = max(arr)
for i in arr:
appropriate_bucket = math.ceil(i * n / maximumVal)
bucketArr[appropriate_bucket-1].append(i)
for i in bucketArr:
i = insertionSort(i)
arr = []
for i in bucketArr:
arr.extend(i)
print(arr)
Insertion Sort itself is an O(n^2) operation and outer loop goes upto sqaure root of the number of elements i.e. O(sqrt(n)), So it should be O(log(n) * n^2)
Insertion Sort itself is an O(n^2) operation and outer loop goes upto sqaure root of the number of elements i.e. O(sqrt(n)), So it should be O(log(n) * n^2)
You have given an argument why the time complexity might be O(n2.5), not O(log(n) * n^2), although there is a relatively simple reason why both of them are not tight upper bounds (loose upper bounds are not wrong, but less interesting, and may be counted as wrong in some contexts).
The total number of items is still only n.
In the worst case, all items are in one bucket, and that's where the O(n²) time complexity comes from. All other distributions of items over buckets are better. The buckets cannot all be big, which is what your argument implicitly assumes.
This is an example of why the rule of thumb "multiply the time complexities of nested loops" is just a rule of thumb.

Improving the complexity of Brocard's problem?

Brocard's problem is n! + 1 = m^2. The solutions to this problems are pairs of integers called Brown numbers (4,5), etc, of which only three are known.
A very literal implementation to Brocard's problem:
import math
def brocard(n,m):
if math.factorial(n)+1 == m**2:
return (n,m)
else:
return
a=10000
for n in range(a):
for m in range(a):
b=brocard(n,m)
if b is not None:
print(b)
The time complexity of this should be O(n^2) because of the nested for loops with differing variables and the complexity of whatever math.factorial algorithm is (apparently divide-and-conquer). Is there any way to improve upon O(n^2)?
There are other interpretations on SO like this. How does the time complexity of this compare with my implementation?
Your algorithm is O(n^3).
You have two nested loops, and inside you use factorial(), having O(n) complexity itself.
Your algorithm tests all (n,m) combinations, even those where factorial(n) and m^2 are far apart, e.g. n=1 and m=10000.
You always recompute the factorial(n) deep inside the loop, although it's independent of the inner loop variable m. So, it could be moved outside of the inner loop.
And, instead of always computing factorial(n) from scratch, you could do that incrementally. Whenever you increment n by 1, you can multiply the previous factorial by n.
A different, better approach would be not to use nested loops, but to always keep n and m in a number range so that factorial(n) is close to m^2, to avoid checking number pairs that are vastly off. We can do this by deciding which variable to increment next. If the factorial is smaller, then the next brocard pair needs a bigger n. If the square is smaller, we need a bigger m.
In pseudo code, that would be
n = 1; m = 1; factorial = 1;
while n < 10000 and m < 10000
if factorial + 1 == m^2
found a brocard pair
// the next brocard pair will have different n and m,
// so we can increment both
n = n + 1
factorial = factorial * n
m = m + 1
else if factorial + 1 < m^2
// n is too small for the current m
n = n + 1
factorial = factorial * n
else
// m is too small for the given n
m = m + 1
In each loop iteration, we either increment n or m, so we can have at most 20000 iterations. There is no inner loop in the algorithm. We have O(n). So, this should be fast enough for n and m up to the millions range.
P.S. There are still some optimizations possible.
Factorials (after n=1, known to have no brocard pair) are always even numbers, so m^2 must be odd to satisfy the brocard condition, meaning that we can always increment m by 2, skipping the even number in between.
For larger n values, the factorial increases much faster than the square. So, instead of incrementing m until its square reaches the factorial+1 value, we could recompute the next plausible m as integer square root of factorial+1.
Or, using the square root approach, just compute the integer square root of factorial(n), and check if it matches, without any incremental steps for m.

What is the time complexity of this agorithm (that solves leetcode question 650) (question 2)?

Hello I have been working on https://leetcode.com/problems/2-keys-keyboard/ and came upon this dynamic programming question.
You start with an 'A' on a blank page and you get a number n when you are done you should have n times 'A' on the page. The catch is you are allowed only 2 operations copy (and you can only copy the total amount of A's currently on the page) and paste --> find the minimum number of operations to get n 'A' on the page.
I solved this problem but then found a better solution in the discussion section of leetcode --> and I can't figure out it's time complexity.
def minSteps(self, n):
factors = 0
i=2
while i <= n:
while n % i == 0:
factors += i
n /= i
i+=1
return factors
The way this works is i is never gonna be bigger than the biggest prime factor p of n so the outer loop is O(p) and the inner while loop is basically O(logn) since we are dividing n /= i at each iteration.
But the way I look at it we are doing O(logn) divisions in total for the inner loop while the outer loop is O(p) so using aggregate analysis this function is basically O(max(p, logn)) is this correct ?
Any help is welcome.
Your reasoning is correct: O(max(p, logn)) gives the time complexity, assuming that arithmetic operations take constant time. This assumption is not true for arbitrary large n, that would not fit in the machine's fixed-size number storage, and where you would need Big-Integer operations that have non-constant time complexity. But I will ignore that.
It is still odd to express the complexity in terms of p when that is not the input (but derived from it). Your input is only n, so it makes sense to express the complexity in terms of n alone.
Worst Case
Clearly, when n is prime, the algorithm is O(n) -- the inner loop never iterates.
For a prime n, the algorithm will take more time than for n+1, as even the smallest factor of n+1 (i.e. 2), will halve the number of iterations of the outer loop, and yet only add 1 block of constant work in the inner loop.
So O(n) is the worst case.
Average Case
For the average case, we note that the division of n happens just as many times as n has prime factors (counting duplicates). For example, for n = 12, we have 3 divisions, as n = 2·2·3
The average number of prime factors for 1 < n < x approaches loglogn + B, where B is some constant. So we could say the average time complexity for the total execution of the inner loop is O(loglogn).
We need to add to that the execution of the outer loop. This corresponds to the average greatest prime factor. For 1 < n < x this average approaches C.n/logn, and so we have:
O(n/logn + loglogn)
Now n/logn is the more important term here, so this simplifies to:
O(n/logn)

Time Complexity of Dependent Nested Loop

Hi I've been trying to understand what the time complexity of this nested loop will be for a while now.
int i = 1;
while(i < n) {
int j = 0;
while(j < n/i){
j++;
}
i = 2 * i;
}
Based on the couple of calculations I've done I think its Big O notation is O(log(n)), but I'm not sure if that is correct. I've tried looking for some examples where the inner loop speeds up at this rate, but I couldn't find anything.
Thanks
One information that surprisingly few people use when calculating complexity is: the sum of terms is equal to the average multiplied by the quantity of terms. In other words, you can replace a changing term by its average, and get the same result.
So, your outer while loop repeats O(log n) times. But the inner while loop, repeats: n, n/2, n/4, n/8, ..., 1, depending on which step of the outer while are we. But (n, n/2, n/4, ..., 1) is a geometric progression, with log(n) terms, and ratio 1/2, which sum is n.(1-1/n)/(1/2) = 2n-2 \in O(n). Its average, therefore, is O(n/log(n)). Since it repeats O(log(n)) times, the whole complexity is O(log(n)*n/log(n)) = O(n)...

Resources