diff --git a/labs/06-dynamic-programming-exercises.ipynb b/labs/06-dynamic-programming-exercises.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..0a593a200289f75a10c3a766cbeb35e40a06db0a --- /dev/null +++ b/labs/06-dynamic-programming-exercises.ipynb @@ -0,0 +1,546 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "2a3fec41", + "metadata": {}, + "source": [ + "NAME:" + ] + }, + { + "cell_type": "markdown", + "id": "a701f259-d48a-43a8-aa7d-6f77c3896e5e", + "metadata": {}, + "source": [ + "# UE5 Fundamentals of Algorithms\n", + "# Lab 6: Programming strategies: Dynamic programming" + ] + }, + { + "cell_type": "markdown", + "id": "6c24ca95-9c26-427c-ac1c-0a5f185140e9", + "metadata": {}, + "source": [ + "---" + ] + }, + { + "cell_type": "markdown", + "id": "cd8663e3-c5a5-41ec-b888-49bac1e82ef4", + "metadata": {}, + "source": [ + "<details style=\"border: 1px\">\n", + "<summary> How to use those notebooks</summary>\n", + " \n", + "For each of the following questions:\n", + "- In the `# YOUR CODE HERE` cell, remove `raise NotImplementedError()` to write your code\n", + "- Write an example of use of your code or make sure the given examples and tests pass\n", + "- Add extra tests in the `#Tests` cell\n", + " \n", + "</details>" + ] + }, + { + "cell_type": "markdown", + "id": "d2673591-dbe2-45f2-b24d-f21ae1186770", + "metadata": {}, + "source": [ + "## Exercise 1: Fibonacci\n", + "\n", + "Write the Fibonacci sequence $T(n) = T(n-1) + T(n-2)$ using dynamic programming, in particular the _memoization_ mechanism." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a0ca630b-5a69-4e32-8e89-91afaec02c3e", + "metadata": { + "deletable": false, + "nbgrader": { + "cell_type": "code", + "checksum": "14bdfcbc0dce21040b0a18ceb499f6fa", + "grade": false, + "grade_id": "cell-c248cd22cc3ba06b", + "locked": false, + "schema_version": 3, + "solution": true, + "task": false + }, + "tags": [] + }, + "outputs": [], + "source": [ + "def fibonacci(n, memo = {}):\n", + " # YOUR CODE HERE\n", + " raise NotImplementedError()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c7baa400-bd44-4e35-a3cc-1d44e1711af5", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "# Example of use\n", + "fibonacci(10)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3b629a14-7c08-49e1-8cce-8ad320066eec", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "# Tests\n", + "assert fibonacci(10) == 55" + ] + }, + { + "cell_type": "markdown", + "id": "a45c7015-d59b-4ab3-96f7-d5eaf26273fc", + "metadata": {}, + "source": [ + "## Exercise 2: Knapsack\n", + "\n", + "Propose a dynamic programming solution for the knapsack problem (as defined in the previous lab). Here is the general case (where the weights availability is 1, which means there can be any duplicate in the solution):\n", + "\n", + "$\n", + "dp[i][w] = \n", + "\\begin{cases}\n", + "0 & \\text{if } i = 0 \\text{ or } w = 0 \\\\\n", + "dp[i-1][w] & \\text{if } w_i > w \\\\\n", + "\\max(dp[i-1][w], w_i + dp[i-1][w - w_i]) & \\text{otherwise}\n", + "\\end{cases}$\n", + "\n", + "Your algorithm should return the weight the solution managed to reach, and the list of weights." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e77956b0-b286-4347-858d-81468f6a8017", + "metadata": { + "deletable": false, + "nbgrader": { + "cell_type": "code", + "checksum": "902fa8c81abad673763f5d33f68cdd86", + "grade": false, + "grade_id": "cell-f61939dd2c0306bd", + "locked": false, + "schema_version": 3, + "solution": true, + "task": false + }, + "tags": [] + }, + "outputs": [], + "source": [ + "def dynamic_knapsack(W, wt):\n", + " # YOUR CODE HERE\n", + " raise NotImplementedError()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d92f7880-6a1d-4b43-a37e-71ae9d92bb6d", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "# Example of use\n", + "weights = [2, 3, 4, 5]\n", + "max_weight = 5\n", + "dynamic_knapsack(max_weight, weights)" + ] + }, + { + "cell_type": "markdown", + "id": "d27f1afa-da6d-45dd-81c9-c99a8cdf1855", + "metadata": {}, + "source": [ + "Compare to the greedy version which solution is given below:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3c67509f-a10d-4452-9637-1592d6a3aea1", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "def greedy_knapsack(W, w):\n", + "\n", + " w.sort(reverse=True)\n", + " n = len(w)\n", + " remaining_items = list(range(n))\n", + "\n", + " total_weight = 0\n", + " knapsack = []\n", + "\n", + " while remaining_items and total_weight < W:\n", + " best_weight = float('inf')\n", + " best_item = None\n", + "\n", + " for i in remaining_items:\n", + " if w[i] < best_weight:\n", + " best_weight = w[i]\n", + " best_item = i\n", + "\n", + " if best_item is not None:\n", + " weight = w[best_item]\n", + " knapsack.append(weight)\n", + " total_weight += weight\n", + " remaining_items.remove(best_item)\n", + "\n", + " return total_weight, knapsack" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a12fc960-a59e-410d-976e-9c65f822cedc", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "assert greedy_knapsack(5, [2, 3, 4, 5]) == (5, [2, 3])" + ] + }, + { + "cell_type": "markdown", + "id": "de7cf7d9-6a75-49e4-a143-84c43174d68e", + "metadata": {}, + "source": [ + "## Exercise 3: Longest increasing subsequence\n", + "\n", + "Given an array of integers, write a function to find the length of the longest increasing subsequence. For intance, given the sequence `[10, 9, 2, 5, 3, 7, 101, 18]` the result is `[2, 3, 7, 101]` (size `4`). Note that the increasing numbers are not necessarily immediately consecutive." + ] + }, + { + "cell_type": "markdown", + "id": "341924a7-5e56-4ead-8fb0-921cb7edf935", + "metadata": {}, + "source": [ + "Write a dynamic programming approach by following those steps:\n", + "\n", + "1. Create a `dp` array of size \\(n\\), where \\(dp[i]\\) represents the length of the longest incresing sequence ending at index \\(i\\)\n", + "2. Initialize all values in `dp` to 1, since the longest incresing sequence at each index is at least the element itself\n", + "3. For each pair of indices \\(i\\) and \\(j\\) where \\(j < i\\):\n", + " - If \\(nums[j] < nums[i]\\), update \\(dp[i] = max(dp[i], dp[j] + 1)\\)\n", + "4. The length of the longest increasing sequence is the maximum value in the `dp` array" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e09b072d-78f3-4ce1-9203-c85cf0e464ae", + "metadata": { + "deletable": false, + "nbgrader": { + "cell_type": "code", + "checksum": "f8e8fb911dc852a5217bd2927101c4ce", + "grade": false, + "grade_id": "cell-1b6f016aceaf66f0", + "locked": false, + "schema_version": 3, + "solution": true, + "task": false + }, + "tags": [] + }, + "outputs": [], + "source": [ + "def longest_dynprog(L):\n", + " # YOUR CODE HERE\n", + " raise NotImplementedError()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8d29b1e7-d541-4363-94cf-e928944d019f", + "metadata": {}, + "outputs": [], + "source": [ + "L = [10, 9, 2, 5, 3, 7, 101, 18]\n", + "assert longest_dynprog(L) == 4" + ] + }, + { + "cell_type": "markdown", + "id": "358e492b-9a9b-499f-a49b-6a90c530b0fa", + "metadata": { + "tags": [] + }, + "source": [ + "Finally, you may implement a more efficient solution using binary search like explained [in this book](https://cp-algorithms.com/sequences/longest_increasing_subsequence.html).\n", + "\n", + "First write a binary search function:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "64570eb7-a5e6-4282-9b41-730227337658", + "metadata": { + "deletable": false, + "nbgrader": { + "cell_type": "code", + "checksum": "d680f22270b7acba59aa5a0463f34144", + "grade": false, + "grade_id": "cell-aae9cefd2fa1258a", + "locked": false, + "schema_version": 3, + "solution": true, + "task": false + }, + "tags": [] + }, + "outputs": [], + "source": [ + "def binary_search(sub, target):\n", + " # YOUR CODE HERE\n", + " raise NotImplementedError()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "dcd76d9f-2f4d-40fb-8ca6-0ce3acc40070", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "assert binary_search([1, 2, 3], 2) == [1, 2, 3].index(2)" + ] + }, + { + "cell_type": "markdown", + "id": "4165d800-4a37-4d8c-99f0-f55f995c747f", + "metadata": {}, + "source": [ + "Now write a function that uses the above binary search function to return the longest sequence:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "418a8882-8d04-4ef6-9718-307a4056d800", + "metadata": { + "deletable": false, + "nbgrader": { + "cell_type": "code", + "checksum": "8dd94e438931ebb199550c584ab97802", + "grade": false, + "grade_id": "cell-1dea7220610c9dcb", + "locked": false, + "schema_version": 3, + "solution": true, + "task": false + }, + "tags": [] + }, + "outputs": [], + "source": [ + "def longest_binary(L):\n", + " # YOUR CODE HERE\n", + " raise NotImplementedError()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b6b27c2b-5758-496c-975b-a732e71d1791", + "metadata": {}, + "outputs": [], + "source": [ + "L = [10, 9, 2, 5, 3, 7, 101, 18]\n", + "assert longest_binary(nums) == 4\n" + ] + }, + { + "cell_type": "markdown", + "id": "46eedf29-dac1-4373-9f2c-98275cd749b2", + "metadata": {}, + "source": [ + "## Exercise 4: Find a target sum\n", + "\n", + "You are given a list of integers `L` and a target sum `t`. You need to return the number of different ways you can assign a + or - sign to each number in the list such that the sum equals target.\n", + "\n", + "\n", + "```\n", + "L = [1, 1, 1, 1, 1]\n", + "t = 3\n", + "```\n", + "\n", + "There are 5 possible solutions (`[1, 1, -1, 1, 1]`, etc)." + ] + }, + { + "cell_type": "markdown", + "id": "89555ffc-d9b1-4386-a278-4a583dca2bdc", + "metadata": { + "tags": [] + }, + "source": [ + "Generate a brute force approach like in the previous lab by using the `product` method." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7f98f9bb-d1c5-46c7-9ce8-6ccee5087e69", + "metadata": { + "deletable": false, + "nbgrader": { + "cell_type": "code", + "checksum": "317e34d49cca07e3bcd39f53da969e95", + "grade": false, + "grade_id": "cell-d4038eeafd2e1851", + "locked": false, + "schema_version": 3, + "solution": true, + "task": false + }, + "tags": [] + }, + "outputs": [], + "source": [ + "from itertools import product\n", + "\n", + "def find_target_brut(L, target):\n", + " # YOUR CODE HERE\n", + " raise NotImplementedError()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "efd683c1-1be1-4bf3-a424-f59cbfdd1160", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "L = [1, 1, 1, 1, 1]\n", + "t = 3\n", + "assert find_target_brut([1, 1, 1, 1, 1], t) == 5" + ] + }, + { + "cell_type": "markdown", + "id": "ee303057-a1c2-47cd-a9b3-8f11747e1b3e", + "metadata": {}, + "source": [ + "\n", + "Now write a dynamic programming solution by following the instructions below:\n", + "\n", + "Before proceeding, check for two conditions that would make it impossible to achieve the target:\n", + "\n", + "1. If the total sum \\( s \\) is less than the target, it's impossible to form a subset equal to the target\n", + "2. If \\( s - target \\) is odd, you cannot split the array into two subsets with integer sums that have the required difference\n", + "\n", + "Next, initialize a dynamic programming (dp) array of size \\( n + 1 \\) with all values set to zero, except for `dp[0]`, which is set to 1. This means there is one way to achieve a sum of zero (by choosing no number).\n", + "\n", + "Then, iterate through each number \\( v \\) in `L`. For each number, iterate backward through the dp array from \\( n \\) down to \\( v \\) to avoid recomputing values that rely on the current index. This ensures that we don't count the same combination more than once.\n", + "\n", + "Update the dp array by adding the number of ways to achieve a sum of \\( j \\) without including the current number \\( v \\) (i.e., `dp[j - v]`). This accumulates the number of ways to form a sum of \\( j \\) by either including or excluding the current number \\( v \\)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "49c42188-c753-46dc-a8c7-8de377b9014b", + "metadata": { + "deletable": false, + "nbgrader": { + "cell_type": "code", + "checksum": "7b66206a9db8da90c205ade12661f06e", + "grade": false, + "grade_id": "cell-9eacab97fa9220e4", + "locked": false, + "schema_version": 3, + "solution": true, + "task": false + }, + "tags": [] + }, + "outputs": [], + "source": [ + "def find_target_dynprog(L, target):\n", + " # YOUR CODE HERE\n", + " raise NotImplementedError()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c1585467-2783-460c-b6ac-0cf2252112b8", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "# Example usage\n", + "find_target_dynprog([1, 1, 1, 1, 1], t) # 5" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d60099d5-335d-494b-bb8a-3cbada63bf94", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "# Tests\n", + "L = [1, 1, 1, 1, 1]\n", + "t = 3\n", + "assert find_target_dynprog([1, 1, 1, 1, 1], t) == 5" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "500075c4-8084-41c3-aa1d-b98a7137c1e3", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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", + "version": "3.10.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}