Optimising Performance of Codility Flags Python - python

I've written the below algorithm as a solution to Codility Flags. This passes the correctness checks, however it times out on most of the performance checks.
The complexity of this should be O(m**2) where m is the number of peaks in A and n is the length of A. However, the while potentialK > maxFlags loop should only execute until a suitable number of flags is found which satisfies the criteria. I'm not sure how to further optimise this for time complexity.
def solution(A):
peaks = []
distances = []
if len(A) == 1: return 0
for i in range(1, len(A) -1):
if A[i] > A[i-1] and A[i] > A[i+1]:
if len(distances) == 0:
distances.append(i)
else:
distances.append(i - peaks[-1])
peaks.append(i)
if len(peaks) == 0: return 0
if len(peaks) == 1: return 1
if len(peaks) == 2: return 2 if peaks[1] - peaks[0] >= 2 else 1
potentialK = len(peaks)
maxFlags = 0
while potentialK > maxFlags:
cumDistance = 0
flags = 0
firstFlag = True
for i in range(1, len(distances)):
cumDistance += distances[i]
if cumDistance >= potentialK:
if firstFlag:
flags += 2
firstFlag = False
else:
flags += 1
cumDistance = 0
if flags > maxFlags and flags == potentialK:
return flags
elif flags > maxFlags and potentialK > flags:
maxFlags = flags
potentialK -= 1
return maxFlags

Your algorithm is O(n^2), since there can be O(n) peaks in the input. Speeding up your algorithm relies on the fact that you know the input size in advance.
Observe that the answer is an integer in the interval [1, ceil(sqrt(n))]. Any distance requirement that's less than 1 means that you can't place any flags. You can't place more than ceil(sqrt(n)) flags because of the distance requirement, even if every element was somehow a peak (which isn't possible).
So one optimization you could make is that you need to only check for O(sqrt(n)) potentialK values. (You posted this as an answer to your own question.) That would bring the runtime down to O(n^(3/2)), since m is O(n), which is apparently fast enough to pass Codility's tests, but I think that the runtime can still be improved (and so can the correctness).
We can make one more observation:
There exists a positive integer i such that:
for every j, such that j is a positive integer less than i, we can place j flags that are at least j distance apart, and
for every j, such that j is a positive integer greater than i, we cannot place j flags that are at least j distance apart.
This enables us to use binary search:
import math
def does_distance_work(peaks, distance):
peak_count = 1
last_peak = peaks[0]
for i in range(len(peaks)):
if peaks[i] >= last_peak + distance:
peak_count += 1
last_peak = peaks[i]
return peak_count >= distance
def solution(A):
# Get the indices of the peaks.
peaks = []
for i in range(1, len(A) - 1):
if A[i] > A[i - 1] and A[i] > A[i + 1]:
peaks.append(i)
# Return 0 if no peaks.
if not peaks:
return 0
# Check maximum value.
if does_distance_work(peaks, math.ceil(math.sqrt(len(A)))):
return math.ceil(math.sqrt(len(A)))
# If neither of the above two checks apply, find the largest i (as specified above) using binary search.
low, high = 1, math.ceil(math.sqrt(len(A))) - 1
while low <= high:
mid = low + (high - low) // 2
mid_valid_distance = does_distance_work(peaks, mid)
mid_plus_one_valid_distance = does_distance_work(peaks, mid + 1)
if not mid_valid_distance:
high = mid
elif mid_plus_one_valid_distance:
low = mid + 1
else:
return mid
# If we've reached this line, something has gone wrong.
return -1
which recurses to a depth of O(log(sqrt(n)), with O(n) work for each iteration of our binary search. Then the final runtime is O(n * log(sqrt(n))), which should (and does) pass the performance tests.

I managed to optimize it as follows:
Since the distance between the individual flags has to be >= the number of flags, we know the maximum number of flags will be the root of the last element of peaks - the first element of peaks: sqrt(peaks[-1] - peaks[0])
I was then able to update the initial value of potentialK to
potentialK = math.ceil(math.sqrt(peaks[-1] - peaks[0]))
which should substantially reduce the number of iterations of the outer while loop.
import math
def solution(A):
peaks = []
distances = []
if len(A) == 1: return 0
for i in range(1, len(A) -1):
if A[i] > A[i-1] and A[i] > A[i+1]:
if len(distances) == 0:
distances.append(i)
else:
distances.append(i - peaks[-1])
peaks.append(i)
if len(peaks) == 0: return 0
if len(peaks) == 1: return 1
if len(peaks) == 2: return 2 if peaks[1] - peaks[0] >= 2 else 1
potentialK = math.ceil(math.sqrt(peaks[-1] - peaks[0]))
maxFlags = 0
while potentialK > maxFlags:
cumDistance = 0
flags = 0
firstFlag = True
for i in range(1, len(distances)):
cumDistance += distances[i]
if cumDistance >= potentialK:
if firstFlag:
flags += 2
firstFlag = False
else:
flags += 1
cumDistance = 0
if flags > maxFlags and flags == potentialK:
return flags
elif flags > maxFlags and potentialK > flags:
maxFlags = flags
potentialK -= 1
return maxFlags

Related

Number of subsets with a given sum . Recursion not tracing few branches of the tree

I have written this code in python and it is not yielding the right answer for the input wt[]=[2,3,5,6,8,10] in this order . It is giving right answer for few other combinations like wt[]=[3,2,6,10,8,5].I I have also tried tracing the recursive tree to debug accordingly but still cannot figure out why the code is not tracing some branches of the tree.
Kindly please help me figure out the problem.
Thank you!
n=6 #int(input())
m=10 #int(input())
wt=[2,3,5,6,8,10]
dp_table=[[-1 for i in range(n+1)]for j in range (m+1)]
total=[0]
def SS(m,n):
a=0
b=0
if m==0:
print(n-1)
total[0]=total[0]+1
return 0;
if n==0:
return 0;
else:
if wt[n-1]>m:
return (SS(m,n-1));
else:
if dp_table[m-wt[n-1]][n-1]==-1:
a=SS(m-wt[n-1],n-1) + wt[n-1]
if dp_table[m][n-1]==-1:
b=SS(m,n-1)
dp_table[m][n]=max(a,b)
return dp_table[m][n];
if m==0 or n==0:
print("Not Possible!")
if SS(m,n)==m:
print("Possible and the no of subsets with equal sum are: ",total[0])
else:
print("False")
You're storing results in dp_table but never using them (giving incorrect results). If the dp_table value of an entry isn't -1, you're ignoring the result (and assuming it's always 0).
Often it's better to do the cache-checks at the top of the function (or better yet, use functools.cache).
If you really want to keep the code structured as it is now, this will fix the issue:
def SS(m, n):
a = dp_table[m - wt[n - 1]][n - 1]
b = dp_table[m][n - 1]
if m == 0:
total[0] += 1
return 0
if n == 0:
return 0
else:
if wt[n - 1] > m:
dp_table[m][n] = b if b != -1 else SS(m, n - 1)
else:
if a == -1:
a = SS(m - wt[n - 1], n - 1)
a += wt[n - 1]
if b == -1:
b = SS(m, n - 1)
dp_table[m][n] = max(a, b)
return dp_table[m][n]
If you want to do the caching yourself, you can put cache checks at the top (a better approach), rather than putting a check (and an if-else statement) before every recursive call, like this:
def SS2(m, n):
if dp_table[m][n] != -1:
return dp_table[m][n]
if m == 0:
total[0] += 1
dp_table[m][n] = 0
elif n == 0:
dp_table[m][n] = 0
else:
if wt[n - 1] > m:
dp_table[m][n] = SS(m, n - 1)
else:
dp_table[m][n] = max(SS(m - wt[n - 1], n - 1) + wt[n - 1], SS(m, n - 1))
return dp_table[m][n]
But the most 'Pythonic' and least work alternative is to use the decorators built into the standard library. You can limit the total memory usage, and it might even be faster if your manual DP-table happens to have cache-unfriendly access patterns.
import functools
#functools.cache
def SS3(m, n):
if m == 0:
total[0] += 1
return 0
elif n == 0:
return 0
else:
if wt[n - 1] > m:
return SS(m, n - 1)
else:
return max(SS(m - wt[n - 1], n - 1) + wt[n - 1], SS(m, n - 1))

Describing QuickSort Algoritm

Im having a problem to understand this algorithm, how would you describe it, mostly the while loops. I understand that this i an iterative function, and the use of Hoare partition scheme. But what does the while loops do, and why does it use breaks.
def inplace_quick_sort_nonrecursive(S):
if len(S) < 2:
return S
stack = [] #initialize the stack
stack.append([0, len(S) - 1])
while len(stack) > 0: #loop till the stack is empty
low, high = stack.pop() #pop low and high indexes
pivot = S[(low + high) // 2] #create pivot, of any S[] except S[high]
i = low - 1 #Hoare partition
j = high + 1
while True:
while True: #while (S[++i] < pivot)
i += 1
if(S[i] >= pivot):
break
while True: #while(S[--j] < p)
j -= 1
if(S[j] <= pivot):
break
if (i >= j): #if indexes met or crossed, break
break
S[i], S[j] = S[j], S[i] #else swap the elements
if (j > low): #push the indexes into the stack
stack.append([low, j])
j += 1
if (high > j):
stack.append([j, high])

minimum jump to reach to end

Question statement:
Given array: [1,0,1,0,1,1,1,1,1,0,1,1,1,0]
output: minimum steps required to reach to end
Conditions:
step on 0 is exit
you can take max of 1 step or 2 steps at a time.
I have done using without DP, Is a DP solution present for this problem.
My code:
def minjump(arr):
n = len(arr)
if n <= 0 or arr[0] == 0:
return 0
index = jump = 0
while index < n:
if index == n-1:
return jump
if arr[index] == 0 and arr[index+1] == 0:
return 0
if arr[index] == 1:
if index < n-2 and arr[index+2] == 1:
jump +=1
index +=2
else:
jump += 1
index += 1
return jump
A naïve solution with no memoization simply recurses through the list taking both one or two steps and retaining the minimum steps needed:
def min_steps(array, current_count, current_position):
if current_position >= len(array): # Terminal condition if you've reached the end
return current_count
return (
min(
min_steps(array, current_count + 1, current_position + 1),
min_steps(array, current_count + 1, current_position + 2),
) # minimum count after taking one step or two
if array[current_position] # if current step is valid (non-zero)
else float("inf") # else, return float.infinity (fine since we're not imposing types on this prototype)
)
def minjump(arr):
result = min_steps(arr, 0, 0)
return 0 if result == float("inf") else result
You can solve a more general problem using dp, in the pseudocode bellow k represents the maximum number of steps you can jump:
int n = arr.Length
int dp[n]
for (i = 0 ; i < n; i++) {
int lowerBound = max(i - k, 0)
for (j = i - 1; j >= lowerBound; j--) {
if (arr[j] == 1 && (j == 0 || dp[j] > 0)) {
dp[i] = min(dp[i], 1 + dp[j])
}
}
}
return dp[n - 1]

Max Sub Array (non-adjacent)

I'm working on a HackerRank Max Array Sum problem. I see some simple and smart solutions but I wanna know why my code failing.
Here is my code. It takes next 4 elements from array and choose index0 or index1 and shift 2 or 3 elements.
if I can find max subset elements instead of sum of elements I can see my mistake.
HackerRank Problem Link
def Calc(arr):
result = 0
i = 0
while i < len(arr):
tempar = [i if i > 0 else 0 for i in arr[i:i+4]]
if len(tempar) == 4:
tmax = max(tempar[0] + tempar[2], tempar[0] + tempar[3], tempar[1] + tempar[3])
if tempar[0] + tempar[2] == tmax or tempar[0] + tempar[3] == tmax:
result += tempar[0]
i += 2
elif tempar[1] + tempar[3] == tmax:
result += tempar[1]
i+=3
if len(tempar) == 3:
if tempar[0] + tempar[2] > tempar[1]:
result += tempar[0] + tempar[2]
else:
result += tempar[1]
i+=3
if len(tempar) == 2:
result += max(tempar)
i+=2
if len(tempar) == 1:
result += tempar[0]
i+=1
return result
input()
ar = list(map(int, input().split()))
print(Calc(ar))
I didn't read your algorithm and the problem carefully, so I can't say whether your algorithm is right or not.
But it seems that your code allows choosing an empty set when all elements are negative. (tempar = [i if i > 0 else 0 for i in arr[i:i+4]])
For example, your program will output 0 for the input
5
-1 -1 -1 -1 -1
Your algorithm doesn't work.
for simple input
5
-1 -1 3 -2 5
I suggest you use the following approach:
def Calc(arr):
result = 0
prev_result=0
is_last_included=False
i = 0
while i < len(arr):
if arr[i] > 0:
if is_last_included==False:
prev_result=result
result=result+arr[i]
is_last_included=True
elif (prev_result+arr[i])>result:
temp=result
result=prev_result+arr[i]
prev_result=temp
is_last_included=True
else:
prev_result=result
is_last_included=False
i=i+1
return result

SPOJ prime1 wrong answer in python

I am getting Wrong Answer with the following code in python for SPOJ's PRIME1 problem at http://www.spoj.com/problems/PRIME1/. I have tested it on various testcases myself, but cannot find a failing testcase. Can someone please spot the problem in my code?
This code produces nothing for testcases that don't give any prime as output. First i pre-compute primes upto sqrt(1 billion) and then if the requested range has high value less than sqrt(1 billion), i simply print the primes from the pre-computed array, else i run sieve() with usePrimes = True, which uses the pre-computed primes to rule out the non-primes in the given range.
Thanks.
import math
from bisect import bisect_left
from bisect import bisect_right
primes = []
upper_bound = int(math.sqrt(1000000000)) + 1
usePrimes = False
printNL = False
T = 0
def sieve(lo, hi):
global usePrimes, primes, printNL
atleast_one = False
arr = range(lo,hi+1)
if usePrimes:
for p in primes:
if p*p > hi:
break
less = int(lo/p) * p
if less < lo:
less += p
while less <= hi:
arr[less - lo] = 0
less += p
for num in arr:
if num != 0:
atleast_one = True
if printNL:
print ''
printNL = False
print num
else:
atleast_one = True
for k in xrange(2,hi):
if k*k > hi:
break
if arr[k] == 0:
continue
less = k + k
while less <= hi:
arr[less] = 0
less += k
for num in arr:
if num > 1:
primes.append(num)
return atleast_one
def printPrimesInRange(lo,hi):
global primes, printNL
atleast_one = False
if hi < upper_bound:
for p in primes[bisect_left(primes,lo):bisect_right(primes,hi)]:
atleast_one = True
if printNL:
print ''
printNL = False
print p
else:
atleast_one = sieve(lo,hi)
return atleast_one
sieve(0, upper_bound)
usePrimes = True
T = input()
while T > 0:
lo, hi = [eval(y) for y in raw_input().split(' ')]
atleastOne = printPrimesInRange(lo,hi)
if atleastOne:
printNL = True
T -= 1
If you change the upper_bound to upper_bound = int(math.sqrt(1000000000)) + 123456, then it will pass all the test cases.
Now, can you figure why so? I'll leave it as an exercise to you.

Categories