diff --git a/dynamic_programming/minimum_tickets_cost.py b/dynamic_programming/minimum_tickets_cost.py new file mode 100644 index 000000000..261a5a7cf --- /dev/null +++ b/dynamic_programming/minimum_tickets_cost.py @@ -0,0 +1,129 @@ +""" +Author : Alexander Pantyukhin +Date : November 1, 2022 + +Task: +Given a list of days when you need to travel. Each day is integer from 1 to 365. +You are able to use tickets for 1 day, 7 days and 30 days. +Each ticket has a cost. + +Find the minimum cost you need to travel every day in the given list of days. + +Implementation notes: +implementation Dynamic Programming up bottom approach. + +Runtime complexity: O(n) + +The implementation was tested on the +leetcode: https://leetcode.com/problems/minimum-cost-for-tickets/ + + +Minimum Cost For Tickets +Dynamic Programming: up -> down. +""" + +from functools import lru_cache + + +def mincost_tickets(days: list[int], costs: list[int]) -> int: + """ + >>> mincost_tickets([1, 4, 6, 7, 8, 20], [2, 7, 15]) + 11 + + >>> mincost_tickets([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 30, 31], [2, 7, 15]) + 17 + + >>> mincost_tickets([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 30, 31], [2, 90, 150]) + 24 + + >>> mincost_tickets([2], [2, 90, 150]) + 2 + + >>> mincost_tickets([], [2, 90, 150]) + 0 + + >>> mincost_tickets('hello', [2, 90, 150]) + Traceback (most recent call last): + ... + ValueError: The parameter days should be a list of integers + + >>> mincost_tickets([], 'world') + Traceback (most recent call last): + ... + ValueError: The parameter costs should be a list of three integers + + >>> mincost_tickets([0.25, 2, 3, 4, 5, 6, 7, 8, 9, 10, 30, 31], [2, 90, 150]) + Traceback (most recent call last): + ... + ValueError: The parameter days should be a list of integers + + >>> mincost_tickets([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 30, 31], [2, 0.9, 150]) + Traceback (most recent call last): + ... + ValueError: The parameter costs should be a list of three integers + + >>> mincost_tickets([-1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 30, 31], [2, 90, 150]) + Traceback (most recent call last): + ... + ValueError: All days elements should be greater than 0 + + >>> mincost_tickets([2, 367], [2, 90, 150]) + Traceback (most recent call last): + ... + ValueError: All days elements should be less than 366 + + >>> mincost_tickets([2, 3, 4, 5, 6, 7, 8, 9, 10, 30, 31], []) + Traceback (most recent call last): + ... + ValueError: The parameter costs should be a list of three integers + + >>> mincost_tickets([], []) + Traceback (most recent call last): + ... + ValueError: The parameter costs should be a list of three integers + + >>> mincost_tickets([2, 3, 4, 5, 6, 7, 8, 9, 10, 30, 31], [1, 2, 3, 4]) + Traceback (most recent call last): + ... + ValueError: The parameter costs should be a list of three integers + """ + + # Validation + if not isinstance(days, list) or not all(isinstance(day, int) for day in days): + raise ValueError("The parameter days should be a list of integers") + + if len(costs) != 3 or not all(isinstance(cost, int) for cost in costs): + raise ValueError("The parameter costs should be a list of three integers") + + if len(days) == 0: + return 0 + + if min(days) <= 0: + raise ValueError("All days elements should be greater than 0") + + if max(days) >= 366: + raise ValueError("All days elements should be less than 366") + + days_set = set(days) + + @lru_cache(maxsize=None) + def dynamic_programming(index: int) -> int: + if index > 365: + return 0 + + if index not in days_set: + return dp(index + 1) + + return min( + costs[0] + dp(index + 1), + costs[1] + dp(index + 7), + costs[2] + dp(index + 30), + ) + + return dynamic_programming(1) + + +if __name__ == "__main__": + import doctest + + doctest.testmod()