I am writing a function to find all the hills and valleys in a given list. For instance, [1,0,0,0,1] returns 3 and [0,1,0,1,0] returns 5. [0,2,2,1,1,0,0] returns 3. If a number (or consecutive numbers with same values) are bigger or smaller than both of its neighbors, it is considered as a hill or a valley.
Below is my code:
def hill_and_vally(s):
if not s or len(s) < 2:
return 0
i = 0
count = 0
pre = None
while i < len(s):
if i == 0:
while s[i] == s[i+1]: # loop until value is different
i += 1
i += 1
if i < len(s): # check if it reaches the end
count += 1
pre = s[i-1] # track the previous value
elif i == len(s) - 1:
while s[i] == s[i-1]:
i -= 1
i -= 1
if i >= 0:
count += 1
break
else:
while s[i] == s[i+1]:
i += 1
i += 1
if s[i] > s[i-1] and pre > s[i-1]: # it is a valley
count += 1
elif s[i] < s[i-1] and pre < s[i-1]: # it is a hill
count += 1
pre = s[i-1]
return count
Can someone help me to improve the complexity to O(N). Or show me another way to do it with better complexity? Please show me some examples. Thanks in advance.
Here's how I would do it:
compute differences d between consecutive elements (remove 0s from result)
count the number of times the sign changes in d
return 2 plus that count (because there is a hill and a valley even in a monotonically increasing sequence)
In code:
def hill_and_vally(s):
d=[x1-x0 for x0,x1 in zip(s,s[1:]) if x1!=x0]
return 2+sum(d0*d1<0 for d0,d1 in zip(d,d[1:]))
of course it can be implemented with for loops and indexes, but zip and list comprehensions is more pythonic.
zip(s,s[1:]) is a common way to get pairs of adjacent elements in a list.
Tests:
>>> hill_and_vally([1,0,0,0,1])
3
>>> hill_and_vally([0,1,0,1,0])
5
>>> hill_and_vally([0,2,2,1,1,0,0])
3
Handling corner cases such as the reported [1,1,1,1] is left as an exercise :-)
I know that the question is quite old and already answered, but recently solved the same problem with just one linear scan:
def solution(arr):
prev = None
curr = None
total = 0
for n in arr:
if curr == None:
curr = n
else:
if n != curr:
if prev != None:
if (prev < curr and n < curr) or (prev > curr and n > curr):
total += 1
else:
prev = curr
total += 1
prev = curr
curr = n
if prev != curr:
total += 1
return total
It works well for all the inputs:
print(solution([1])) # 1
print(solution([1,2])) # 2
print(solution([1,1,1])) # 1
print(solution([1,2,1])) # 3
print(solution([1,2,6])) # 2
print(solution([1,2,3,4,4,3,4,4,5,6])) # 4
print(solution([-10,2,2,2,2])) # 2
print(solution([1,0,0,0,1])) # 3
print(solution([0,1,0,1,0])) # 5
print(solution([0,2,2,1,1,0,0])) # 3
What it does, it keeps track of previous downhill/uphill and increments total counter if another appropriate downhill/uphill is met.
Related
I basically want to put each letter to a different list so that the first letter goes in the first list until the kth letter. Then the k+1th letter should go in the k-1th list and the next to the k-2th list etc. The fact is that after the second loop the while enters in an infinite loop
def f(s,k):
l = [[] for i in range(k)]
i = 0
while i < len(s):
for j in range(0,k):
l[j].append(s[i])
print(l)
i += 1
for k in range(k-2,-1,-1):
l[k].append(s[i])
print(l)
i += 1
return l
Don't use the same variable k for the second iteration variable and your function parameter. When the second loop is done, k will be set to 0. The next iteration of the while loop will then do for j in range(0, 0):, which doesn't do anything, followed by for k in range(-2, -1, -1):, which also doesn't do anything. So i never gets incremented any more, and you get stuck in the infinite loop.
Change for k to for m and you won't get the infinite loop. But you'll get an IndexError when accessing s[i] if len(s) is not a multiple of 2 * k - 1.
It would be better to iterate over the characters in s.
def f(s,k):
l = [[] for i in range(k)]
i = 0
inc = 1
for c in s:
l[i].append(c)
i += inc
# Check if we need to reverse direction
if inc == 1 and i == k:
inc = -1
i = k - 2
elif inc == -1 and i == -1:
inc = 1
i = 0
return l
As #Barmar already stated, the reason why your algorithm does not work are the for-loops. So you effectively skip the first letter, at least.
Also the usage of k is problematic.
However his proposed algorithm doesn't work like you want it to work, though.
In the end this is what #Barmar had in mind:
def f(string, k):
result = [[] for _ in range(k)]
bin_index = -1
inc = 1
for i in range(len(string)):
bin_index += inc
# reverse by setting the bin_index and changing the increment-step
if bin_index == k:
bin_index = (k - 1) - 1
inc = -1
# going forward again
if bin_index == 0 and i != 0:
inc = 1
result[bin_index].append(string[i])
return result
#Barmar:
You have a bug in your program when i returns from the back-pass. It will not be set to another bin-index so that always the character on index k+1 and k+2 (or multiples of them) will be in the same bin (the first list). But they should be in two different bins. So this would get rid of the bug:
def f(s, k):
l = [[] for i in range(k)]
i = 0
inc = 1
for c in s:
l[i].append(c)
i += inc
# Check if we need to reverse direction
if inc == 1 and i == k:
inc = -1
i = k - 2
elif inc == -1 and i == -1:
inc = 1
i = 1 # this should be 1 not 0
return l
I'm working on a function that will accept a list of integers and a "run length" as arguments. The goal is to return a list of the indices of the first element of each "run" that increments or decrements by 1 for the given run length
e.g.
values=[1,2,3,5,5,9,8,9,10,11], run_length=3 would return [0,6,7]
Where I've gotten so far, and can't quite get what I'm messing up:
def solution(values, run_length):
count = 0
previous_int = 99
marker = 0
accumulator = 0
locations = []
for i in values:
if i == (previous_int + 1):
accumulator += 1
if accumulator == (run_length - 1):
locations.append(marker)
accumulator -= 1
marker += 1
else:
marker = count
accumulator = 0
count += 1
previous_int = i
return(locations)
values=[1,2,3,5,10,9,8,9,10,11,7,8,7,6,5,4,3,2,1]
run_length=3
result = solution(values, run_length)
print(result)
Will work for some test cases but not all, like the above will only return [0,6,7] when there are clearly more.
You need to make a second pass for handling a decreasing sequence, then merge the two lists together. This code is somewhat long for my liking, but including all the code is necessary because some of the changes are subtle.
def solution(values, run_length):
increasing_locations = []
decreasing_locations = []
for differential, location_storage in [(1, increasing_locations), (-1, decreasing_locations)]:
count = 0
previous_int = 99
marker = 0
accumulator = 0
for i in values:
if i == (previous_int + differential):
accumulator += 1
if accumulator == (run_length - 1):
location_storage.append(marker)
accumulator -= 1
marker += 1
else:
marker = count
accumulator = 0
count += 1
previous_int = i
merged_result = []
increase_location_index = 0
decrease_location_index = 0
while len(merged_result) < len(increasing_locations) + len(decreasing_locations):
if decrease_location_index == len(decreasing_locations):
merged_result.append(increasing_locations[increase_location_index])
increase_location_index += 1
elif increase_location_index == len(increasing_locations):
merged_result.append(decreasing_locations[decrease_location_index])
decrease_location_index += 1
elif decreasing_locations[decrease_location_index] < increasing_locations[increase_location_index]:
merged_result.append(decreasing_locations[decrease_location_index])
decrease_location_index += 1
else:
merged_result.append(increasing_locations[increase_location_index])
increase_location_index += 1
return merged_result
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
Hi I am attempting to make a Merge Sort algorithm for fun, and do not want to just copy code off the internet. Which is why I have not referred to another person's Stack Overflow thread. So unless the thread has the same issue, please do not direct me towards that. I am using 2 functions, merge and merge sort. Merge sort is recursive, I intend for it to split a list in half, and then sort each half. The merge algorithm should then take the two sorted lists and return a new list, which is just the two lists combined and sorted. Eventually the code should return a fully sorted list. Below is my code, and if you run it you will see that I am getting an empty list returned, which makes no sense to me. Any help would be greatly appreciated. Thanks :)
def merge(left, right):
resultList = []
leastRight = 0
leastLeft = 0
if len(left) >= len(right):
for i in range(len(left)-1):
counter = 0
for j in range(len(right)-1):
counter += 1
if right[counter % (len(right)-1)] <= right[j]:
leastRight = right[counter % (len(right)-1)]
print("leastRight if",leastRight)
else:
leastRight = right[j]
print("leastRight else", leastRight)
right.remove(leastRight)
if left[i] <= leastRight:
resultList.append(left[i])
else:
resultList.append(leastRight)
else:
for i in range(len(right)-1):
counter = 0
for j in range(len(left)-1):
counter += 1
if left[counter % (len(left)-1)] <= left[j]:
leastLeft = left[counter%(len(left)-1)]
print("leastLeft if", leastLeft)
else:
leastLeft = left[j]
print("leastLeft else", leastLeft)
left.remove(leastLeft)
if right[i] <= leastLeft:
resultList.append(right[i])
else:
resultList.append(leastLeft)
return (resultList)
def mergeSort(alist):
print("alist", alist)
if len(alist) > 2:
middleIndex = len(alist) // 2
sortedLeft = mergeSort(alist[:middleIndex])
print("left", sortedLeft)
sortedRight = mergeSort(alist[middleIndex:])
print("right", sortedRight)
result = merge(sortedLeft, sortedRight)
print("result", result)
else:
result = alist
return (result)
print("mergesort", mergeSort([6, 0, 2, 8, 9, 1]))
Sorry, but approach of your merge function is not usable at all. Principle of choosing the smallest element is too tangled here and causes errors (I just saw it cannot merge [6] and [0,2]). Have you read classical description of merging?
In mergesort (we cannot omit treatment of length 2 lists):
if len(alist) >= 2:
Quick-made working implementation of merge routine.
def merge(left, right):
resultlist = []
llen = len(left)
rlen = len(right)
il = 0
ir = 0
while il < llen and ir < rlen: #while both list are not exhausted
if left[il] <= right[ir]: #choose the smallest current item
resultlist.append(left[il])
il += 1
else:
resultlist.append(right[ir])
ir += 1
#now treat the rest tail
while il < llen:
resultlist.append(left[il])
il += 1
while ir < rlen:
resultlist.append(right[ir])
ir += 1
return resultlist
result
>>> mergesort [0, 1, 2, 6, 8, 9]
Note it would be wise to make resultlist of known length for speed.
def merge(left, right):
llen = len(left)
rlen = len(right)
resultlist = [0]*(llen+rlen) # we know data type and full length
il = 0
ir = 0
k = 0
while il < llen and ir < rlen:
if left[il] <= right[ir]:
resultlist[k] = left[il]
il += 1
else:
resultlist[k] = right[ir]
ir += 1
k += 1
while il < llen:
resultlist[k] = left[il]
il += 1
k += 1
while ir < rlen:
resultlist[k] = right[ir]
ir += 1
k += 1
return resultlist
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