I have a problem with some implementation of a function that solves the problem of a subset sum in Python.
We have dynamic programming here, so the complexity should be polynomial.
The problem is that if the size of set grows linearly and the size of the numbers also increases linearly (of course it is not a logarithm of numbers) then the code execution time can grow exponentially.
My guess is that this may be due to a particular implementation.
Is it possible to improve it somehow?
Code in Python:
def subsetsum(array,num):
if num == 0 or num < 1:
return None
elif len(array) == 0:
return None
else:
if array[0] == num:
return [array[0]]
else:
with_v = subsetsum(array[1:],(num - array[0]))
if with_v:
return [array[0]] + with_v
else:
return subsetsum(array[1:],num)
You're using slices to pass suffixes of array, this will make a copy which has linear runtime. To avoid that you can pass indices instead.
Another advantage is that indices are hashable, so you can cache (or memoize) and avoid recomputing answers:
from functools import lru_cache
def ssum(array, N):
#lru_cache(maxsize=None)
def subsetsum(idx, num):
if num < 1 or idx >= len(array):
return frozenset()
if array[idx] == num:
return frozenset([idx])
with_v = subsetsum(idx + 1, num - array[idx])
if with_v:
return with_v | frozenset([idx])
else:
return subsetsum(idx + 1, num)
return list(array[i] for i in subsetsum(0, N))
>>> ssum([1,1,2], 4)
[1, 1, 2]
Unfortunately, there's still the cost of copying the answer obtained from the suffix
Related
Hi I'm trying to solve Leetcode 413: Arithmetic slices. I'm trying to start with a brute force recursive solution.
def numberOfArithmeticSlices(self, nums: List[int]) -> int:
def slices(nums: List[int], i: int):
if (i < 2):
return 0
if nums[i] - nums[i-1] == nums[i-1] - nums[i-2]:
return 1 + slices(nums, i -1)
else:
return slices(nums, i-1)
if len(nums) < 3:
return 0
return slices(nums, len(nums)-1)
This doesn't work for the test case [1,2,3,4] (it returns 2 instead of 3). In my head I know it doesn't work because when the function is called, 1 + slices([1,2,3], 2) returns 2. How can I fix my code to get the arithmetic slice coming from the entire array [1,2,3,4]?
For solving this problem you have to take two steps.
First you have to find all possible contiguous sub-arrays
You have to check them, if they are arithmetic slices.
An understandable solution which is not memory and time efficient is as below:
def numberOfArithmeticSlices(self, nums: List[int]) -> int:
if len(nums) <= 2:
return 0
sub_arrays = self.contiguous_subarray(nums) # type List[List[int]] all contiguous sub arrays with length 3 or more
count = 0
for subset in sub_arrays:
count = count + self.is_arithmetic_subset(subset)
return count
#staticmethod
def is_arithmetic_subset(subset):
if len(subset) <= 2:
return 0
diff = subset[1] - subset[0]
for i in range(2, len(subset)):
if subset[i] - subset[i - 1] != diff:
return 0
return 1
#staticmethod
def contiguous_subarray(nums):
return [nums[i:i + j] for i in range(0, len(nums)) for j in range(3, len(nums) - i + 1)]
But a solution that is little more harder to grasp but is memory and time efficient is as bellow(You could still replace the recursive call with a loop and I think you would get better results doing so):
def numberOfArithmeticSlices(self, nums: List[int]) -> int:
array_len = len(nums)
if array_len <= 2:
return 0
count = self.numberOfArithmeticSlices(nums[:array_len - 1])
diff = nums[array_len - 1] - nums[array_len - 2]
for i in range(2, array_len):
if nums[array_len - i ] - nums[array_len - i - 1] == diff:
count += 1
else:
break
return count
I wrote the following two codes for computing an element of the Fibonacci Sequence.
def fib(n):
zero, one = 0, 1
k = 1
while k < n:
zero, one = one, zero + one
k = k + 1
return one, ls
def fib2(n, memo=None):
if memo is None:
memo = {}
if n == 1 or n == 2:
return 1
if n in memo:
return memo[n]
else:
memo[n-1] = fib2(n-1, memo)
memo[n-2] = fib2(n-2, memo)
return memo[n-1] + memo[n-2]
##import timeit
##
##print('Fibonacci 1:', timeit.timeit('fib(10000)', '''def fib(n):
## zero, one = 0, 1
## k = 1
## while k < n:
## zero, one = one, zero + one
## k = k + 1
## return one''', number=100))
##
##print('Fibonacci 2:', timeit.timeit('fib2(10000)', '''import sys; sys.setrecursionlimit(10001);
##def fib2(n, memo=None):
## if memo is None:
## memo = {}
## if n == 0 or n == 1:
## return 1
## if n in memo:
## return memo[n]
## else:
## memo[n-1] = fib2(n-1, memo)
## memo[n-2] = fib2(n-2, memo)
## return memo[n-1] + memo[n-2]''', number=100))
I am using a simple while loop in fib and fib2 is a recursive implementation of the same. But it turns out that fib2 is exceptionally slower. I want to know why it is. Is it because fib2 creates a whole lot of frames? Have I implemented fib2 correctly?
Thanks.
Time this streamlined recursive version against your original iterative solution -- first up the recursion limit by ~ 1% to 10%:
def fib2(n, memo={0: None, 1: 1, 2: 1}):
if n in memo:
return memo[n]
previous = fib2(n - 1) # implicitly computes fib2(n - 2)
result = memo[n] = previous + memo[n - 2]
return result
I'm not passing memo as an argument on the recursion as I'm taking advantage of the "problem" when default arguments are set to structures that can be modified.
The above solution is ~ 4.5x slower than the original iterative one on my machine on the first invocation -- after that, memoization takes over. We can improve on this a little bit, in both space & time, by changing our "memory" from a dictionary to a list since all the keys are sequential integers:
def fib3(n, memo=[None, 1, 1]):
if n < len(memo):
return memo[n]
previous = fib3(n - 1) # implicitly computes fib3(n - 2)
result = previous + memo[-2]
memo.append(result)
return result
This one times out at ~ 3x slower than the iterative solution, on my machine, for the first invocation. However, we can do better speed-wise using recursion:
def fib4(n, res=0, nxt=1):
if n == 0:
return res
return fib4(n - 1, nxt, res + nxt)
This is only ~ 2x slower than the iterative solution and/but has no memoization. In a language with tail call optimization (i.e. not Python), this would likely become/tie iteration.
What is the recursive call (or inductive steps) for a function that returns the number of integers from 1 to N, which evenly divide N. The idea is to concieve a pure recursive code in python for this function. No 'for' or 'while' loops, neither modules can be used. The function num_of_divisors(42) returns 8, representing 1, 2, 3, 6, 7, 14, 21, and 42 as divisors of 42.
def num_of_divisors(n):
return sum(1 if n % i==0 else 0 for i in range(((n+1)**0.5)//1)
Good luck explaining it to your teacher!
If you really can't use for loops (?????????) then this is impossible without simulating one.
def stupid_num_of_divisors_assigned_by_shortsighted_teacher(n, loop_num=1):
"""I had to copy this from Stack Overflow because it's such an
inane restriction it's actually harmful to learning the language
"""
if loop_num <= (n+1) ** 0.5:
if n % loop_num == 0:
return 2 + \
stupid_num_of_divisors_assigned_by_shortsighted_teacher(n, loop_num+1)
else:
return stupid_num_of_divisors_assigned_by_shortsighted_teacher(n, loop_num+1)
else:
if n % loop_num == 0:
return 1
Bonus points: explain why you're adding 2 in the first conditional, but only 1 in the second conditional!
Here you go buddy your teacher'll be happy.
def _num_of_divisors(n, k):
if (k == 0):
return 0
return _num_of_divisors(n, k-1) + (n % k == 0)
def num_of_divisors(n):
return _num_of_divisors(n, n)
It's easier than you think to convert such a simple problem from a loop to a recursive function.
Start with a loop implementation:
n = 42
result = []
for i in range(n+1):
if n % i == 0:
result.append(i)
then write a function
def num_of_divisors_helper(i, n, result):
if <condition when a number should be added to result>:
result.append(n)
# Termination condition
if <when should it stop>:
return
# Recursion
num_of_divisors_helper(i+1, n, result)
Then you define a wrapper function num_of_divisors that calls num_of_divisors_helper. You should be able to fill the gaps in the recursive function and write the wrapper function yourself.
It's a simple, inefficient solution, but it matches your terms.
Without using %
def is_divisible(n, i, k):
if k > n:
return False
if n - i*k == 0:
return True
else:
return is_divisible(n, i, k+1)
def num_of_divisors(n, i=1):
if i > n/2:
return 1
if is_divisible(n, i, 1):
return 1 + num_of_divisors(n, i+1)
else:
return num_of_divisors(n, i+1)
num_of_divisors(42) -> 8
def n_divisors(n,t=1):
return (not n%t)+(n_divisors(n,t+1) if t < n else 0)
good luck on the test later ... better hit those books for real, go to class and take notes...
with just one input i guess
t=0
def n_divisors(n):
global t
t += 1
return (not n%t)+(n_divisors(n) if t < n else 0)
I am trying to write a function that will not only determine whether the sum of a subset of a set adds to a desired target number, but also to print the subset that is the solution.
Here is my code for finding whether a subset exists:
def subsetsum(array,num):
if num == 0 or num < 1:
return False
elif len(array) == 0:
return False
else:
if array[0] == num:
return True
else:
return subsetsum(array[1:],(num - array[0])) or subsetsum(array[1:],num)
How can I modify this to record the subset itself so that I can print it? Thanks in advance!
Based on your solution:
def subsetsum(array,num):
if num == 0 or num < 1:
return None
elif len(array) == 0:
return None
else:
if array[0] == num:
return [array[0]]
else:
with_v = subsetsum(array[1:],(num - array[0]))
if with_v:
return [array[0]] + with_v
else:
return subsetsum(array[1:],num)
Modification to also detect duplicates and further solutions when a match happened
def subset(array, num):
result = []
def find(arr, num, path=()):
if not arr:
return
if arr[0] == num:
result.append(path + (arr[0],))
else:
find(arr[1:], num - arr[0], path + (arr[0],))
find(arr[1:], num, path)
find(array, num)
return result
You could change your approach to do that more easily, something like:
def subsetsum(array, num):
if sum(array) == num:
return array
if len(array) > 1:
for subset in (array[:-1], array[1:]):
result = subsetsum(subset, num)
if result is not None:
return result
This will return either a valid subset or None.
Thought I'll throw another solution into the mix.
We can map each selection of a subset of the list to a (0-padded) binary number, where a 0 means not taking the member in the corresponsing position in the list, and 1 means taking it.
So masking [1, 2, 3, 4] with 0101 creates the sub-list [2, 4].
So, by generating all 0-padded binary numbers in the range between 0 and 2^LENGTH_OF_LIST, we can iterate all selections. If we use these sub-list selections as masks and sum the selection - we can know the answer.
This is how it's done:
#!/usr/bin/env python
# use a binary number (represented as string) as a mask
def mask(lst, m):
# pad number to create a valid selection mask
# according to definition in the solution laid out
m = m.zfill(len(lst))
return map(lambda x: x[0], filter(lambda x: x[1] != '0', zip(lst, m)))
def subset_sum(lst, target):
# there are 2^n binary numbers with length of the original list
for i in xrange(2**len(lst)):
# create the pick corresponsing to current number
pick = mask(lst, bin(i)[2:])
if sum(pick) == target:
return pick
return False
print subset_sum([1,2,3,4,5], 7)
Output:
[3, 4]
To return all possibilities we can use a generator instead (the only changes are in subset_sum, using yield instead of return and removing return False guard):
#!/usr/bin/env python
# use a binary number (represented as string) as a mask
def mask(lst, m):
# pad number to create a valid selection mask
# according to definition in the solution laid out
m = m.zfill(len(lst))
return map(lambda x: x[0], filter(lambda x: x[1] != '0', zip(lst, m)))
def subset_sum(lst, target):
# there are 2^n binary numbers with length of the original list
for i in xrange(2**len(lst)):
# create the pick corresponsing to current number
pick = mask(lst, bin(i)[2:])
if sum(pick) == target:
yield pick
# use 'list' to unpack the generator
print list(subset_sum([1,2,3,4,5], 7))
Output:
[[3, 4], [2, 5], [1, 2, 4]]
Note: While not padding the mask with zeros may work as well, as it will simply select members of the original list in a reverse order - I haven't checked it and didn't use it.
I didn't use it since it's less obvious (to me) what's going on with such trenary-like mask (1, 0 or nothing) and I rather have everything well defined.
Slightly updated the below code to return all possible combinations for this problem. Snippet in the thread above will not print all possible combinations when the input is given as subset([4,3,1],4)
def subset(array, num):
result = []
def find(arr, num, path=()):
if not arr:
return
if arr[0] == num:
result.append(path + (arr[0],))
else:
find(arr[1:], num - arr[0], path + (arr[0],))
find(arr[1:], num, path)
find(array, num)
return result
A bit different approach to print all subset through Recursion.
def subsetSumToK(arr,k):
if len(arr)==0:
if k == 0:
return [[]]
else:
return []
output=[]
if arr[0]<=k:
temp2=subsetSumToK(arr[1:],k-arr[0]) #Including the current element
if len(temp2)>0:
for i in range(len(temp2)):
temp2[i].insert(0,arr[0])
output.append(temp2[i])
temp1=subsetSumToK(arr[1:],k) #Excluding the current element
if len(temp1)>0:
for i in range(len(temp1)):
output.append(temp1[i])
return output
arr=[int(i) for i in input().split()]
k=int(input())
sub=subsetSumToK(arr,k)
for i in sub:
for j in range(len(i)):
if j==len(i)-1:
print(i[j])
else:
print(i[j],end=" ")
Rather than using recursion, you could use the iterative approach.
def desiredSum(array, sum):
numberOfItems = len(array)
storage = [[0 for x in range(sum + 1)] for x in range(numberOfItems + 1)]
for i in range(numberOfItems + 1):
for j in range(sum + 1):
value = array[i - 1]
if i is 0: storage[i][j] = 0
if j is 0: storage[i][j] = 1
if value <= j:
noTake = storage[i - 1][j]
take = storage[i - 1][j - value]
storage[i][j] = noTake + take
return storage[numberOfItems][sum]
This question already has answers here:
Understanding recursion [closed]
(20 answers)
Closed 5 months ago.
How can I write a function that computes:
C(n,k)= 1 if k=0
0 if n<k
C(n-1,k-1)+C(n-1,k) otherwise
So far I have:
def choose(n,k):
if k==0:
return 1
elif n<k:
return 0
else:
Assuming the missing operands in your question are subtraction operators (thanks lejlot), this should be the answer:
def choose(n,k):
if k==0:
return 1
elif n<k:
return 0
else:
return choose(n-1, k-1) + choose(n-1, k)
Note that on most Python systems, the max depth or recursion limit is only 1000. After that it will raise an Exception. You may need to get around that by converting this recursive function to an iterative one instead.
Here's an example iterative function that uses a stack to mimic recursion, while avoiding Python's maximum recursion limit:
def choose_iterative(n, k):
stack = []
stack.append((n, k))
combinations = 0
while len(stack):
n, k = stack.pop()
if k == 0:
combinations += 1
elif n<k:
combinations += 0 #or just replace this line with `pass`
else:
stack.append((n-1, k))
stack.append((n-1, k-1))
return combinations
Improving from Exponential to Linear time
All of the answers given so far run in exponential time O(2n). However, it's possible to make this run in O(k) by changing a single line of code.
Explanation:
The reason for the exponential running time is that each recursion separates the problem into overlapping subproblems with this line of code (see Ideone here):
def choose(n, k):
...
return choose(n-1, k-1) + choose(n-1, k)
To see why this is so bad consider the example of choose(500, 2). The numeric value of 500 choose 2 is 500*499/2; however, using the recursion above it takes 250499 recursive calls to compute that. Obviously this is overkill since only 3 operations are needed.
To improve this to linear time all you need to do is choose a different recursion which does not split into two subproblems (there are many on wikipedia).
For example the following recursion is equivalent, but only uses 3 recursive calls to compute choose(500,2) (see Ideone here):
def choose(n,k):
...
return ((n + 1 - k)/k)*choose(n, k-1)
The reason for the improvement is that each recursion has only one subproblem that reduces k by 1 with each call. This means that we are guaranteed that this recursion will only take k + 1 recursions or O(k). That's a vast improvement for changing a single line of code!
If you want to take this a step further, you could take advantage of the symmetry in "n choose k" to ensure that k <= n/2 (see Ideone here):
def choose(n,k):
...
k = k if k <= n/2 else n - k # if k > n/2 it's equivalent to k - n
return ((n + 1 - k)/k)*choose(n, k-1)
Solution from wikipedia (http://en.wikipedia.org/wiki/Binomial_coefficient)
def choose(n, k):
if k < 0 or k > n:
return 0
if k > n - k: # take advantage of symmetry
k = n - k
if k == 0 or n <= 1:
return 1
return choose(n-1, k) + choose(n-1, k-1)
You're trying to calculate the number of options to choose k out of n elements:
def choose(n,k):
if k == 0:
return 1 # there's only one option to choose zero items out of n
elif n < k:
return 0 # there's no way to choose k of n when k > n
else:
# The recursion: you can do either
# 1. choose the n-th element and then the rest k-1 out of n-1
# 2. or choose all the k elements out of n-1 (not choose the n-th element)
return choose(n-1, k-1) + choose(n-1, k)
just like this
def choose(n,k):
if k==0:
return 1
elif n<k:
return 0
else:
return choose(n-1,k-1)+choose(n-1,k)
EDIT
It is the easy way, for an efficient one take a look at wikipedia and spencerlyon2 answer