>

Climbing Stairs

"""
70. Climbing Stairs

https://leetcode.com/problems/climbing-stairs

NOTES
  * Use dynamic programming and/or memoization.

If the problem can be broken down into subproblems, and if it contains the
optimal substructure property (i.e. its optimal solution can be constructed
efficiently from optimal solutions of its subproblems), it can be efficiently
solved using dynamic programming.
"""

from typing import Dict, List


class Solution:
    """
    The brute force solution would be to try every step combination, however,
    we can use dynamic programming and/or memoization to simplify the problem
    by "remembering" how many different combinations it took to reach the
    (i−1)th and (i−2)th step.

    The total number of ways to reach the ith step is equal to sum of ways of
    reaching the (i−1)th step and ways of reaching the (i−2)th step.
    """

    def climbStairs(self, n: int) -> int:
        dp: List[int] = [0, 1, 2]
        for i in range(3, n + 1):
            dp.append(dp[i - 1] + dp[i - 2])
        return dp[n]


class MemoizationSolution:
    """
    Memoization is an optimization technique used primarily in recursive
    algorithms. It involves storing the results of expensive function calls and
    returning the cached result when the same inputs occur again.

    This solution uses a top-down approach. Instead of solving each subproblem
    from scratch, we store the results of subproblems in a hash table and
    retrieve them when needed.
    """

    def climbStairs(self, n: int) -> int:
        memo: Dict[int, int] = {}

        def _climbStairs(i: int, n: int, memo: Dict[int, int]) -> int:
            """
            Where 'i' is the current step, 'n' is the target step, and 'memo'
            is a hash table of previous calculations.
            """
            # If 'i' (current step) is greater than 'n' (the target step), we
            # have overshot our target step and therefore the function returns
            # 0, as the combination is not a valid path.
            if i > n:
                return 0
            # If 'i' (current step) is equal to 'n' (the target step), we have
            # reached our target step and therefore the function returns 1, as
            # the combination is a valid path.
            if i == n:
                return 1
            # If 'i' (current step) exists in 'memo', reuse its value. It
            # should be noted that we could also use an array here.
            if i in memo:
                return memo[i]
            memo[i] = _climbStairs(i + 1, n, memo) + _climbStairs(i + 2, n, memo)
            return memo[i]

        return _climbStairs(0, n, memo)


class BruteForceSolution:
    """
    The brute force solution calculates all the distinct ways to climb to the
    top by summing the ways to get to each step using either 1 or 2 steps.

    It should be noted that the time complexity of this solution is O(2^n),
    where 'n' is the number of steps.
    """

    def climbStairs(self, n: int) -> int:
        def _climbStairs(i: int, n: int) -> int:
            if i > n:
                return 0
            if i == n:
                return 1
            return _climbStairs(i + 1, n) + _climbStairs(i + 2, n)

        return _climbStairs(0, n)

grind.rip

From Grind Hell, with Love