Time complexity and number of calls - python

I have trouble finding the time complexity of my program and the exact formula to compute the number of calls (represented by the length of the list). Here is my python program that I wrote:
from math import *
def calc(n):
i = n
li = []
while i>0:
j = 0
li.append(1)
while j<n:
li.append(1)
k = j
while k<n:
li.append(1)
k+=1
j+=1
i = floor(i/2)
return len(li)
for i in range(1, 16):
print(calc(i))

For computing time complexity of this program we come from most inner loop.
k=j
while k<n:
li.append(1)
k+=1
Here k iterate one by one forward move start from j to n. That means it run (n-k) time. Now back to the outer loop of this inner loop.
j=0
while j<n:
li.append(1)
k = j
//(n-j)
j+=1
Here j also iterate one by one from 0 to n. Total n times. And for every j inner loop iterate n-j.
while j=0 than k=0,1,2,3,....(n-1) Total n times
while j=1 than k=1,2,3,....(n-1) Total n-1 times
while j=2 than k=2,3,4,....(n-1) Total n-2 times
....
....
while j=n-1 than k=(n-1) Total 1 times
Total complexity = 1+2+3+......(n-2)+(n-1)+n
=(n*(n+1))/2;
Now the outer loop for i :
i=n
while i>0:
(n*(n+1))/2;
i = floor(i/2)
Here i start from n and every time divided by 2. We know if it iterate dividing by x than it's complexity will log(base x)(n). Hence, for i the complexity will approximately log(base 2)(n).
Hence the Total time complexity is approximately log(n)(n(n+1))/2;
it also can be represented as O(n^2*log(n)).

Maybe plotting your total number of calls against your value for n will help:
import matplotlib.pyplot as plt
x = [i for i in range(1, 16)]
y = [calc(n) for n in x]
plt.plot(x, y)
plt.show()

Related

What is the time complexity of below code?

Can some one please let me know the time complexity of below code.
nums=[1,2,4,6,180,290,1249]
ll=[]
l=[]
for i in nums:
for j in range(1,int(sqrt(i))+1):
if(i%j==0):
l.append(j)
ll.append(l.copy())
l.clear()
print(ll)
pass
There are three main operations that are going to determine the time complexity.
The outer loop for i in nums is O(N) where N = len(nums)
The inner loop for j in range(1,int(sqrt(i))+1)
Within the first loop we also have ll.append(l.copy()), where l.copy() is an O(k) operation where k == len(l)
Let N = len(nums), M = sqrt(max(nums)), and K = the length of the longest list l being copied.
As M and K are at the same level, this starts us at O(N * (M+K))
However, K is dependent on M, and will always be smaller (K is the number of factors of i <= sqrt(i)), so we can effectively ignore it.
This results in the a complexity of O(N * M), where N = len(nums), and M = sqrt(max(nums))

Find largest substring of numbers within a tolerance level

I have the following input:
a tolerance level T
Number of numbers N
N numbers
The task is to find the longest period within those N numbers such that they are within the tolerance level. More precisely, given a left and a right bound of a substring l and r and two distinct elements a1 and a2 between the two bounds, it must hold that |a1 - a1| <= T. How can I do this in an efficient way? My approach is:
def getLength(T, N, numbers):
max_length = 1
for i in range(0, N-1):
start = numbers[i]
numlist = [start]
for j in range(i+1, N):
end = numbers[j]
numlist.append(end)
if (max(numlist) - min(numlist)) > T:
break
if (j-i+1) > max_length:
max_length = j-i+1
return max_length
EDIT: To make it clear. The code works as expected. However, it is not efficient enough. I would like to do it more efficiently.
First of all, I'm not sure if your code does what you describe in your question. Secondly, it takes (much) less than second to process 12,000 random numbers.
Regardless, it can be sped up by not calling min() and max() on the numlist every time a new element is appended to it. Instead you can just update the current minimum and maximum variables with a couple of if statements.
Here code showing that being done, along with a simple framework I wrote for timing performance:
def getLength(T, N, numbers):
max_length = 1
for i in range(N-1):
start = numbers[i]
numlist = [start]
min_numlist = max_numlist = start # Added variables.
for j in range(i+1, N):
end = numbers[j]
numlist.append(end)
# Inefficient - replaced.
# if (max(numlist) - min(numlist)) > T:
# break
# Update extremities.
if end > max_numlist:
max_numlist = end
if end < min_numlist:
min_numlist = end
if max_numlist-min_numlist > T:
break
if j-i+1 > max_length:
max_length = j-i+1
return max_length
if __name__ == '__main__':
import random
import time
random.seed(42) # Use hardcoded seed to get same numbers each time run.
T = 100
N = 12000
numbers = [random.randrange(1000) for _ in range(N)]
starttime = time.time()
max_length = getLength(T, N, numbers)
stoptime = time.time()
print('max length: {}'.format(max_length))
print('processing {:,d} elements took {:.5f} secs'.format(N, stoptime-starttime))

Program terminated due to time out

PROBLEM :
You are given a list of size N, initialized with zeroes. You have to perform M operations on the list and output the maximum of final values of all the elements in the list. For every operation, you are given three integers a,b and k and you have to add value to all the elements ranging from index a to b(both inclusive).
Input Format
First line will contain two integers N and M separated by a single space.
Next lines will contain three integers a,b and k separated by a single space.
Numbers in list are numbered from 1 to N.
Here is the code which I have written:
n,m=map(int,input().split())
arr=[]
for i in range(n+1):
arr.append(0)
for j in range(m):
a,b,k=map(int,input().split())
for i in range(a,b+1):
arr[i]+=k;
print(max(arr))
When I try to submit my solution I get a "TERMINATED DUE TO TIMOUT" message.Could you please suggest a strategy to avoid these kind of errors and also a solution to the problem.
Thanks in advance!
Don't loop over the list range; instead, use map again to increment the indicated values. Something like
for j in range(m):
a,b,k=map(int,input().split())
arr[a:b+1] = map(lambda <increment-by-k>, arr[a:b+1])
This should let your resident optimization swoop in and save some time.
You probably need an algorithm that has better complexity than O(M*N).
You can put interval delimiters in a list:
n,m=map(int,input().split())
intervals = []
arr = [0 for i in range(n)]
for j in range(m):
a,b,k=map(int,input().split())
intervals += [(str(a), "begin", k)]
intervals += [(str(b), "end", k)]
intervals = sorted(intervals, key=lambda x: x[0]+x[1])
k, i = 0, 0
for op in intervals:
ind = int(op[0])
if op[1] == "begin":
while ind > i:
arr[i] += k
i += 1
k += op[2]
else:
while i <= ind:
arr[i] += k
i+= 1
k -= op[2]
print(arr)
If the sorting algorithm is O(MlogM), this is O(MlogM + N)

Nested while loop not looping properly in python

I'm new, and I have a question about my python code. It doesn't execute like I think it should.
The part that's messing up is in the nested while:
import math
mu=4*math.pi*10**(-7) ##mu naught
I=
float(input('Enter a value for the current measured in Amperes: '))
R=abs(float(input('Enter a value for the radius
of the circle measured in meters: '))) ##radius
step=R/200 ##Step size for integration
B=[] ##array for B(r)
J=[]
Plot=[B,J]
k = 1 ##step counter for Circumfrence
j = 1 ##step counter for Radius
b_temp = 0 ##place holder to sum b(step*k)
D_Vector = [R*math.cos(k*math.pi/100),R*math.sin(k*math.pi/100)]
R_Vector = [R-j*step,0]
while(j*step<=R):
while(k*step<=2*math.pi*R):
r=(math.sqrt((R_Vector[0]-D_Vector[0])**2 +
(R_Vector[1]-D_Vector[1])**2))
b_temp = b_temp + (mu/(4*math.pi))*I*step*step/(r**2)
k=k+1
D_Vector = [R*math.cos(k*math.pi/100),R*math.sin(k*math.pi/100)]
R_Vector = [R-j*step,0]
print(round(r,3), j)
B.append(round(b_temp,8))
print('New j value!')
b_temp=0
J.append(round(step*j,3))
j=j+1
It's supposed to step down the radius (the first while loop) and then loop around the circle, summing the magnetic field contribution for each chunk of wire. For some reason, it's not looping through the inner loop each iteration of the outer loop like it should be, and I'm honestly not sure why. The new j value line is to let me see if it's looping properly, and this is my output:
...
13.657 1
13.884 1
14.107 1
14.327 1
14.543 1
14.756 1
14.965 1
15.17 1
15.372 1
New j value!
New j value!
New j value!
New j value!
New j value!
New j value!
New j value!
New j value!
...
The 1 at the end of each float (radius value) is the j value, which stays at 1 for each circle around the loop...
You are increasing k but never resetting it to 1. so k * step becomes bigger and bigger until the condition of the inner loop becomes false. At which point is clear that it's not getting executed anymore.
Note that you should avoid while loops when the you are simply iterating over an integer range. Just use for j in range(a, b). This avoid exactly the kind of bug you have in your code.
If I'm not mistaken you can replace the loops with:
area = 2*math.pi*R
for j in range(1, R//step + 1):
# j*step <= R holds
for k in range(1, area // step + 1):
# k * step <= area holds here
where a // b is quotient of a and b.

Find the total number of triplets when summed are less than a given threshold

So I'm working on some practice problems and having trouble reducing the complexity. I am given an array of distinct integers a[] and a threshold value T. I need to find the number of triplets i,j,k such that a[i] < a[j] < a[k] and a[i] + a[j] + a[k] <= T. I've gotten this down from O(n^3) to O(n^2 log n) with the following python script. I'm wondering if I can optimize this any further.
import sys
import bisect
first_line = sys.stdin.readline().strip().split(' ')
num_numbers = int(first_line[0])
threshold = int(first_line[1])
count = 0
if num_numbers < 3:
print count
else:
numbers = sys.stdin.readline().strip().split(' ')
numbers = map(int, numbers)
numbers.sort()
for i in xrange(num_numbers - 2):
for j in xrange(i+1, num_numbers - 1):
k_1 = threshold - (numbers[i] + numbers[j])
if k_1 < numbers[j]:
break
else:
cross_thresh = bisect.bisect(numbers,k_1) - (j+1)
if cross_thresh > 0:
count += cross_thresh
print count
In the above example, the first input line simply provides the number of numbers and the threshold. The next line is the full list. If the list is less than 3, there is no triplets that can exist, so we return 0. If not, we read in the full list of integers, sort them, and then process them as follows: we iterate over every element of i and j (such that i < j) and we compute the highest value of k that would not break i + j + k <= T. We then find the index (s) of the first element in the list that violates this condition and take all the elements between j and s and add them to the count. For 30,000 elements in a list, this takes about 7 minutes to run. Is there any way to make it faster?
You are performing binary search for each (i,j) pair to find the corresponding value for k. Hence O(n^2 log(n)).
I can suggest an algorithm that will have the worst case time complexity of O(n^2).
Assume the list is sorted from left to right and elements are numbered from 1 to n. Then the pseudo code is:
for i = 1 to n - 2:
j = i + 1
find maximal k with binary search
while j < k:
j = j + 1
find maximal k with linear search to the left, starting from last k position
The reason this has the worst case time complexity of O(n^2) and not O(n^3) is because the position k is monotonically decreasing. Thus even with linear scanning, you are not spending O(n) for each (i,j) pair. Rather, you are spending a total of O(n) time to scan for k for each distinct i value.
O(n^2) version implemented in Python (based on wookie919's answer):
def triplets(N, T):
N = sorted(N)
result = 0
for i in xrange(len(N)-2):
k = len(N)-1
for j in xrange(i+1, len(N)-1):
while k>=0 and N[i]+N[j]+N[k]>T:
k-=1
result += max(k, j)-j
return result
import random
sample = random.sample(xrange(1000000), 30000)
print triplets(sample, 500000)

Categories