Input - array / list a, constant k
Output - Length of Longest sublist/subarray with sum <=k
E.g. given
I am Bob
i.e. array [1,2,3] and k=3
Sublists possible are [1],[2],[3],[1,2]
Longest sublist here is [1,2]
Length = 2
Issue - TimeOut error in Python on Hackerrank
Time Complexity - 1 for loop - O(n)
Space complexity O(n)
def maxLength(a, k):
lenmax=0
dummy=[]
for i in a:
dummy.append(i)
if sum(dummy)<=k:
lenmax=max(lenmax,len(dummy))
else:
del dummy[0]
return lenmax
Solved it by replacing the time-intensive operation
Time-out occurs when it exceeds the time limit set by HackerRank for every environment "HackerRank TimeOut"
Solution
Replace sum() function by a variable
In worst case, sum(list) would take O(n^2) time if the entire list was to be summed up all the time.
Instead, maintaining a variable would mean O(n) for the entire function as O(1) for updating a variable.
def maxLength(a, k):
lenmax=0
dummy=[]
sumdummy=0
for i in a:
dummy.append(i)
sumdummy+=i
if sumdummy<=k:
lenmax=max(lenmax,len(dummy))
else:
sumdummy-=dummy[0]
del dummy[0]
return lenmax
Related
I'm doing the longest consecutive sequence problem on LeetCode (https://leetcode.com/problems/longest-consecutive-sequence/) and wrote the following solution:
(I made a typo earlier and put s instead of nums on line 6)
class Solution:
def longestConsecutive(self, nums: List[int]) -> int:
s = set(nums)
res = 0
for n in nums:
if n - 1 not in nums:
c = 1
while n + 1 in s:
c += 1
n += 1
res = max(res, c)
return res
This solution takes 4902 ms according to the website, but when I change the first for loop to
for n in s:
The runtime drops to 491 ms. Is looping through the hashset 10 times faster?
If you changed if n - 1 not in nums to if n - 1 not in s, then you might see it reduces the runtime a lot. in operator in set is faster in list. Generally, in in set takes O(1), while it takes O(n) for in in list. https://wiki.python.org/moin/TimeComplexity
Regarding iterating in set and list, iterating through a set can be faster if there are lots of duplicates in the list. E.g., iterating through a list with n same elements takes O(n), while it takes O(1) since there will be only one element in the set.
Is iterating through a set faster than through a list?
No, iterating through either of these data structures is the same for the same number of elements.
However, while n + 1 in s does not necessarily iterate through the elements of s. in here is an operator that checks if the value n+1 is an element of s. If s is a set, this operation is guaranteed to have O(1) time. If s is a list, then the operator will have O(n) time.
I am using this sorting algorithm:
def merge(L,R):
"""Merge 2 sorted lists provided as input
into a single sorted list
"""
M = [] #Merged list, initially empty
indL,indR = 0,0 #start indices
nL,nR = len(L),len(R)
#Add one element to M per iteration until an entire sublist
#has been added
for i in range(nL+nR):
if L[indL]<R[indR]:
M.append(L[indL])
indL = indL + 1
if indL>=nL:
M.extend(R[indR:])
break
else:
M.append(R[indR])
indR = indR + 1
if indR>=nR:
M.extend(L[indL:])
break
return M
def func(L_all):
if len(L_all)==1:
return L_all[0]
else:
L_all[-1] = merge(L_all[-2],L_all.pop())
return func(L_all)
merge() is the classical merge algorithm in which, given two lists of sorted numbers, it merges them into a single sorted list, it has a linear complexity. An example of input is L_all = [[1,3],[2,4],[6,7]], a list of N sorted lists. The algorithm applies merge to the last elements of the list until there is just one element in the list, which is sorted. I have evaluated the execution time for different N, using constant length for the lists inside the list and I have obtained an unexpected pattern. The algorithm has a linear complexity but the execution time is constant, as you can see in the graph
What could be the explanation of the fact that the execution time does not depend on N?.
You haven't shown your timing code, but the problem is likely to be that your func mutates the list L_all so that it becomes a list of length 1, containing a single sorted list. After the first call func(L_all) in timeit, all subsequent calls don't change L_all at all. Instead, they just instantly return L_all[0]. Rather than 100000 calls to L_all for each N in timeit , you are in effect just doing one real call for each N. Your timing code just shows that return L_all[0] is O(1), which is hardly surprising.
I would rewrite your code like this:
import functools, random, timeit
def func(L_all):
return functools.reduce(merge,L_all)
for n in range(1,10):
L = [sorted([random.randint(1,10) for _ in range(5)]) for _ in range(n)]
print(timeit.timeit("func(L)",globals=globals()))
Then even for these smallish n you see a clear dependence on n:
0.16632885999999997
1.711736347
3.5761923199999996
6.058960655
8.796722217
15.112843280999996
17.723825805000004
22.803739991999997
26.114925834000005
I have written a python program to Calculate the average word length and return the average word length.
def stringlength(A: List[str]) -> int:
average = sum(len(word) for word in A)/len(A)
return average
test_list = ['gfg', 'is', 'best', 'for', 'geeks']
print(stringlength(test_list))
Answer: 3.4
I am new to calculating time and space complexitities.
I am assuming the time complexity of this python program would be O(N) where N is the length of the list since it uses builtin function like len and for loop
and space complexity would be O(N) since len() in for loop would return a list of lengths.
Can anyone please clarify if my assumption is correct
You are right. Time complexity would be O(n) where n is length of your list.
This is because you are going over every element in your list.
When I plot the time taken for the following algorithm for different size input, the time complexity appears to be polynomial. I'm not sure which operations account for this.
I'm assuming it's to do with list(s), del l[i] and l[::-1], but I'm not clear what the complexity of these is individually. Can anyone please explain?
Also, is there a way to optimize the algorithm without completely changing the approach? (I know there is a way to bring it down to linear time complexity by using "double-ended pincer-movement".)
def palindrome_index(s):
for i, c in enumerate(s):
l = list(s)
del l[i]
if l[::-1] == l:
return i
return -1
Your algorithm indeed is quadratic in len(s):
In iteration i, you perform linear time operations in the length: creating the list, reversing it, and (on linear on average) erasing element i. Since you perform this len(s) times, it is quadratic in len(s).
I'm assuming it's to do with list(s), del l[i] and l[::-1], but I'm not clear what the complexity of these is individually. Can anyone please explain?
Each of these operations is linear time (at least on average, which is enough to analyze your algorithm). Constructing a list, either from an iterable, or by reversing an existing list, is linear in the length of the list. Deleting element i, at the very least, requires about n - i + 1 shifts of the elements, as each one is moved back once.
All of these are linear "O(n)":
list(s)
list(s) creates a new list from s. To do that, it has to go through all elements in s, so its time is proportional to the length of s.
l[::-1]
Just like list(s), l[::-1] creates a new list with the same elements as l, but in different order. It has to touch each element once, so its time is proportional to the length of l.
del l[i]
In order to delete an element at position i, the element which was at position i+1 has to be moved to position i, then element which was at i+2 has to be moved to position i+1 etc. So, if you are deleting the first element (del l[0]), it has to touch move elements of the list and if you are deleting the last (del l[-1]), it just has to remove the last. On average, it will move n/2 elements, so it is also linear.
I have list of numbers as follows -
L = [ 1430185458, 1430185456, 1430185245, 1430185246, 1430185001 ]
I am trying to determine which numbers are within range of "2" from each other. List will be in unsorted when I receive it.
If there are numbers within range of 2 from each other I have to return "1" at exact same position number was received in.
I was able to achieve desired result , however code is running very slow. My approach involves sorting list, iterating it twice taking two pointers and comparing it successively. I will have millions of records coming as seperate lists.
Just trying to see what is best possible approach to address this problem.
Edit - Apology as I was away for a while. List can have any number of elements in it ranging from 1 to n. Idea is to return either 0 or 1 in exact same position number was received. I can not post actual code I implemented but here is pseudo code.
a. create new list as list of list with second part as 0 for each element. We assume that there are no numbers within range of 2 of each other.
[[1430185458,0], [1430185456,0], [1430185245,0], [1430185246,0], [1430185001,0]]
b. sort original list
c. compare first element to second, second to third and so on until end of list is reached and whenever difference is less than or equal to 2 update corresponding second elements in step a to 1.
[[1430185458,1], [1430185456,1], [1430185245,1], [1430185246,1], [1430185001,0]]
The goal is to be fast, so that presumably means an O(N) algorithm. Building an NxN difference matrix is O(N^2), so that's not good at all. Sorting is O(N*log(N)), so that's out, too. Assuming average case O(1) behavior for dictionary insert and lookup, the following is an O(N) algorithm. It rips through a list of a million random integers in a couple of seconds.
def in_range (numbers) :
result = [0] * len(numbers)
index = {}
for idx, number in enumerate(numbers) :
for offset in range(-2,3) :
match_idx = index.get(number+offset)
if match_idx is not None :
result[match_idx] = result[idx] = 1
index[number] = idx
return result
Update
I have to return "1" at exact same position number was received in.
The update to the question asks for a list of the form [[1,1],[2,1],[5,0]] given an input of [1,2,5]. I didn't do that. Instead, my code returns [1,1,0] given [1,2,5]. It's about 15% faster to produce that simple 0/1 list compared to the [[value,in_range],...] list. The desired list can easily be created using zip:
zip(numbers,in_range(numbers)) # Generator
list(zip(numbers,in_range(numbers))) # List of (value,in_range) tuples
I think this does what you need (process() modifies the list L). Very likely it's still optimizable, though:
def process(L):
s = [(v,k) for k,v in enumerate(L)]
s.sort()
j = 0
for i,v_k in enumerate(s):
v = v_k[0]
while j < i and v-s[j][0]>2:
j += 1
while j < i:
L[s[j][1]] = 1
L[s[i][1]] = 1
j += 1