How to fix the recursive codeso that it has the same asymptotic running time as the iterative version (but is still recursive) - data-structures

I was struggling to figure out what changes need to be made to the recursive version of my code below so that the asymptotic run time just about matches the iterative version. Of course, the recursive version of the code still needs to be recursive, but I was stuck on how I should approach cutting down the run time on the recursion.
def binarySearch(alist, item): //ITERATIVE VERSION
first = 0
last = len(alist)-1
found = False
while first<=last and not found:
midpoint = (first + last)/2
if alist[midpoint] == item:
found = True
else:
if item < alist[midpoint]:
last = midpoint-1
else:
first = midpoint+1
return found
def binarySearch(alist, item): //RECURSIVE VERSION
if len(alist) == 0:
return False
else:
midpoint = len(alist)/2
if alist[midpoint]==item:
return True
else:
if item<alist[midpoint]:
return binarySearch(alist[:midpoint],item)
else:
return binarySearch(alist[midpoint+1:],item)
Tried to replicate my function recursively, but the asymptotic running time was much slower than the iterative version.

Your recursive version creates new lists -- which is detrimental to both time and space complexity.
So instead of slicing alist, use first and last like you did in the iterative version:
def binarySearch(alist, item, first=0, last=None):
if last is None:
last = len(alist) - 1
if last < first:
return False
else:
midpoint = (start + end) // 2
if alist[midpoint] == item:
return True
elif item < alist[midpoint]:
return binarySearch(alist, item, first, midpoint - 1)
else:
return binarySearch(alist, item, midpoint + 1, last)

Related

Implementing iterative solution in a functionally recursive way with memoization

I am trying to solve the following problem on leetcode: Coin Change 2
Input: amount = 5, coins = [1, 2,5]
Output: 4 Explanation: there are four ways to make up the amount:
5=5
5=2+2+1
5=2+1+1+1
5=1+1+1+1+1
I am trying to implement an iterative solution which essentially simulates/mimic recursion using stack. I have managed to implement it and the solution works, but it exceeds time limit.
I have noticed that the recursive solutions make use of memoization to optimize. I would like to incorporate that in my iterative solution as well, but I am lost on how to proceed.
My solution so far:
# stack to simulate recursion
stack = []
# add starting indexes and sum to stack
#Tuple(x,y) where x is sum, y is index of the coins array input
for i in range(0, len(coins)):
if coins[i]<=amount:
stack.append((coins[i], i))
result = 0
while len(stack)!=0:
c = stack.pop()
currentsum = c[0]
currentindex = c[1]
# can't explore further
if currentsum >amount:
continue
# condition met, increment result
if currentsum == amount:
result = result+1
continue
# add coin at current index to sum if doesn't exceed amount (append call to stack)
if (currentsum+coins[currentindex])<=amount:
stack.append((currentsum+coins[currentindex], currentindex))
#skip coin at current index (append call to stack)
if (currentindex+1)<=len(coins)-1:
stack.append((currentsum, currentindex+1))
return result
I have tried using dictionary to record appends to the stack as follows:
#if the call has not already happened, add to dictionary
if dictionary.get((currentsum, currentindex+1), None) == None:
stack.append((currentsum, currentindex+1))
dictionary[currentsum, currentindex+1)] = 'visited'
Example, if call (2,1) of sum = 2 and coin-array-index = 1 is made, I append it to dictionary. If the same call is encountered again, I don't append it again. However, it does not work as different combinations can have same sum and index.
Is there anyway I can incorporate memoization in my iterative solution above. I want to do it in a way such that it is functionally same as the recursive solution.
I have managed to figure out the solution. Essentially, I used post order traversal and used a state variable to record the stage of recursion the current call is in. Using the stage, I have managed to go bottom up after going top down.
The solution I came up with is as follows:
def change(self, amount: int, coins: List[int]) -> int:
if amount<=0:
return 1
if len(coins) == 0:
return 0
d= dict()
#currentsum, index, instruction
coins.sort(reverse=True)
stack = [(0, 0, 'ENTER')]
calls = 0
while len(stack)!=0:
currentsum, index, instruction = stack.pop()
if currentsum == amount:
d[(currentsum, index)] = 1
continue
elif instruction == 'ENTER':
stack.append((currentsum, index, 'EXIT'))
if (index+1)<=(len(coins)-1):
if d.get((currentsum, index+1), None) == None:
stack.append((currentsum, index+1, 'ENTER'))
newsum = currentsum + coins[index]
if newsum<=amount:
if d.get((newsum, index), None) == None:
stack.append((newsum, index, 'ENTER'))
elif instruction == 'EXIT':
newsum = currentsum + coins[index]
left = 0 if d.get((newsum, index), None) == None else d.get((newsum, index))
right = 0 if d.get((currentsum, index+1), None) == None else d.get((currentsum, index+1))
d[(currentsum, index)] = left+right
calls = calls+1
print(calls)
return d[(0,0)]

algo class question: compare n no. of sequence showing their comparison which leads to the particular sequence

Whenever you compare 3 no. it end up in 6 results and similarly 4 no it goes for 24 no. making permutation of no. of inputs.
The task is to compare n no. of sequence showing their comparison which leads to the particular sequence
For example your input is a,b,c
If a<b
If b<c
Abc
Else
If a<c
Acb
Else a>c
cab
Else b>c
Cba
Else
If a<c
Bac
Else
Bca
Else
Cba
The task is to print all the comparisons which took place to lead that sequence for n no.s and
confirm that there is no duplication.
Here is Python code that outputs valid Python code to assign to answer the sorted values.
The sorting algorithm here is mergesort. Which is not going to give the smallest possible decision tree, but it will be pretty good.
Here is Python code that outputs valid Python code to assign to answer the sorted values.
The sorting algorithm here is mergesort. Which is not going to give the smallest possible decision tree, but it will be pretty good.
#! /usr/bin/env python
import sys
class Tree:
def __init__ (self, node_type, value1=None, value2=None, value3=None):
self.node_type = node_type
self.value1 = value1
self.value2 = value2
self.value3 = value3
def output (self, indent='', is_continue=False):
rows = []
if self.node_type == 'answer':
rows.append("{}answer = [{}]".format(indent, ', '.join(self.value1)))
elif self.node_type == 'comparison':
if is_continue:
rows.append('{}elif {} < {}:'.format(indent, self.value1[0], self.value1[1]))
else:
rows.append('{}if {} < {}:'.format(indent, self.value1[0], self.value1[1]))
rows = rows + self.value2.output(indent + ' ')
if self.value3.node_type == 'answer':
rows.append('{}else:'.format(indent))
rows = rows + self.value3.output(indent + ' ')
else:
rows = rows + self.value3.output(indent, True)
return rows
# This call captures a state in the merging.
def _merge_tree (chains, first=None, second=None, output=None):
if first is None and second is None and output is None:
if len(chains) < 2:
return Tree('answer', chains[0])
else:
return _merge_tree(chains[2:], chains[0], chains[1], [])
elif first is None:
return _merge_tree(chains + [output])
elif len(first) == 0:
return _merge_tree(chains, second, None, output)
elif second is None:
return _merge_tree(chains + [output + first])
elif len(second) < len(first):
return _merge_tree(chains, second, first, output)
else:
subtree1 = _merge_tree(chains, first[1:], second, output + [first[0]])
subtree2 = _merge_tree(chains, first, second[1:], output + [second[0]])
return Tree('comparison', [first[0], second[0]], subtree1, subtree2)
def merge_tree (variables):
# Turn the list into a list of 1 element merges.
return _merge_tree([[x] for x in variables])
# This captures the moment when you're about to compare the next
# variable with the already sorted variable at position 'position'.
def insertion_tree (variables, prev_sorted=None, current_variable=None, position=None):
if prev_sorted is None:
prev_sorted = []
if current_variable is None:
if len(variables) == 0:
return Tree('answer', prev_sorted)
else:
return insertion_tree(variables[1:], prev_sorted, variables[0], len(prev_sorted))
elif position < 1:
return insertion_tree(variables, [current_variable] + prev_sorted)
else:
position = position - 1
subtree1 = insertion_tree(variables, prev_sorted, current_variable, position)
subtree2 = insertion_tree(variables, prev_sorted[0:position] + [current_variable] + prev_sorted[position:])
return Tree('comparison', [current_variable, prev_sorted[position]], subtree1, subtree2)
args = ['a', 'b', 'c']
if 1 < len(sys.argv):
args = sys.argv[1:]
for line in merge_tree(args).output():
print(line)
For giggles and grins, you can get insertion sort by switching the final call to merge_tree to insertion_tree.
In principle you could repeat the exercise for any sort algorithm, but it gets really tricky, really fast. (For quicksort you have to do continuation passing. For heapsort and bubble sort you have to insert fancy logic to only consider parts of the decision tree that you could actually arrive at. It is a fun exercise if you want to engage in it.)

Python Codility Frog River One time complexity

So this is another approach to probably well-known codility platform, task about frog crossing the river. And sorry if this question is asked in bad manner, this is my first post here.
The goal is to find the earliest time when the frog can jump to the other side of the river.
For example, given X = 5 and array A such that:
A[0] = 1
A[1] = 3
A[2] = 1
A[3] = 4
A[4] = 2
A[5] = 3
A[6] = 5
A[7] = 4
the function should return 6.
Example test: (5, [1, 3, 1, 4, 2, 3, 5, 4])
Full task content:
https://app.codility.com/programmers/lessons/4-counting_elements/frog_river_one/
So that was my first obvious approach:
def solution(X, A):
lista = list(range(1, X + 1))
if X < 1 or len(A) < 1:
return -1
found = -1
for element in lista:
if element in A:
if A.index(element) > found:
found = A.index(element)
else: return -1
return found
X = 5
A = [1,2,4,5,3]
solution(X,A)
This solution is 100% correct and gets 0% in performance tests.
So I thought less lines + list comprehension will get better score:
def solution(X, A):
if X < 1 or len(A) < 1:
return -1
try:
found = max([ A.index(element) for element in range(1, X + 1) ])
except ValueError:
return -1
return found
X = 5
A = [1,2,4,5,3]
solution(X,A)
This one also works and has 0% performance but it's faster anyway.
I also found solution by deanalvero (https://github.com/deanalvero/codility/blob/master/python/lesson02/FrogRiverOne.py):
def solution(X, A):
# write your code in Python 2.6
frog, leaves = 0, [False] * (X)
for minute, leaf in enumerate(A):
if leaf <= X:
leaves[leaf - 1] = True
while leaves[frog]:
frog += 1
if frog == X: return minute
return -1
This solution gets 100% in correctness and performance tests.
My question arises probably because I don't quite understand this time complexity thing. Please tell me how the last solution is better from my second solution? It has a while loop inside for loop! It should be slow but it's not.
Here is a solution in which you would get 100% in both correctness and performance.
def solution(X, A):
i = 0
dict_temp = {}
while i < len(A):
dict_temp[A[i]] = i
if len(dict_temp) == X:
return i
i += 1
return -1
The answer already been told, but I'll add an optional solution that i think might help you understand:
def save_frog(x, arr):
# creating the steps the frog should make
steps = set([i for i in range(1, x + 1)])
# creating the steps the frog already did
froggy_steps = set()
for index, leaf in enumerate(arr):
froggy_steps.add(leaf)
if froggy_steps == steps:
return index
return -1
I think I got the best performance using set()
take a look at the performance test runtime seconds and compare them with yours
def solution(X, A):
positions = set()
seconds = 0
for i in range(0, len(A)):
if A[i] not in positions and A[i] <= X:
positions.add(A[i])
seconds = i
if len(positions) == X:
return seconds
return -1
The amount of nested loops doesn't directly tell you anything about the time complexity. Let n be the length of the input array. The inside of the while-loop needs in average O(1) time, although its worst case time complexity is O(n). The fast solution uses a boolean array leaves where at every index it has the value true if there is a leaf and false otherwise. The inside of the while-loop during the entire algotihm is excetuded no more than n times. The outer for-loop is also executed only n times. This means the time complexity of the algorithm is O(n).
The key is that both of your initial solutions are quadratic. They involve O(n) inner scans for each of the parent elements (resulting in O(n**2)).
The fast solution initially appears to suffer the same fate as it's obvious it contains a loop within a loop. But the inner while loop does not get fully scanned for each 'leaf'. Take a look at where 'frog' gets initialized and you'll note that the while loop effectively picks up where it left off for each leaf.
Here is my 100% solution that considers the sum of numeric progression.
def solution(X, A):
covered = [False] * (X+1)
n = len(A)
Sx = ((1+X)*X)/2 # sum of the numeric progression
for i in range(n):
if(not covered[A[i]]):
Sx -= A[i]
covered[A[i]] = True
if (Sx==0):
return i
return -1
Optimized solution from #sphoenix, no need to compare two sets, it's not really good.
def solution(X, A):
found = set()
for pos, i in enumerate(A, 0):
if i <= X:
found.add(i)
if len(found) == X:
return pos
return -1
And one more optimized solution for binary array
def solution(X, A):
steps, leaves = X, [False] * X
for minute, leaf in enumerate(A, 0):
if not leaves[leaf - 1]:
leaves[leaf - 1] = True
steps -= 1
if 0 == steps:
return minute
return -1
The last one is better, less resources. set consumes more resources compared to binary list (memory and CPU).
def solution(X, A):
# if there are not enough items in the list
if X > len(A):
return -1
# else check all items
else:
d = {}
for i, leaf in enumerate(A):
d[leaf] = i
if len(d) == X:
return i
# if all else fails
return -1
I tried to use as much simple instruction as possible.
def solution(X, A):
if (X > len(A)): # check for no answer simple
return -1
elif(X == 1): # check for single element
return 0
else:
std_set = {i for i in range(1,X+1)} # list of standard order
this_set = set(A) # set of unique element in list
if(sum(std_set) > sum(this_set)): # check for no answer complex
return -1
else:
for i in range(0, len(A) - 1):
if std_set:
if(A[i] in std_set):
std_set.remove(A[i]) # remove each element in standard set
if not std_set: # if all removed, return last filled position
return(i)
I guess this code might not fulfill runtime but it the simplest I could think of
I am using OrderedDict from collections and sum of first n numbers to check the frog will be able to cross or not.
def solution(X, A):
from collections import OrderedDict as od
if sum(set(A))!=(X*(X+1))//2:
return -1
k=list(od.fromkeys(A).keys())[-1]
for x,y in enumerate(A):
if y==k:
return x
This code gives 100% for correctness and performance, runs in O(N)
def solution(x, a):
# write your code in Python 3.6
# initialize all positions to zero
# i.e. if x = 2; x + 1 = 3
# x_positions = [0,1,2]
x_positions = [0] * (x + 1)
min_time = -1
for k in range(len(a)):
# since we are looking for min time, ensure that you only
# count the positions that matter
if a[k] <= x and x_positions[a[k]] == 0:
x_positions[a[k]] += 1
min_time = k
# ensure that all positions are available for the frog to jump
if sum(x_positions) == x:
return min_time
return -1
100% performance using sets
def solution(X, A):
positions = set()
for i in range(len(A)):
if A[i] not in positions:
positions.add(A[i])
if len(positions) == X:
return i
return -1

Method is slower after memoization

I'm working on the following algorithm problem on Leetcode:
Given a binary tree, determine if it is height-balanced. For this
problem, a height-balanced binary tree is defined as a binary tree in
which the depth of the two subtrees of every node never differ by more
than 1.
Here's the link: https://leetcode.com/problems/balanced-binary-tree/
I've written two solutions--both of which pass Leetcode's tests. The first one should be more expensive than the second one because it repeatedly scans each subtree's nodes to calculate the heights for each node. The second solution should collect all the node heights into a cache so that the calculation doesn't have to repeat. However, it turns out my second solution is actually slower than my first one, and I have no idea why. Any input?
Solution 1:
def is_balanced(root) #runs at 139 ms
return true if root.nil?
left_height = height(root.left)
right_height = height(root.right)
if !equal?(left_height, right_height)
return false
else
is_balanced(root.left) && is_balanced(root.right)
end
end
def height(node)
return 0 if node.nil?
[height(node.left), height(node.right)].max + 1
end
def equal?(num1, num2)
return true if (num1-num2).abs <= 1
false
end
Solution 2: (edited to include cobaltsoda's suggestion)
#memo = {}
def is_balanced(root)
#memo = {}
return true if root.nil?
left_height = height(root.left)
right_height = height(root.right)
if !equal?(left_height, right_height)
return false
else
is_balanced(root.left) && is_balanced(root.right)
end
end
def height(node)
#memo[node] = 0 if node.nil?
#memo[node] ||= [height(node.left), height(node.right)].max + 1
end
def equal?(num1, num2)
return true if (num1-num2).abs <= 1
false
end
Because the second solution uses memoization, shouldn't it cut out the redundancies and lower the time expenditure of the program? Not sure what I'm missing.
Also, with memoization this goes from O(NlogN) algorithm to O(N) correct?

Knapsack Solution using Recursion

I am trying to solve Knapsack problem using recursion in Scala but my requirement is to show which items are chosen to keep in Knapsack. availableMoney indicates the knapsack size.
My code is as follows:
def knapsack(availableMoney: Int,wt:List[Int],value :List[Int] ,n:Int) : Int= {
if(n == 0 || availableMoney == 0)
return 0
if (wt(n - 1) > availableMoney) {
return knapsack(availableMoney,wt,value, n - 1)
}
else {
var element1 = value(n-1) + knapsack(availableMoney- wt(n-1), wt, value, n-1)
var element2 = knapsack(availableMoney, wt, value, n-1)
return max(element1,element2);
}
}
How to know which items are picked to keep in Knapsack ?
In your code, you already know if you chose the current element or not.
If you picked element1 (it is higher than element2), then the last (index=n-1) element was picked. Otherewise, you didn't.
So, you can add another output argument to be returned from the recursive call, that will indicate the chosen elements.
And you will need to modify all return ... to also take care of it:
return 0 will become return (0,[])
return knapsack(availableMoney,wt,value, n - 1) stays as is.
return max(element1,element2) will return (element1, list1.add(n-1)) or (element2, list2), depending on which is higher, element or element2.
Also, if you want to implement is as Dynamic programming, this question discusses how to return the elements and not only values:
How to find which elements are in the bag, using Knapsack Algorithm [and not only the bag's value]?
Please consider accepting amit's solution as the answer, I just want to supplement additional note on top of his solution here.
In this solution, I will also cater the case when the knapsack solution is not unique.
As pointed out by amit, it is straight-forward to modify your code to keep track of the elements in the knapsack. Your method should return a tuple instead of the knapsack value, where the first entry is the "max value" of the knapsack, and the second entry is a list of list of elements in the knapsack that represents of the combinations of items in the knapsack.
The first if corresponds to the termination condition of the recursion, and there is only one possible combination in the knapsack - knapsack without any element.
If the condition of the second if is true, then item n - 1 cannot be picked so we recur to the next item.
On the other hand, if the weight of item n - 1 is less than availableMoney, then we can either pick item n - 1 in the construction (this corresponds to element1), or leave item n - 1 out in the construction. The returned solution should then be the better one of the two, or merging them together if they give the same value.
Below is a modified version of code for your reference:
def knapsack(availableMoney: Int, wt: List[Int], value: List[Int], n: Int): (Int, List[List[Int]]) = {
if (n == 0 || availableMoney == 0)
return (0, List[List[Int]](List[Int]()))
if (wt(n - 1) > availableMoney) {
return knapsack(availableMoney, wt, value, n - 1)
} else {
val recur = knapsack(availableMoney - wt(n - 1), wt, value, n - 1)
val element1 = (recur._1 + value(n - 1), for (e <- recur._2) yield {e :+ wt(n - 1)})
val element2 = knapsack(availableMoney, wt, value, n - 1)
if (element1._1 > element2._1)
return element1
else if (element1._1 < element2._1)
return element2
else
return (element1._1, element1._2 ::: element2._2)
}
}

Resources