Reputation: 13
The problem I'm working on is this, from Cracking the Coding Interview:
"A child is running up a staircase with n steps, and can hop either 1 step, 2 steps, or 3 steps at a time. Implement a method to count how many possible ways the child can run up the stairs."
Coming from C++ I know that a counter can be passed as reference, but in python you can't. I am also trying to track the step sequence that results in a success. I'm writing my code like this:
def __calculatePaths(currPathLength, paths, currSeries):
if currPathLength == 0:
print "successful series is", currSeries
return 1
elif currPathLength < 0: return 0
for i in range(1, 4):
newSeries = list(currSeries) # make new series to track steps
newSeries.append(i)
paths += __calculatePaths(currPathLength - i, paths, newSeries)
return paths
def calculatePaths(pathLength):
paths = __calculatePaths(pathLength, 0, [])
return paths
if __name__ == '__main__':
calculatePaths(3)
The output for this call is:
successful series is [1, 1, 1]
successful series is [1, 2]
successful series is [2, 1]
successful series is [3]
6
I'm confused because my program gets the correct path sequences, but the wrong number of paths. How should I be incrementing my paths? I know how to do this without a global variable, but I can't figure it out without using one. Thank you!
Upvotes: 1
Views: 1067
Reputation: 26900
This should be the most efficient way to do it computing-wise using your solution:
from collections import deque
def calculate_paths(length):
count = 0 # Global count
def calcuate(remaining_length):
# 0 means success
# 1 means only 1 option is available (hop 1)
if remaining_length < 2:
nonlocal count # Refer to outer count
count += 1
return
# Calculates, removing the length already passed.
# For 1...4 or remaining_length+1 if it's less than 4.
# deque(, maxlen=0) is the fastest way of consuming an iterator
# without also keeping it's data. This is the most efficient both
# memory-wise and clock-wise
deque((calcuate(remaining_length-i)
for i in range(1, min(4, remaining_length+1))), maxlen=0)
calcuate(length)
return count
>>> calculate_paths(2)
2
>>> calculate_paths(3)
4
>>> calculate_paths(4)
7
As you can see, there is no need to keep the path as only the remaining length matters.
@Prune's answer has better algorithm. Here it is implemented:
def calculate_paths(length):
results = deque((1, 2, 4), maxlen=3)
if length <= 3:
return results[length-1]
for i in range(3, length):
results.append(sum(results))
return results.pop()
Eliminating recursion also causes less frames to be used, and doesn't stop with max recursion.
Upvotes: 0
Reputation: 77837
Most important, realize that you don't have to determine those sequences: you need only to count them. For instance, there's only one way to finish from step N-1: hop 1 step. From N-2, there are two ways: hop both steps at once, or hop 1 step and finish from there. Our "ways to finish" list now looks like this, working backwards:
way = [1, 2, ...]
Now, watch what happens with step N-3. We finally have 3 choices:
That's a total of 2+1+1, or 4 ways to finish.
That initializes our algorithm. Now for the recurrence relationship. The initial list looks like this:
way = [1, 2, 4, ...]
From here on, we can't make a single hop to the top. Instead, we have to depend on the three steps above us. Our choices from step N-J are:
Thus, for all j >= 3:
way[j] = way[j-1] + way[j-2] + way[j-3]
This gives you the solution in O(N) time.
Upvotes: 1
Reputation: 211
In your function __calculatePaths, you have to set paths = 0, before the for loop. Otherwise it is add the values to the global instance of paths and that's why you are getting wrong answer.
def __calculatePaths(currPathLength, paths, currSeries):
if currPathLength == 0:
print "successful series is", currSeries
return 1
elif currPathLength < 0: return 0
paths = 0
for i in range(1, 4):
newSeries = list(currSeries) # make new series to track steps
newSeries.append(i)
paths += __calculatePaths(currPathLength - i, paths, newSeries)
return paths
def calculatePaths(pathLength):
paths = __calculatePaths(pathLength, 0, [])
return paths
if __name__ == '__main__':
calculatePaths(3)
You can get the number of ways in much efficient manner. By using Dynamic Programming in O(N). And even more efficiently using matrix exponentiation O(logN).
Upvotes: 0