Algorithms

climbing stairs

10 months, 1 week ago ; 140 views
Share this

Understanding the Problem

To solve the problem of climbing stairs, you can use a technique called dynamic programming. Dynamic programming is a method for solving problems by breaking them down into smaller subproblems and storing the solutions to these subproblems so that they can be reused.

In the case of climbing stairs, the subproblems are the number of ways to climb a staircase with n steps, where n is a positive integer. The base cases are n = 0, n = 1, and n = 2, as there is only one way to climb a staircase with 0 steps, 1 step, or 2 steps, respectively.

For any other value of n, you can climb the staircase by either climbing one step and then solving the subproblem for n-1 steps or by climbing two steps and then solving the subproblem for n-2 steps.

The total number of ways to climb the staircase is the sum of the number of ways to climb the staircase by climbing one step and the number of ways to climb the staircase by climbing two steps.

Solution: Recursive Function

Here's a brute-force solution to this problem.

class Solution:

        def climb_stairs(self, n):
		
		        if n ==1 or n ==0 or n== 2:
			        return n

		        count =self.climb_stairs(n-1) + self.climb_stairs(n-2)
		        return count

This method takes the number of steps n as input and returns the number of ways to climb the staircase. It first checks if n is a base case. If it is, then the function returns the number of ways to climb the staircase with n steps.

Otherwise, the method recursively calls itself to solve the subproblems for n-1 and n-2 steps and then returns the sum of the results.

Time and space Complexity

The time complexity of the recursive solution for climbing stairs is O(2^n), where n is the number of steps in the staircase. This is because the solution involves making a recursive call for each subproblem, and there are 2^n subproblems.

The space complexity of the solution is O(n), where n is the number of steps in the staircase. This is because the solution requires a memoization table with n entries.

 

Solution: Top-Down Dynamic Programming

In a top-down dynamic programming solution, you can use memoization to store the results of subproblems. As a result, you avoid redundant calculations.

class Solution:

    def climb_stairs(self, n: int) -> int:
        memo = {}

        def climb_stairs_helper(k):
            if k == 0 or k == 1 or k == 2:
                return k

            # Check if the result for k is already memoized
            if k in memo:
                return memo[k]

            # Calculate and memoize the result for k
            count = climb_stairs_helper(k - 1) + climb_stairs_helper(k - 2)
            memo[k] = count

            return count

        return climb_stairs_helper(n)

 

Thought Process

Here's how I arrived at this solution.

Original Recursive Solution

The initial solution is a straightforward recursive approach where the number of ways to climb the stairs at the step n is the sum of the ways to climb the stairs at steps n-1 and n-2.

Identifying Redundancy

In the original recursive solution, there's a lot of redundant computation. For example, when calculating climb_stairs(n), it recalculates climb_stairs(n-1) and climb_stairs(n-2), even if those values have been computed before.

Memoization

The idea behind top-down dynamic programming is to store the results of subproblems in a data structure (in this case, a dictionary called, memo).

Before performing a recursive call, we check if the result for a specific input has already been computed and stored in the memo. If yes, we use the stored result instead of recalculating.

Code Explanation

 

Initialization
  • The memo dictionary is initialized to store results.
  • The climb_stairs_helper function is defined as the recursive helper function.
Base Cases
  • Base cases are defined for steps 0, 1, and 2.
  • These base cases are used to terminate the recursion when the input is small.
Memoization Check

Before performing a recursive call, the code checks if the result for the given input k is already memoized in the memo dictionary.

Recursive Calculation and Memoization:
  • If the result for k is not memoized, it is calculated recursively as climb_stairs_helper(k - 1) + climb_stairs_helper(k - 2).
  • The result is then memoized in the memo dictionary.
 Return Result
  • The final result is obtained by calling climb_stairs_helper(n).

This top-down dynamic programming approach with memoization avoids redundant calculations, improving the algorithm's efficiency. The memo dictionary serves as a cache to store and retrieve previously computed results.

Time and space Complexity

Time Complexity

The time complexity is determined by the number of unique subproblems that must be solved.

In this case, each distinct k represents a subproblem. Due to memoization, each subproblem is solved only once, and the result is stored in the memo dictionary.

Therefore, the time complexity is O(n), where n is the number of unique subproblems.

Space Complexity

The space complexity is influenced by the space required for the memoization table and the recursive call stack.

  1. Memoization Table:

    • The memo dictionary stores the results of subproblems.
    • In the worst case, there are O(n) unique subproblems, so the space complexity for the memo dictionary is O(n).
  2. Recursive Call Stack:

    • The maximum depth of the recursive call stack is equal to the maximum value of k.
    • In the worst case, the depth of the recursive stack is O(n).
  3. Total Space Complexity:

    • The overall space complexity is the sum of the space required for the memo dictionary and the space required for the recursive call stack.
    • O(n) (memo) + O(n) (recursive call stack) = O(n).

 

Solution: Bottom-up Dynamic Programming

The code I am showcasing below implements a bottom-up dynamic programming approach to solve the problem of counting the number of ways to climb a staircase with n steps, where you can climb 1 or 2 steps at a time.

The goal is to calculate the number of distinct ways to reach the top of the staircase.

class Solution:

        def bottom_up(self, n):
		
		        dp ={0:0,1:1,2:2}
		        for i in range(3, n+1):
			        dp[i] = dp[i-1] + dp[i-2]
		        return dp[n]

 

Code Explanation

Initialization

dp is a dictionary used for caching in DP. It is initialized with base cases: dp[0] = 0, dp[1] = 1, and dp[2] = 2. These represent the number of ways to climb staircases with 0, 1, and 2 steps.

Iterative Calculation
  • The code then iterates from 3 to n (inclusive).
  • For each step i, it calculates dp[i] by summing up the values of dp[i-1] and dp[i-2].
  • The result is stored in the dp dictionary.
Return Result

Finally, the function returns dp[n], which represents the number of ways to climb the staircase with n steps.

Time & Space Complexity of Bottom-up Solution

Time Complexity

The time complexity is O(n) due to the single loop that iterates from 3 to n, performing constant-time operations within the loop.

Space Complexity

The space complexity is O(n) due to the dp dictionary. In the worst case, all values from 0 to n are stored in the dictionary.

Become a member
Get the latest news right in your inbox. We never spam!

Read next

Island Perimeter

 This solution seeks to find the perimeter of an island in a grid.  The problem considers a grid where each cell represents land (1) or … Read More

Kibsoft 3 months, 3 weeks ago . 76 views

Pacific Atlantic Waterflow

3 months, 3 weeks ago . 82 views