2017-03-27 17:22:19 +08:00
{
" cells " : [
{
" cell_type " : " markdown " ,
" metadata " : { } ,
" source " : [
" This notebook was prepared by [Donne Martin](https://github.com/donnemartin). Source and license info is on [GitHub](https://github.com/donnemartin/interactive-coding-challenges). "
]
} ,
{
" cell_type " : " markdown " ,
" metadata " : { } ,
" source " : [
" # Solution Notebook "
]
} ,
{
" cell_type " : " markdown " ,
" metadata " : { } ,
" source " : [
" ## Problem: Given a list of stock prices on each consecutive day, determine the max profits with k transactions. \n " ,
" \n " ,
" * [Constraints](#Constraints) \n " ,
" * [Test Cases](#Test-Cases) \n " ,
" * [Algorithm](#Algorithm) \n " ,
" * [Code](#Code) \n " ,
" * [Unit Test](#Unit-Test) "
]
} ,
{
" cell_type " : " markdown " ,
" metadata " : { } ,
" source " : [
" ## Constraints \n " ,
" \n " ,
" * Is k the number of sell transactions? \n " ,
" * Yes \n " ,
" * Can we assume the prices input is an array of ints? \n " ,
" * Yes \n " ,
" * Can we assume the inputs are valid? \n " ,
" * No \n " ,
" * If the prices are all decreasing and there is no opportunity to make a profit, do we just return 0? \n " ,
" * Yes \n " ,
" * Should the output be the max profit and days to buy and sell? \n " ,
" * Yes \n " ,
" * Can we assume this fits memory? \n " ,
" * Yes "
]
} ,
{
" cell_type " : " markdown " ,
" metadata " : { } ,
" source " : [
" ## Test Cases \n " ,
" \n " ,
" <pre> \n " ,
" * Prices: None or k: None -> None \n " ,
" * Prices: [] or k <= 0 -> [] \n " ,
" * Prices: [0, -1, -2, -3, -4, -5] \n " ,
" * (max profit, list of transactions) \n " ,
" * (0, []) \n " ,
" * Prices: [2, 5, 7, 1, 4, 3, 1, 3] k: 3 \n " ,
" * (max profit, list of transactions) \n " ,
" * (10, [Type.SELL day: 7 price: 3, \n " ,
" Type.BUY day: 6 price: 1, \n " ,
" Type.SELL day: 4 price: 4, \n " ,
" Type.BUY day: 3 price: 1, \n " ,
" Type.SELL day: 2 price: 7, \n " ,
" Type.BUY day: 0 price: 2]) \n " ,
" </pre> "
]
} ,
{
" cell_type " : " markdown " ,
" metadata " : { } ,
" source " : [
" ## Algorithm \n " ,
" \n " ,
" We ' ll use bottom up dynamic programming to build a table. \n " ,
" \n " ,
" <pre> \n " ,
" \n " ,
" The rows (i) represent the prices. \n " ,
" The columns (j) represent the number of transactions (k). \n " ,
" \n " ,
" T[i][j] = max(T[i][j - 1], \n " ,
" prices[j] - price[m] + T[i - 1][m]) \n " ,
" \n " ,
" m = 0...j-1 \n " ,
" \n " ,
" 0 1 2 3 4 5 6 7 \n " ,
" -------------------------------------- \n " ,
" | | 2 | 5 | 7 | 1 | 4 | 3 | 1 | 3 | \n " ,
" -------------------------------------- \n " ,
" | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | \n " ,
" | 1 | 0 | 3 | 5 | 5 | 5 | 5 | 5 | 5 | \n " ,
" | 2 | 0 | 3 | 5 | 5 | 8 | 8 | 8 | 8 | \n " ,
" | 3 | 0 | 3 | 5 | 5 | 8 | 8 | 8 | 10 | \n " ,
" -------------------------------------- \n " ,
" \n " ,
" Optimization: \n " ,
" \n " ,
" max_diff = max(max_diff, \n " ,
" T[i - 1][j - 1] - prices[j - 1]) \n " ,
" \n " ,
" T[i][j] = max(T[i][j - 1], \n " ,
" prices[j] + max_diff) \n " ,
" \n " ,
" </pre> \n " ,
" \n " ,
" Complexity: \n " ,
" * Time: O(n * k) \n " ,
" * Space: O(n * k) "
]
} ,
{
" cell_type " : " markdown " ,
" metadata " : { } ,
" source " : [
" ## Code "
]
} ,
{
" cell_type " : " code " ,
" execution_count " : 1 ,
2020-07-14 09:26:50 +08:00
" metadata " : { } ,
2017-03-27 17:22:19 +08:00
" outputs " : [ ] ,
" source " : [
" from enum import Enum # Python 2 users: Run pip install enum34 \n " ,
" \n " ,
" \n " ,
" class Type(Enum): \n " ,
" SELL = 0 \n " ,
" BUY = 1 \n " ,
" \n " ,
" \n " ,
" class Transaction(object): \n " ,
" \n " ,
" def __init__(self, type, day, price): \n " ,
" self.type = type \n " ,
" self.day = day \n " ,
" self.price = price \n " ,
" \n " ,
" def __eq__(self, other): \n " ,
" return self.type == other.type and \\ \n " ,
" self.day == other.day and \\ \n " ,
" self.price == other.price \n " ,
" \n " ,
" def __repr__(self): \n " ,
" return str(self.type) + ' day: ' + \\ \n " ,
" str(self.day) + ' price: ' + \\ \n " ,
" str(self.price) "
]
} ,
{
" cell_type " : " code " ,
" execution_count " : 2 ,
2020-07-14 09:26:50 +08:00
" metadata " : { } ,
2017-03-27 17:22:19 +08:00
" outputs " : [ ] ,
" source " : [
" import sys \n " ,
" \n " ,
" \n " ,
" class StockTrader(object): \n " ,
" \n " ,
" def find_max_profit(self, prices, k): \n " ,
" if prices is None or k is None: \n " ,
" raise TypeError( ' prices or k cannot be None ' ) \n " ,
" if not prices or k <= 0: \n " ,
" return [] \n " ,
" num_rows = k + 1 # 0th transaction for dp table \n " ,
" num_cols = len(prices) \n " ,
" T = [[None] * num_cols for _ in range(num_rows)] \n " ,
" for i in range(num_rows): \n " ,
" for j in range(num_cols): \n " ,
" if i == 0 or j == 0: \n " ,
" T[i][j] = 0 \n " ,
" continue \n " ,
" max_profit = -sys.maxsize \n " ,
" for m in range(j): \n " ,
" profit = prices[j] - prices[m] + T[i - 1][m] \n " ,
" if profit > max_profit: \n " ,
" max_profit = profit \n " ,
" T[i][j] = max(T[i][j - 1], max_profit) \n " ,
" return self._find_max_profit_transactions(T, prices) \n " ,
" \n " ,
" def find_max_profit_optimized(self, prices, k): \n " ,
" if prices is None or k is None: \n " ,
" raise TypeError( ' prices or k cannot be None ' ) \n " ,
" if not prices or k <= 0: \n " ,
" return [] \n " ,
" num_rows = k + 1 \n " ,
" num_cols = len(prices) \n " ,
" T = [[None] * num_cols for _ in range(num_rows)] \n " ,
" for i in range(num_rows): \n " ,
" max_diff = prices[0] * -1 \n " ,
" for j in range(num_cols): \n " ,
" if i == 0 or j == 0: \n " ,
" T[i][j] = 0 \n " ,
" continue \n " ,
" max_diff = max( \n " ,
" max_diff, \n " ,
" T[i - 1][j - 1] - prices[j - 1]) \n " ,
" T[i][j] = max( \n " ,
" T[i][j - 1], \n " ,
" prices[j] + max_diff) \n " ,
" return self._find_max_profit_transactions(T, prices) \n " ,
" \n " ,
" def _find_max_profit_transactions(self, T, prices): \n " ,
" results = [] \n " ,
" i = len(T) - 1 \n " ,
" j = len(T[0]) - 1 \n " ,
" max_profit = T[i][j] \n " ,
" while i != 0 and j != 0: \n " ,
" if T[i][j] == T[i][j - 1]: \n " ,
" j -= 1 \n " ,
" else: \n " ,
" sell_price = prices[j] \n " ,
" results.append(Transaction(Type.SELL, j, sell_price)) \n " ,
" profit = T[i][j] - T[i - 1][j - 1] \n " ,
" i -= 1 \n " ,
" j -= 1 \n " ,
" for m in range(j + 1)[::-1]: \n " ,
" if sell_price - prices[m] == profit: \n " ,
" results.append(Transaction(Type.BUY, m, prices[m])) \n " ,
" break \n " ,
" return (max_profit, results) "
]
} ,
{
" cell_type " : " markdown " ,
" metadata " : { } ,
" source " : [
" ## Unit Test "
]
} ,
{
" cell_type " : " code " ,
" execution_count " : 3 ,
2020-07-14 09:26:50 +08:00
" metadata " : { } ,
2017-03-27 17:22:19 +08:00
" outputs " : [
{
" name " : " stdout " ,
" output_type " : " stream " ,
" text " : [
" Overwriting test_max_profit.py \n "
]
}
] ,
" source " : [
" %% writefile test_max_profit.py \n " ,
2020-07-14 09:26:50 +08:00
" import unittest \n " ,
2017-03-27 17:22:19 +08:00
" \n " ,
" \n " ,
2020-07-14 09:26:50 +08:00
" class TestMaxProfit(unittest.TestCase): \n " ,
2017-03-27 17:22:19 +08:00
" \n " ,
" def test_max_profit(self): \n " ,
" stock_trader = StockTrader() \n " ,
2020-07-14 09:26:50 +08:00
" self.assertRaises(TypeError, stock_trader.find_max_profit, None, None) \n " ,
" self.assertEqual(stock_trader.find_max_profit(prices=[], k=0), []) \n " ,
2017-03-27 17:22:19 +08:00
" prices = [5, 4, 3, 2, 1] \n " ,
" k = 3 \n " ,
2020-07-14 09:26:50 +08:00
" self.assertEqual(stock_trader.find_max_profit(prices, k), (0, [])) \n " ,
2017-03-27 17:22:19 +08:00
" prices = [2, 5, 7, 1, 4, 3, 1, 3] \n " ,
" profit, transactions = stock_trader.find_max_profit(prices, k) \n " ,
2020-07-14 09:26:50 +08:00
" self.assertEqual(profit, 10) \n " ,
" self.assertTrue(Transaction(Type.SELL, \n " ,
2017-03-27 17:22:19 +08:00
" day=7, \n " ,
" price=3) in transactions) \n " ,
2020-07-14 09:26:50 +08:00
" self.assertTrue(Transaction(Type.BUY, \n " ,
2017-03-27 17:22:19 +08:00
" day=6, \n " ,
" price=1) in transactions) \n " ,
2020-07-14 09:26:50 +08:00
" self.assertTrue(Transaction(Type.SELL, \n " ,
2017-03-27 17:22:19 +08:00
" day=4, \n " ,
" price=4) in transactions) \n " ,
2020-07-14 09:26:50 +08:00
" self.assertTrue(Transaction(Type.BUY, \n " ,
2017-03-27 17:22:19 +08:00
" day=3, \n " ,
" price=1) in transactions) \n " ,
2020-07-14 09:26:50 +08:00
" self.assertTrue(Transaction(Type.SELL, \n " ,
2017-03-27 17:22:19 +08:00
" day=2, \n " ,
" price=7) in transactions) \n " ,
2020-07-14 09:26:50 +08:00
" self.assertTrue(Transaction(Type.BUY, \n " ,
2017-03-27 17:22:19 +08:00
" day=0, \n " ,
" price=2) in transactions) \n " ,
" print( ' Success: test_max_profit ' ) \n " ,
" \n " ,
" \n " ,
" def main(): \n " ,
" test = TestMaxProfit() \n " ,
" test.test_max_profit() \n " ,
" \n " ,
" \n " ,
" if __name__ == ' __main__ ' : \n " ,
" main() "
]
} ,
{
" cell_type " : " code " ,
" execution_count " : 4 ,
2020-07-14 09:26:50 +08:00
" metadata " : { } ,
2017-03-27 17:22:19 +08:00
" outputs " : [
{
" name " : " stdout " ,
" output_type " : " stream " ,
" text " : [
" Success: test_max_profit \n "
]
}
] ,
" source " : [
" %r un -i test_max_profit.py "
]
}
] ,
" metadata " : {
" kernelspec " : {
" display_name " : " Python 3 " ,
" language " : " python " ,
" name " : " python3 "
} ,
" language_info " : {
" codemirror_mode " : {
" name " : " ipython " ,
" version " : 3
} ,
" file_extension " : " .py " ,
" mimetype " : " text/x-python " ,
" name " : " python " ,
" nbconvert_exporter " : " python " ,
" pygments_lexer " : " ipython3 " ,
2020-07-14 09:26:50 +08:00
" version " : " 3.7.2 "
2017-03-27 17:22:19 +08:00
}
} ,
" nbformat " : 4 ,
2020-07-14 09:26:50 +08:00
" nbformat_minor " : 1
2017-03-27 17:22:19 +08:00
}