I came across this code. It scans through the array elements only once. But I am confused regarding having two nested while loops increase the complexity to O(n^2). The code is as follows:
def summaryRanges(nums):
x, size = 0, len(nums)
ans = []
while x < size:
c, r = x, str(nums[x])
while x + 1 < size and nums[x + 1] - nums[x] == 1:
x += 1
if x > c:
r += "->" + str(nums[x])
ans.append(r)
x += 1
return ans
I am learning algorithms so please correct me if I am going wrong somewhere. Thank you!!
Your question isn't 100% clear, but if you meant why is this NOT O(N^2) while having nesting loops then:
Although there are nesting loops, they operate on the same space using the same variable to advance the iteration. since the inner loop doesn't backtrack, and whenever it moves ahead it also pushes the outer loop ahead (at the exact same distance), the iteration won't grow larger then M if N grows by M (If N1 = N0 + M). O(N^2) means that as N grows, the iteration grows exponentially.
Related
A frequent task in parallelizing N embarrassingly parallel work chunks contiuguously among K workers is to use the following algorithm to partition, in psuedocode:
acc = 0
for _ in range(K):
end = acc + ceil(N/K)
emit acc:end
acc = end
This will emit K contiguous paritions generally of size N/K and works fine for large N. However if K is approximately N this may cause imbalance because the last worker will get very few items. If we define imbalance as the maximum absolute difference between partition sizes, then an iterative algorithm that starts from any random partition and reduces potential until the maximum difference is 1 (or 0 if K divides N) is going to be optimal.
It seems to me that the following may be a more efficient way of getting at the same answer, by performing online "re-planning". Does this algorithm have a name and optimality proof?
acc = 0
workers = K
while workers > 0:
rem = N - acc
end = acc + ceil(rem/workers)
emit acc:end
acc = end
workers -= 1
Edit. Given that we can define the loop above recursively, I can see that an inductive optimality proof might work. In any case, the name and confirmation of its optimality would be appreciated :)
A simple way of dividing the range is:
for i in range(K):
emit (i*N // K):((i+1)*N // K)
This has the advantage of being itself parallelizable since the iterations do not need to be performed in order.
It is easy to prove that every partition has either floor(N/K) or ceil(N/K) elements, and it is evident that every element will be in exactly one partition. Since floor and ceiling differ by at most 1, the algorithm must be optimal.
The algorithm you suggest is also optimal (and the results are similar). I don't know its name, though.
Another way of dividing the ranges which can be done in parallel is to use the range start(N, K, i):start(N, K, i+1) where start(N, K, i) is (N//K)*i + min(i, N%K). (Note that N//K and N%K only need to be computed once.) This algorithm is also optimal, but distributes the imbalance so that the first partitions are the larger ones. That may or may not be useful.
Here's a simpler approach. You have floor(N/K) tasks which can be perfectly partitioned among the workers, leaving N mod K remaining tasks. To keep the regions contiguous, you can put the remaining tasks on the first N mod K workers.
Here it is in imperative style. Just to be clear, I'm numbering the tasks {0..(N-1)}, and emitting sets of contiguous task numbers.
offset = 0
for 0 <= i < K:
end = offset + floor(N/K)
if i < N mod K:
end = end + 1
emit {c | offset <= c < end}
offset = end
And in a more declarative style:
chunk = floor(N/K)
rem = N mod K
// i == worker number
function offset(i) =
i * chunk + (i if i < rem else rem)
for 0 <= i < K:
emit {c | offset(i) <= c < offset(i+1)}
The proof of optimality is pretty trivial at this point. Worker i has offset(i+1) - offset(i) tasks assigned to it. Depending on i, this is either floor(N/K) or floor(N/K) + 1 tasks.
1) i=s=1;
while(s<=n)
{
i++;
s=s+i;
}
2) for(int i=1;i<=n;i++)
for(int j=1;j<=n;j+=i)
cout<<"*";
3) j=1;
for(int i=1;i<=n;i++)
for(j=j*i;j<=n;j=j+i)
cout<<"*";
can someone explain me the time complexity of these three codes?
I know the answers but I can't understand how it came
1) To figure this out, we need to figure out how large s is on the x'th iteration of the loop. Then we'll know how many iterations occur until the condition s > n is reached.
On the x'th iteration, the variable i has value x + 1
And the variable s has value equal to the sum of i for all previous values. So, on that iteration, s has value equal to
sum_{y = 1 .. x} (y+1) = O(x^2)
This means that we have s = n on the x = O(\sqrt{n}) iteration. So that's the running time of the loop.
If you aren't sure about why the sum is O(x^2), I gave an answer to another question like this once here and the same technique applies. In this particular case you could also use an identity
sum_{y = 1 .. x} y = y choose 2 = (y+1)(y) / 2
This identity can be easily proved by induction on y.
2) Try to analyze how long the inner loop runs, as a function of i and n. Since we start at one, end at n, and count up by i, it runs n/i times. So the total time for the outer loop is
sum_{i = 1 .. n} n/i = n * sum_{i = 1 .. n} 1 / i = O(n log n)
The series sum_{i = 1 .. n} 1 / i is called the harmonic series. It is well-known that it converges to O(log n). I can't enclose here a simple proof. It can be proved using calculus though. This is a series you just have to know. If you want to see a simple proof, you can look on on wikipedia at the "comparison test". The proof there only shows the series is >= log n, but the same technique can be used to show it is <= O(log n) also.
3.) This looks like kind of a trick question. The inner loop is going to run once, but once it exits with j = n + 1, we can never reenter this loop, because no later line that runs will make j <= n again. We will run j = j * i many times, where i is a positive number. So j is going to end up at least as large as n!. For any significant value of n, this is going to cause an overflow. Ignoring that possibility, the code is going to perform O(n) operations in total.
x=1;
While(x<n)
{
x=x + n/100;
}
I'm trying to figure out if it's o(n) or o(1). Because no matter what we put in n's place I think the loop will go just 10 times.
lets say n=1.1
then it will go for 10 times and if n=1.2 loop will go on for 17 times
and if n=2 it will go on for 50 times and when n>=101 loop will be repeated 100 times even if n=10^10000 else you can figure out
Unfortunately you're wrong it it being O(n) or O(1) and this is immediately clear by the fact that it can't be O(1), because it takes different numbers of iterations for varying values of n(even looking at n = 1,2,3,4,5), and it can't be O(n) because it doesn't grow linearly.
Even through a bit of manual calculation you can see clearly that it won't always run 10 times. Examine the following short python program:
def t(n):
x = 1
c = 0
while x < n:
c += 1
x += n/100
return c
a = []
for i in range(10000):
a += [i/100 + 1]
with open("out.csv","w") as f:
for i in a:
f.write(str(i) + "," + str(t(i)) + "\n")
Using Excel or some other application you can easily trend the number of iterations taken to see the following curve:
It is immediately clear at this point that the number of iterations taken is logarithmic in the range {0:100} with any n < 1 taking 0 iterations and n > 100 taking 100 operations. So while Big-O notation wasn't my best subject, I would guess that the time complexity is thus O(log(n)).
This is the code I need to analyse:
i = 1
while i < n
do
j = 0;
while j <= i
do
j = j + 1
i = 2i
So, the first loop should run log(2,n) and the innermost loop should run log(2,n) * (i + 1), but I'm pretty sure that's wrong.
How do I use a theta notation to prove it?
An intuitive way to think about this is to see how much work your inner loop is doing for a fixed value of outer loop variable i. It's clearly as much as i itself. Thus, if the value of i is 256, then then you will do j = j + 1 that many times.
Thus, total work done is the sum of the values that i takes in the outer loop's execution. That variable is increasing much rapidly to catch up with n. Its values, as given by i = 2i (it should be i = 2*i), are going to be like: 2, 4, 8, 16, ..., because we start with 2 iterations of the inner loop when i = 1. This is a geometric series: a, ar, ar^2 ... with a = 1 and r = 2. The last term, as you figured out will be n and there will be log2 n terms in the series. And that is simple summation of a geometric series.
It doesn't make much sense to have a worst case or a best case for this algorithm because there are no different permutations of the input which is just a number n in this case. Best case or worst case are relevant when a particular input (e.g. a particular sequence of numbers) affects the running time of the algorithm.
The running time then is the sum of geometric series (a.(r^num_terms - 1)/(r-1)):
T(n) = 2 + 4 + ... 2^(log2 n)
= 2 . (2^log2 n - 1)
= 2 . (n - 1)
⩽ 3n = O(n)
Thus, you can't be doing work that is more than some constant multiple of n. Hence, the running time of this algorithm is O(n).
You can't be doing some work that is less than some (other) constant multiple of n, since you have to go through the increment in inner loop as shown above. Thus, the running time of this algorithm is also ≥ c.n i.e. it is Ω(n).
Together, this means that running time of this algorithm is Θ(n).
You can't use i in your final expression; only n.
You can easily see that the inner loop executes i times each time it is reached. And it sounds like you've figured out the different values that i can have. So add up those values, and you have the total amount of work.
For each of the procedures below, let T (n) be the running time. Find the order of T (n)
(i.e., find f(n) such that T (n) ∈ (f(n)).
Procedure Fum(int n):
for i from 1 to n do
y ← 1/i
x ← i
while x > 0 do
x ← x − y
end while
end for
I know how to find run times of simple functions but since this is a nested loop where the inner loop depends on a variable from the outer loop, I'm having trouble.
It should be 1+4+9+...+n^2 = n(n+1)(2n+1)/6, or simply O(n^3), for this case.
For each step in the for-loop, it will run i^2 times for the while. Given x=i;y=1/i;, it will take i^2 (as x=y*i^2) times for x to reach x<=0 by decreament step x=x-y.
For i, it will be 1,2,...,n, summing them up, you will get 1+4+9+...n^2 = n(n+1)(2n+1)/6.
First, lets consider the runtime of the inner loop:
We want to figure out how many times the inner loop runs, in terms of i.
That is, we want to solve for f(i) in x-f(i)y = 0. If we sub in x = i, and y = 1/i, we get f(i) = i^2.
We know the outer loop will run exactly n times, so then, we get the total number of times the inner loop will be run:
= 1 + 4 + 9 + ... + n^2
This sum is equal to n(n+1)(2n+1)/6, which is O(n^3)