Why is recursion not the same as dynamic programming in some cases? - python

I am working on dynamic programming and I came across a problem to find unique paths to reach a point 'b' from point 'a' in a m x n grid. You can only move right or down. My question is why is the following recursive approach much slower than the dynamic one described later?
Recursion:
def upaths(m,n):
count = [0]
upathsR(m,n, count)
return count[0]
def upathsR(m,n,c):
# Increase count when you reach a unique path
# When you reach 1 there is only one way to get to an end (optimization)
if m==1 or n==1:
c[0]+=1
return
if m > 1:
upathsR(m-1,n,c)
if n > 1:
upathsR(m,n-1,c)
Dynamic:
def upaths(m, n):
count = [[1 for x in range(n)] for x in range(m)]
for i in range(1, m):
for j in range(1, n):
count[i][j] = count[i][j-1] + count[i-1][j]
return count[-1][-1]
Usually recursion has repetitive calls that can be memoized but in this case I see unique calls, Even so Recursion is much slower. Can someone explain why..
Following suggestions from the answers, it worked faster. And the calls aren't unique.
New Recursive Approach:
def upaths(m,n):
d = dict()
return upathsR(m,n,d)
def upathsR(m,n,d):
if (m,n) in d:
return d[(m,n)]
if m==1 or n==1:
return 1
d[(m,n)] = upathsR(m-1,n,d)+upathsR(m,n-1,d)
return d[(m,n)]

Related

Non-recursive algorithm for all permutations using a stack

I'm trying to solve the following exercise from "Data Structures and Algorithms in Python" by Goodrich et al.:
"Describe a nonrecursive algorithm for enumerating all permutations of the numbers {1, 2, ..., n} using an explicit stack."
The hint for this excercise is to use "[...] a stack to reduce the problem to that of enumerating all permutations of the numbers {1, 2, ..., n-1}."
My first idea was to push n onto the stack, then n - 1, ..., until I reach 1. Now, I'd save all permutations of 1 ("1") in a list. The next step would be to pop the next value from the stack ("2") and to insert it into every possible position for every permutation that is in my list so far. As a result, when the stack is empty again, there would be a list containing all permutations of the numbers 1 to n.
However, I doubt that this is what is meant by the hint (I'm using an extra list, which is not mentioned in the hint, for example). What other approaches are there that fit the hint better?
Edit: This is my implementation of the solution I've described
def permutations(n):
s = MyStack()
perms = [[]]
while n > 0:
s.push(n)
n -= 1
total = 1
factor = 1
while not s.is_empty():
val = s.pop()
total *= factor
factor += 1
prev = perms
perms = [None] * total
idx = 0
for perm in prev:
for pos in range(len(perm) + 1):
perms[idx] = [*perm[:pos], val, *perm[pos:]]
idx += 1
for perm in perms:
print(perm)
Edit 2: explicit stack implementation of #btilly‘s answer
def permutations_explicit_stack(items):
items = list(items)
n = len(items)
s = MyStack()
i = 0
perm = []
while i < n:
perm.append(items.pop(0))
s.push(i)
i = 0
if len(items) == 0:
print(perm)
while i == len(items) and s:
i = s.pop()
items.append(perm.pop())
i += 1
Better hint. Write a recursive function to calculate permutations. That uses an implicit stack. Then figure out how to use an explicit stack to do the same thing that the recursive function did, but without having to use recursion. The point of the exercise being to understand what your programming language did for you with recursion.
The reason why this matters is that, as you learn data structures, you'll encounter more and more situations where recursion really is easier. But you'll also encounter situations where rewriting it with an explicit stack allows you to understand and modify the algorithm in a way that straight recursion hid from you. A classic example being that depth first search is easiest to write recursively. But if you rewrite with a stack, then switch the stack for a queue, you get breadth first search. And switch out the queue for a priority queue, and you've got A* search.
Here is a recursive permutation function to get you started.
def permutations (items):
_permutations([], list(items))
def _permutations(perm, items):
if 0 == len(items):
print(perm)
else:
for i in range(len(items)):
perm.append(items.pop(0))
_permutations(perm, items)
items.append(perm.pop())
What information is being kept on the implicit function call stack? And how can you store the same information in an explicit stack? How can you rewrite this function without any recursion?

Python Code Optimization Problem (Lintcode Problem 1886 Moving Targets)

I have been working on a problem on https://www.lintcode.com/ and I have ran into a problem while doing one of the questions. The problem requires me to write a function with two parameters. A list of nums and a target num. You have to take all instances of the target from the list and move them to the front of the original list and the function cannot have a return value. The length of the list is between 1 and 1000000. You also have to do it within a time limit, which is around 400 milliseconds. I can solve the problem, I can't pass the last test case where the length of the list is 1000000. Does anyone know how I can make my code faster?
Original Problem Description for anyone who still isn't clear:
Current Code:
def MoveTarget(nums, target):
if len(set(nums)) == 1:
return nums
index = [i for i in range(len(nums)) if nums[i] == target]
for i in index:
nums.insert(0, nums.pop(i))
It works if you do:
def MoveTarget(nums, target):
count = 0
left, right = len(nums) - 1, len(nums) - 1
while left >= 0:
if nums[left] != target:
nums[right] = nums[left]
right -= 1
else:
count += 1
left -= 1
for i in range(count):
nums[i] = target
but I was wondering if there was another, less complicated way.
Here is a simple and relatively efficient implementation:
def MoveTarget(nums, target):
n = nums.count(target)
nums[:] = [target] * n + [e for e in nums if e != target]
It creates a new list with the n target values in the front and append all the other values that are not target. The input list nums is mutated thanks to the expression nums[:] = ....
The solution run in linear time as opposed to the previously proposed implementations (running in quadratic time). Indeed, insert runs in linear time in CPython.
Your code uses 2 loops. One in:
index = [i for i in range(len(nums)) if nums[i] == target]
And one in:
for i in index:
nums.insert(0, nums.pop(i))
Instead, you can combine finding the target and moving it to the front of array with only one loop, which will greatly reduce the execution time:
def MoveTarget(nums, target):
if len(set(nums)) == 1:
return nums
for num in nums:
if num == target:
nums.insert(0, nums.pop(num))

simplify code in depth first search function call

I am one of the many hanging around stack overflow for knowledge and help, especially those who are out of school already. Much of my CS knowledge is learnt from this excellent web. Sometimes my question can get quite silly. Please forgive me as a newbie.
I am working on the Largest Divisible Subset problem on leetcode. There are many good solutions there, but I try to solve with my own thought first. My strategy is turning this problem into a combination problem and find the largest one who meets the divisible requirement.I use depth-first search method and isDivisible to create such a combinations. All the combinations I found meet the divisible requirement.
Here is how I would code to conduct all possible combinations of a given sequence.
def combinations(nums, path, res):
if not nums:
res.append(path)
for i in range(len(nums)):
combinations(nums[i+1:], path+[nums[i]], res)
Following is my code to create a combination of all possible divisible subsets. The code is almost exactly the same as the above code, except that that I add isDivisible to determine whether or not to add the nums[i] to the path.
def isDivisible(num, list_):
return all([num%item==0 or item%num==0 for item in list_])
def dfs(nums, path, res):
if not nums:
res.append(path)
return
for i in range(len(nums)):
# if not path or isDivisible(nums[i], path):
# path = path + [nums[i]]
# dfs(nums[i+1:], path , res)
dfs(nums[i+1:], path + ([nums[i]] if not path or isDivisible(nums[i], path) else []), res)
path = []
res = []
dfs(nums, [], res)
return sorted(res, key=len)
It works fine (almost got accepted but exceeded the time limit for large input) because of the performance of dfs. My question here is how I can simplify the last line of code in dfs by moving ([nums[i]] if not path or isDivisible(nums[i], path) else []) out of the function call, which is too bulky inside a function call. I tried to use the three lines in the comment to replace the last line of code, but it failed because path will propagate every nums[i] who meets the condition to next dfs. Could you please teach me to simplify the code and give some general suggestions. Thank you very much.
Not sure about your method, check first to see if it would get accepted.
Here is a bit simpler to implement solution (which I guess it would be one of Stefan's suggested methods):
class Solution:
def largestDivisibleSubset(self, nums):
hashset = {-1: set()}
for num in sorted(nums):
hashset[num] = max((hashset[k] for k in hashset if num % k == 0), key=len) | {num}
return list(max(hashset.values(), key=len))
Here is LeetCode's DP solution with comments:
class Solution(object):
def largestDivisibleSubset(self, nums):
"""
:type nums: List[int]
:rtype: List[int]
"""
if len(nums) == 0:
return []
# important step !
nums.sort()
# The container that keep the size of the largest divisible subset that ends with X_i
# dp[i] corresponds to len(EDS(X_i))
dp = [0] * (len(nums))
""" Build the dynamic programming matrix/vector """
for i, num in enumerate(nums):
maxSubsetSize = 0
for k in range(0, i):
if nums[i] % nums[k] == 0:
maxSubsetSize = max(maxSubsetSize, dp[k])
maxSubsetSize += 1
dp[i] = maxSubsetSize
""" Find both the size of largest divisible set and its index """
maxSize, maxSizeIndex = max([(v, i) for i, v in enumerate(dp)])
ret = []
""" Reconstruct the largest divisible subset """
# currSize: the size of the current subset
# currTail: the last element in the current subset
currSize, currTail = maxSize, nums[maxSizeIndex]
for i in range(maxSizeIndex, -1, -1):
if currSize == dp[i] and currTail % nums[i] == 0:
ret.append(nums[i])
currSize -= 1
currTail = nums[i]
return reversed(ret)
I guess maybe this LeetCode solution would be a bit closer to your method, is a recursion with memoization.
class Solution:
def largestDivisibleSubset(self, nums: List[int]) -> List[int]:
def EDS(i):
""" recursion with memoization """
if i in memo:
return memo[i]
tail = nums[i]
maxSubset = []
# The value of EDS(i) depends on it previous elements
for p in range(0, i):
if tail % nums[p] == 0:
subset = EDS(p)
if len(maxSubset) < len(subset):
maxSubset = subset
# extend the found max subset with the current tail.
maxSubset = maxSubset.copy()
maxSubset.append(tail)
# memorize the intermediate solutions for reuse.
memo[i] = maxSubset
return maxSubset
# test case with empty set
if len(nums) == 0: return []
nums.sort()
memo = {}
# Find the largest divisible subset
return max([EDS(i) for i in range(len(nums))], key=len)
References
For additional details, you can see the Discussion Board. There are plenty of accepted solutions with a variety of languages and explanations, efficient algorithms, as well as asymptotic time/space complexity analysis1, 2 in there.

Why was my algorithm for this interview a sub-optimal approach?

Given a list of integers, l = [1,5,3,2,6] and a target t = 6, return true if the list contains two distinct integers that sum to the target
I was given this question on a technical Python interview that caused me not to pass. My answer was:
def two_Sum(l, target):
for num in l:
for secondNum in l:
if num != secondNum:
if num + secondNum == target:
return True
The feedback I was given was that my solution was "not optimal". Please help me to understand why this was not the optimal solution and explain in detail what would be optimal for this case!
Your solution has a nested loop iterating the list, which means it's O(n^2) time complexity - and O(1) space, since you don't need to store any data during the iteration.
Reducing to O(n) time complexity is possible like this, coming at the cost of increasing to O(n) space complexity:
def two_sum(l, target):
s = set(l)
for n in l:
delta = target - n
if delta != n and delta in s:
return True
return False
As a slight improvement, you can even avoid to traverse the entire list, but it's still O(n):
def two_sum(l, target):
seen = set()
for n in l:
delta = target - n
if delta != n and delta in seen:
return True
seen.add(n)
return False
you can start by having two pointers (start,end), start will point to start of the list and end will point to end of list, then add them and see if it equals to your target, if equals then print or add to result.
if sum is greater then your target that means decrease your end pointer by 1 and if it's equal to or smaller than your target then increase your start pointer.
def two_Sum(l,target):
start=0
end=len(l)-1
while start!=end:
pair_sum=l[start]+l[end]
if pair_sum==target:
print l[start],l[end]
if pair_sum <= target:
start=start+1
if pair_sum > target:
end = end-1
l=[1,2,3,4,5,6,7,8,9,10]
two_Sum(l,9)
The most efficient way is to hash T-I[i] for each i and check each element as you see it
def sum2(I,T):
h = {}
for itm in I:
if itm in h:
return True
h[T-itm] = 1
return False
This will only go once through your list:
def two_sum(l, t):
s = set(l)
for n in s:
if t-n in s:
if n != t-n:
return True
return False
Your solution is O(n²), as you do a nested iteration of the whole list.
A simple solution with time complexity n log(n) would be:
sort your list
iterate doing a binary search for the complementary to target
Supposing you have binary search implemented in function bs(item, sorted_list):
def two_Sum(l, target):
l_sorted = sorted(l) # n log(n)
return any(bs(target - x, l_sorted) for x in l_sorted) # n log(n)
You can also do some other optimisation like stop iterating if you reach target/2.
Caveat: I don't garantee nor really believe this is the optimal solution, but rather intended to show you a better one and give insight to your improving your own.

How to find number of ways that the integers 1,2,3 can add up to n?

Given a set of integers 1,2, and 3, find the number of ways that these can add up to n. (The order matters, i.e. say n is 5. 1+2+1+1 and 2+1+1+1 are two distinct solutions)
My solution involves splitting n into a list of 1s so if n = 5, A = [1,1,1,1,1]. And I will generate more sublists recursively from each list by adding adjacent numbers. So A will generate 4 more lists: [2,1,1,1], [1,2,1,1], [1,1,2,1],[1,1,1,2], and each of these lists will generate further sublists until it reaches a terminating case like [3,2] or [2,3]
Here is my proposed solution (in Python)
ways = []
def check_terminating(A,n):
# check for terminating case
for i in range(len(A)-1):
if A[i] + A[i+1] <= 3:
return False # means still can compute
return True
def count_ways(n,A=[]):
if A in ways:
# check if alr computed if yes then don't compute
return True
if A not in ways: # check for duplicates
ways.append(A) # global ways
if check_terminating(A,n):
return True # end of the tree
for i in range(len(A)-1):
# for each index i,
# combine with the next element and form a new list
total = A[i] + A[i+1]
print(total)
if total <= 3:
# form new list and compute
newA = A[:i] + [total] + A[i+2:]
count_ways(A,newA)
# recursive call
# main
n = 5
A = [1 for _ in range(n)]
count_ways(5,A)
print("No. of ways for n = {} is {}".format(n,len(ways)))
May I know if I'm on the right track, and if so, is there any way to make this code more efficient?
Please note that this is not a coin change problem. In coin change, order of occurrence is not important. In my problem, 1+2+1+1 is different from 1+1+1+2 but in coin change, both are same. Please don't post coin change solutions for this answer.
Edit: My code is working but I would like to know if there are better solutions. Thank you for all your help :)
The recurrence relation is F(n+3)=F(n+2)+F(n+1)+F(n) with F(0)=1, F(-1)=F(-2)=0. These are the tribonacci numbers (a variant of the Fibonacci numbers):
It's possible to write an easy O(n) solution:
def count_ways(n):
a, b, c = 1, 0, 0
for _ in xrange(n):
a, b, c = a+b+c, a, b
return a
It's harder, but possible to compute the result in relatively few arithmetic operations:
def count_ways(n):
A = 3**(n+3)
P = A**3-A**2-A-1
return pow(A, n+3, P) % A
for i in xrange(20):
print i, count_ways(i)
The idea that you describe sounds right. It is easy to write a recursive function that produces the correct answer..slowly.
You can then make it faster by memoizing the answer. Just keep a dictionary of answers that you've already calculated. In your recursive function look at whether you have a precalculated answer. If so, return it. If not, calculate it, save that answer in the dictionary, then return the answer.
That version should run quickly.
An O(n) method is possible:
def countways(n):
A=[1,1,2]
while len(A)<=n:
A.append(A[-1]+A[-2]+A[-3])
return A[n]
The idea is that we can work out how many ways of making a sequence with n by considering each choice (1,2,3) for the last partition size.
e.g. to count choices for (1,1,1,1) consider:
choices for (1,1,1) followed by a 1
choices for (1,1) followed by a 2
choices for (1) followed by a 3
If you need the results (instead of just the count) you can adapt this approach as follows:
cache = {}
def countwaysb(n):
if n < 0:
return []
if n == 0:
return [[]]
if n in cache:
return cache[n]
A = []
for last in range(1,4):
for B in countwaysb(n-last):
A.append(B+[last])
cache[n] = A
return A

Categories