what this code should do is you have to climb a staircase with 'n' steps. At each step, you can either climb '1' or '2' steps.
this code works with small numbers but not with big numbers, for exemple print(climbing_stairs(45)) won't work. In C there are long and double long, i was wondering if there is something similar in python.
def climbing_stairs(n):
if ( n == 0 ):
return 1
elif (n < 0):
return 0
else:
return climbing_stairs(n - 2) + climbing_stairs(n - 1)
print(climbing_stairs(2))
print(climbing_stairs(3))
print(climbing_stairs(45))
The problem with the current code is the run time complexity. This code runs in O(2^n) and it isn't feasible for larger numbers.
This problem is just a rewording of the Fibonacci numbers. What you are currently doing is to recalculate similar results repeatedly. Say, you know how many ways you can move from step 4 to n. When you don't memoize it, you will recalculate it the next time since there are different ways to reach step 4.
dp = {}
def climbing_stairs(n):
if n == 0 or n == 1:
return 1
elif n < 0:
return 0
elif n in dp:
return dp[n]
else:
dp[n] = climbing_stairs(n - 2) + climbing_stairs(n - 1)
return dp[n]
In a more pythonic way:
from functools import cache
#cache
def climbing_stairs(n):
if n == 0 or n == 1:
return 1
elif n < 0:
return 0
else:
return climbing_stairs(n - 2) + climbing_stairs(n - 1)
And there is also an elegant iterative approach to this problem that has a constant time complexity:
def climbing_stairs(n):
if n < 3: return n
first, second = 1, 2
for _ in range(1, n):
third = first+second
first, second = second, third
return second
Both the memoized recursive version and the iterative one have a linear runtime complexity, since we only calculate the each step once.
Related
I am trying to solve a problem where recursion is a must. The tasks is: Write a function that takes in an integer n and returns the highest integer in the corresponding Collatz sequence.
My solution is this:
collatz = []
def max_collatz(num):
collatz.append(num)
if num == 1:
return max(collatz)
else:
return max_collatz(num / 2) if num%2 == 0 else max_collatz((3 * num) + 1)
However, I need to find a way to solve it without using a list outside of the function. I really couldn't find a solution, is there any?
It's either the current number or the largest in the rest of the sequence.
def max_collatz(n):
if n == 1:
return 1
elif n % 2:
return max_collatz(3 * n + 1)
else:
return max(n, max_collatz(n // 2))
This question already has answers here:
FibFrog Codility Problem - Optimising for Performance
(2 answers)
Closed last year.
I'm trying to solve the Codility FibFrog problem and I came up with the following solution:
def jumps_from(position, fb, A):
paths = set([])
for i in fb:
newPos = position + i
if newPos == len(A):
return set([-1])
elif newPos < len(A):
if A[newPos] == 1:
paths.add(newPos)
else: break
return paths
def solution(A):
if len(A) < 3: return 1
fibonaccis = fibonacci(len(A))
if len(A) + 1 in fibonaccis: return 1
paths = set([-1])
steps = 0
while True:
paths = set([idx for pos in paths for idx in jumps_from(pos, fibonaccis, A)])
if len(paths) == 0: return -1
if -1 in paths:
return steps + 1
steps += 1
return steps
def fibonacci(N):
arr = [0] * (N + 2)
arr[1] = 1
for i in range(2, N + 2):
arr[i] = arr[i-1] + arr[i-2]
return dict.fromkeys(arr[2:], 1)
Codility detects the runtime of this as O(N * log(N) ** N).
Codility Report: https://app.codility.com/demo/results/trainingJV7YAC-G3B/
I'm comparing this with the following solution, which scores 100% on Codility, and has runtime O(N * log(N)):
def gen_fib(n):
fn = [0,1]
i = 2
s = 2
while s < n:
s = fn[i-2] + fn[i-1]
fn.append(s)
i+=1
return fn
def new_paths(A, n, last_pos, fn):
"""
Given an array A of len n.
From index last_pos which numbers in fn jump to a leaf?
returns list: set of indexes with leaves.
"""
paths = []
for f in fn:
new_pos = last_pos + f
if new_pos == n or (new_pos < n and A[new_pos]):
paths.append(new_pos)
return paths
def solution(A):
n = len(A)
if n < 3:
return 1
# A.append(1) # mark final jump
fn = sorted(gen_fib(100000)[2:]) # Fib numbers with 0, 1, 1, 2.. clipped to just 1, 2..
# print(fn)
paths = set([-1]) # locate all the leaves that are one fib jump from the start position.
jump = 1
while True:
# Considering each of the previous jump positions - How many leaves from there are one fib jump away
paths = set([idx for pos in paths for idx in new_paths(A, n, pos, fn)])
# no new jumps means game over!
if not paths:
break
# If there was a result in the new jumps record that
if n in paths:
return jump
jump += 1
return -1
I'm not sure why my solution differs in runtime, since the approach is exactly the same - compute all the indices you can jump to from -1, and then compute all the indices you can jump to from the new positions, until you get to the other side of the river, or no new positions can be found.
Please refer to the first point in my previous answer.
If len(A) = 100000, you are calculating 100003 fibonacci numbers, while we only need fibonacci numbers which are less than 100k, which would be <30 of them.
The current fibonacci function is still returning N fibonacci numbers instead of just returning fibonacci numbers which are less than N. For N=100k, it should be just 25 numbers instead of over 100k.
Please update your fibonacci function to this -
def fibonacci(N):
arr = [1, 1]
while arr[-1] < N:
arr.append(arr[-1] + arr[-2])
return dict.fromkeys(arr[1:], 1)
I just ran a test locally, and looks like your fibonacci function takes ~1 sec to generate the first 100k fibonacci numbers, and that's the reason it might be failing the performance test, even though the rest of your code is optimal. I think you should be able to clear it with the required performance limits after correcting the fibonacci function.
We have an input integer let's say 13. We can find consistent subarray of fibonacci numbers that sums to 10 - [2,3,5]. I need to find next number that is not a sum of consistent subarray. In this case this number will be 14. I have this code, but the catch is, it can be optimized to not iterate through all of the N's from starting Left Pointer = 1 and Right Pointer = 1 but somehow "import" from previous N and i have no clue how to do it so maybe someone smarter might help.
def fib(n):
if n == 1: return 1
if n == 2: return 1
return fib(n-1) + fib(n-2)
def fibosubarr(n):
L_pointer = 1
R_pointer = 2
sumfibs = 1
while sumfibs != n:
if sumfibs > n and L_pointer < R_pointer:
sumfibs -= fib(L_pointer)
L_pointer += 1
elif sumfibs < n and L_poiner < R_pointer:
sumfibs += fib(R_pointer)
R_pointer += 1
else: return False
return True
n = int(input())
while fibosubarr(n):
n += 1
print(n)
Here's a technique called "memoizing". The fib function here keeps track of the current list and only extends it as necessary. Once it has generated a number, it doesn't need to do it again.
_fib = [1,1]
def fib(n):
while len(_fib) <= n:
_fib.append( _fib[-2]+_fib[-1] )
return _fib[n]
With your scheme, 200000 caused a noticeable delay. With this scheme, even 2 billion runs instantaneously.
To get the next subarray sum, you only need one call of the function -- provided you keep track of the least sum value that was exceeding n.
I would also use a generator for the Fibonacci numbers:
def genfib():
a = 1
b = 1
while True:
yield b
a, b = b, a + b
def fibosubarr(n):
left = genfib()
right = genfib()
sumfibs = next(right)
closest = float("inf")
while sumfibs:
if sumfibs > n:
closest = min(sumfibs, closest)
sumfibs -= next(left)
elif sumfibs < n:
sumfibs += next(right)
else:
return n
return closest
Now you can do as you did -- produce the next valid sum that is at least the input value:
n = int(input())
print(fibosubarr(n))
You could also loop to go from one such sum to the next:
n = 0
for _ in range(10): # first 10 such sums
n = fibosubarr(n+1)
print(n)
Question from codewars https://www.codewars.com/kata/52ec24228a515e620b0005ef/python
In number theory and combinatorics, a partition of a positive integer n, also called an integer partition, is a way of writing n as a sum of positive integers. Two sums that differ only in the order of their summands are considered the same partition. If order matters, the sum becomes a composition. For example, 4 can be partitioned in five distinct ways:
4
3 + 1
2 + 2
2 + 1 + 1
1 + 1 + 1 + 1
Given number n, write a function exp_sum(n) that returns the total number of ways n can be partitioned.
Eg: exp_sum(4) = 5
Why does the recursion approach:
def exp_sum(n):
arr = list(range(1, n+1))
mem = {}
return rec(n, arr, mem)
def rec(n, arr, mem):
key = str(n)+ ":" + str(arr)
if key in mem:
return mem[key]
elif n < 0:
return 0
elif n == 0:
return 1
elif n > 0 and not arr:
return 0
else:
to_return = rec(n - arr[-1], arr, mem) + rec(n, arr[:-1], mem)
mem[key] = to_return
return to_return
take so much longer to run compared to this particular method (top solution of this kata)?
def exp_sum(n):
if n < 0:
return 0
dp = [1]+[0]*n
for num in range(1,n+1):
for i in range(num,n+1):
dp[i] += dp[i-num]
return dp[-1]
Even with using memoisation, the recursion approach barely managed to pass the test case at a time of about 10000ms, compared to the 1000ms taken for the above approach.
And can anyone explain how the particular method above works and the logic behind it or if it uses some particular algorithm which I can read up about?
I had to create a program for a staircase problem where i had to design staircase with n number of bricks. The complexity is that the number of bricks in every step must be unique, and less than the previous step.
for example, using 6 bricks, i can make a staircase with step height (5,1) , (4,2) and (3,2,1) but not (3,3) or (1,2,3) or (2,4) or any other permutation.
I have designed the code but the problem is that it runs fine till n as nearly 100 or 120, but freezes if the inputs are larger than these values. I am a beginner in Python programming and learning concepts on the go.
I tried memoization, but to no avail. I need to know if there is something else i can do to make my code more optimized to run over n as 200-250?
import cProfile
def solution(n):
memory = {0: [], 1: [1], 2: [2]}
def rec(max_val, i):
t = []
r = []
for j in range(1,i):
y = i - j
if y < max_val:
if y > j:
t = [y, j]
r.append(t)
if n / 2 >= j >= 3 and j in memory:
mem = memory[j]
[r.append([y, item]) for item in mem]
else:
if y >= 3 and n / 2 >= j >= 3 and j in memory:
mem = memory[j]
for item in mem:
if y > item[0]:
r.append([y, item])
else:
v = rec(y, j)
if v:
for item in v:
t = [y, item]
r.append(t)
if r:
if i in memory:
if len(memory[i]) < len(r):
memory[i] = r
else:
memory[i] = r
return r
def main_func(n):
stair = []
max_val = 201
total = 0
for i in range (1,n):
x = n - i
if x > i:
s = [x, i]
total += 1
if i >= 3:
u = rec(max_val, i)
total += len(u)
elif x == i and i >= 3:
u = rec(max_val, i)
total += len(u)
elif x < i and i >= 3:
u = rec(x, i)
total += len(u)
return total
stairs = main_func(n)
return (stairs)
print(solution(100))
You can approach the problem recursively from the perspective of the base of the stairs. The strategy is to add up the pattern counts of the next step levels for each base size.
For example, with 6 bricks, the first call would go through base sizes n = 5,4,3,2 and make a recursive calls to know how many combinations are possible for the next step levels using the remaining bricks and with a maximum base of n-1. The sum of next levels counts will constitute the total count of possible stair patterns.
At the top of the stairs, you need at least 3 bricks to add more than one level so you can stop the recursion with a count of 1 when there are fewer than 3 bricks left. This will roll up the recursive calls to form larger totals and produce the right answer once the original call completes.
In order to optimize this process, you can use memoization and you can also short circuit the calculation using the base size provided at each recursion.
For a given base, the largest number of bricks that can be used will be the sum of numbers from 1 to that base. This can be computed using Gauss's formula: base*(base+1)/2. If you have more bricks than the maximum brick count for the base, then you can stop the recursion and return a count of zero (since you have too many remaining bricks and will not be able to fit them all over the previous level's base)
Another way to optimize the calculation is to loop through the base sizes in decreasing order. This will allow you to stop the loop as soon as you get a count of zero for the next levels, which means that there are too many remaining bricks for that base size (or any smaller base size)
Here's an example (using lru_cache for memoization):
from functools import lru_cache
#lru_cache(None)
def stairCount(N,base=None):
base = min(base or N-1,N)
if N > base*(base+1)//2: return 0
if N < 3: return 1
count = 0
while True:
nextLevels = stairCount(N-base,base-1)
if nextLevels == 0: break
count += nextLevels
base = base - 1
return count
With these optimizations, the function will respond in less than a second for up to 600 bricks (depending on the speed of your computer).
With Python's list comprehensions, you could write this function more concisely (though it would lose the decreasing base order optimization ≈ 10%):
#lru_cache(None)
def stairCount(N,base=None):
base = min(base or N-1,N)
if N > base*(base+1)//2: return 0
if N < 3: return 1
return sum(stairCount(N-b,b-1) for b in range(2,base+1))
EDIT Here's a version with a "manual" memoization (i.e. without using functools):
def stairCount(N,base=None,memo=dict()):
memoKey = (N,base)
if memoKey in memo: return memo[memoKey]
base = min(base or N-1,N)
if N > base*(base+1)//2: return 0
if N < 3: return 1
count = 0
while True:
nextLevels = stairCount(N-base,base-1,memo)
if nextLevels == 0: break
count += nextLevels
base = base - 1
memo[memoKey] = count
return count