def house_robber(houses: list[int]) -> int:
    """
    Solves the House Robber problem using memoization (caching).
    Returns the maximum amount of money that can be robbed without triggering alarms.
    Problem URL: https://leetcode.com/problems/house-robber/

    Args:
        houses: A list of integers representing the amount of money in each house.
    Returns:
        int: The maximum amount of money that can be robbed without triggering alarms.
    Raises:
        ValueError: If there are no houses in the input list.

    Examples:
        >>> house_robber([2, 3, 2])
        4
        >>> house_robber([1, 2, 3, 1])
        4
        >>> house_robber([0, 0, 0, 0])
        0
        >>> house_robber([10, 15, 20, 25])
        40
        >>> house_robber([50])
        50
        >>> house_robber([5, 15, 5, 15, 5])
        30
    """
    number_of_houses = len(houses)
    if number_of_houses == 0:
        raise ValueError("There must be at least one house")

    memo = [-1 for _ in range(number_of_houses)]

    def dp(n: int) -> int:
        if n >= number_of_houses:
            return 0
        # If the house has been visited before, avoid revisiting (Memoization)
        if memo[n] != -1:
            return memo[n]

        # Decide to rob this house and skip the next, or skip this one
        rob = houses[n] + dp(n + 2)
        dont_rob = dp(n + 1)

        memo[n] = max(rob, dont_rob)
        return memo[n]

    return dp(0)