From 66f332c35666164fc17dd4333a306d3137a14bd8 Mon Sep 17 00:00:00 2001 From: Jee Gikera Date: Thu, 25 Feb 2021 16:11:44 +0300 Subject: [PATCH] [chore] add markdown versions of DP notebooks --- dynamic-programming/disk_stacking.md | 76 ++++++++++++ dynamic-programming/fibonacci.md | 46 +++++++ dynamic-programming/find_denominations.md | 56 +++++++++ dynamic-programming/kadanes_algorithm.md | 66 ++++++++++ dynamic-programming/knapsack.md | 92 ++++++++++++++ dynamic-programming/levenshtein_distance.md | 102 ++++++++++++++++ .../longest_common_subsequence.md | 53 ++++++++ .../max_sum_increasing_subsequence.md | 77 ++++++++++++ dynamic-programming/max_sum_no_adjacent.md | 78 ++++++++++++ dynamic-programming/min_jumps_to_end.md | 48 ++++++++ .../minimum_number_of_coins__for_change.md | 56 +++++++++ .../no_of_ways_to_make_change.md | 39 ++++++ dynamic-programming/throw_dice.md | 115 ++++++++++++++++++ 13 files changed, 904 insertions(+) create mode 100644 dynamic-programming/disk_stacking.md create mode 100644 dynamic-programming/fibonacci.md create mode 100644 dynamic-programming/find_denominations.md create mode 100644 dynamic-programming/kadanes_algorithm.md create mode 100644 dynamic-programming/knapsack.md create mode 100644 dynamic-programming/levenshtein_distance.md create mode 100644 dynamic-programming/longest_common_subsequence.md create mode 100644 dynamic-programming/max_sum_increasing_subsequence.md create mode 100644 dynamic-programming/max_sum_no_adjacent.md create mode 100644 dynamic-programming/min_jumps_to_end.md create mode 100644 dynamic-programming/minimum_number_of_coins__for_change.md create mode 100644 dynamic-programming/no_of_ways_to_make_change.md create mode 100644 dynamic-programming/throw_dice.md diff --git a/dynamic-programming/disk_stacking.md b/dynamic-programming/disk_stacking.md new file mode 100644 index 0000000..e0d274e --- /dev/null +++ b/dynamic-programming/disk_stacking.md @@ -0,0 +1,76 @@ +### Disk stacking +An array's element represents a disk. Each element has `[length, width, height]` +Write a function that will stack the disks and build a tower with the greatest height, such that each disk has strictly lower dimensions than those below it. + +Sample input +``` +array = [[2, 1, 2], [3, 2, 3], [2, 2, 8], [2, 3, 4], [1, 3, 1], [4, 4, 5]] +``` + +Sample output: +``` +[[4, 4, 5], [3, 2, 3], [2, 1, 2]] + +The tower looks something like this + + [2, 1, 2] + [ 3, 2, 3 ] + [4, 4, 5] +``` + + + +```python +def diskStacking(disks): + """O(n) space, O(n^2) time""" + # sort based on height + disks.sort(key=lambda disk: disk[2]) + diskHeights = [disk[2] for disk in disks] + sequences = [None for disk in disks] + + # maxHeightIndex + maxHeightIndex = 0 + + for i in range(1, len(disks)): + currentDisk = disks[i] + for j in range(0, i): + otherDisk = disks[j] + # validate dimensions + if areValidDimensions(otherDisk, currentDisk): + if diskHeights[i] <= currentDisk[2] + diskHeights[j]: + diskHeights[i] = currentDisk[2] + diskHeights[j] + sequences[i] = j + # update maxHeightIndex: which will help us backtrack to find the disks involved. + if diskHeights[i] >= diskHeights[maxHeightIndex]: + maxHeightIndex = i + return buildSequence(disks, sequences, maxHeightIndex) + +def buildSequence(array, sequences, currentIdx): + results = [] + while currentIdx is not None: + results.append(disks[currentIdx]) + currentIdx = sequences[currentIdx] + return results + +def areValidDimensions(o, c): + return o[0] < c[0] and o[1] < c[1] and o[2] < c[2] + +``` + + +```python +disks = [[2, 1, 2], [3, 2, 3], [2, 2, 8], [2, 3, 4], [1, 3, 1], [4, 4, 5]] +diskStacking(disks) +``` + + + + + [[4, 4, 5], [3, 2, 3], [2, 1, 2]] + + + + +```python + +``` diff --git a/dynamic-programming/fibonacci.md b/dynamic-programming/fibonacci.md new file mode 100644 index 0000000..9ef9d8e --- /dev/null +++ b/dynamic-programming/fibonacci.md @@ -0,0 +1,46 @@ +```python +def fibonacci(n): + dp = [0, 1] + for i in range(2, n + 1): + dp.append(dp[i - 1] + dp[i - 2]) + return dp[n] +``` + + +```python +def fibo(n, memoize={1:0,2:1}): + if n < 2: + return n + + memoize[n] = fibo(n - 1, memoize) + fibo(n - 2, memoize) + return memoize[n] +``` + + +```python +fibo(5) +``` + + + + + 5 + + + + +```python +fibonacci(6) +``` + + + + + 8 + + + + +```python + +``` diff --git a/dynamic-programming/find_denominations.md b/dynamic-programming/find_denominations.md new file mode 100644 index 0000000..b1e2da0 --- /dev/null +++ b/dynamic-programming/find_denominations.md @@ -0,0 +1,56 @@ +### Problem +You are given an array of length N, where each element i represents the number of ways we can produce i units of change. For example, [1, 0, 1, 1, 2] shows that there is only one way to make [0, 2, or 3] units, and 2 ways of making 4 units. + +Given such an array, determine the denominations that must be in use. In the case above, there must be coins with value 2, 3, and 4. + +### Approach + +There are two situation that will make the value at that index i be incremented: +1. Coin `i` is one of the denominations +2. Another coin `j` is one of the denominations and the value of array[i - j] is also non-zero + +For each index: +- Check the lower denominations j that are known to be part of the solution. +- Each time we find `i - j` to be non-zero, we have encountered one of the ways of getting to element `i`, so we decrement the value at that index by 1. +- If after going through all coins and the value at that index is still positive, we know that we must include `i` as part of the solution + +We must take note not to double count. For example, when `i == 7` and [3, 4] are in our solution set, that is one way of producing 7. +When two coins sum up to an index, only the lower one cause us to decrement the value at that index. + + + + +```python +def find_denominations(array): + coins = set() + # start from index 1, and start counting from 1 (not zero-based index) + for i, val in enumerate(array[1:], 1): + if val > 0: + for j in coins: + if array[i - j] > 0 and (i - j not in coins or i - j <= j): + val -= 1 + if val > 0: + coins.add(i) + return coins +``` + + +```python +find_denominations([1, 0, 1, 1, 2]) +``` + + + + + {2, 3, 4} + + + +The time complexity of this algorithm is O(M * N), where M is the number of coins in our solution and N is the length of our input. + +The space complexity will be O(M), since we require extra space to store the set of solution coins. + + +```python + +``` diff --git a/dynamic-programming/kadanes_algorithm.md b/dynamic-programming/kadanes_algorithm.md new file mode 100644 index 0000000..a1c13f7 --- /dev/null +++ b/dynamic-programming/kadanes_algorithm.md @@ -0,0 +1,66 @@ +### Problem +Given an array of integers, find the largest possible sum of contiguous subarray within the given array. + + +```python +## solution +def kadanes_algorithm(array): + """Complexity: O(n) time | O(1) space""" + + local_max = global_max = array[0] + for i in range(1, len(array)): + # the local maximum for an index is always => max(array[i], local_max of array[i - 1]) + local_max = max(array[i], array[i] + local_max) + global_max = max(global_max, local_max) + return global_max +``` + + +```python +array = [-3, 4, -1, 2, 1, -5, 4, -12, 3, -7] +kadanes_algorithm(array) +``` + + + + + 6 + + + + +```python +## unit tests to validate solution +import unittest + +class KadenesTestCase(unittest.TestCase): + def test_case_1(self): + array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + expected_sum = 55 + self.assertEqual(kadanes_algorithm(array), expected_sum) + + def test_case_2(self): + array = [-2, 1, -3, 4, -1, 2, 1, -5, 4] + expected_sum = 6 + self.assertEqual(kadanes_algorithm(array), expected_sum) + +if __name__ == "__main__": + unittest.main(argv=['first-arg-is-ignored'], exit=False) +``` + + +```python +kadanes_algorithm([1, -1, 2, 3, -6]) +``` + + + + + 5 + + + + +```python + +``` diff --git a/dynamic-programming/knapsack.md b/dynamic-programming/knapsack.md new file mode 100644 index 0000000..d6cde93 --- /dev/null +++ b/dynamic-programming/knapsack.md @@ -0,0 +1,92 @@ +## Knapsack Problem +You are given an array of arrays. Each subarray in this array contains two integer elements: the first represents an item value, the second an item's weight. + +You are also given an integer representing the maximum capacity of the knapsack that you have. + +Your goal is to fit items that will collectively fit within the knapsack's capacity while maximizing on their combined value. + +Write a function that returns the maximum combined value of the items you'd pick, as well as an array of the item indices picked. + +Sample Input: [[1,2],[4,3],[5,6],[6,7]], 10 +Sample Output: 10, [1, 3] + + + + +```python +def knapsack(items, capacity): + """O(nc) time | O(nc) space, where n == number of items, c == capacity""" + knapsack_values = [[0 for col in range(capacity + 1)] for row in range(len(items) + 1)] + + for row in range(1, len(items) + 1): + value = items[row - 1][0] + weight = items[row - 1][1] + for col in range(capacity + 1): + if weight > col: # col here represents a given capacity + knapsack_values[row][col] = knapsack_values[row - 1][col] + else: + knapsack_values[row][col] = max( + knapsack_values[row - 1][col], + knapsack_values[row - 1][col - weight] + value + ) + + return [knapsack_values[-1][-1], backtrack_items(knapsack_values, items)] + +def backtrack_items(values, items): + """ + Backtracks from the bottom right of the 2d array, finding all the items that were put in the knapsack. + """ + results = [] + row = len(values) - 1 + col = len(values[0]) - 1 + + while row > 0: + if values[row][col] == values[row - 1][col]: + row -= 1 + else: + results.append(row - 1) + col -= items[row - 1][1] + row -= 1 + return list(reversed(results)) +``` + + +```python +# test it out +capacity = 10 +items = [[1, 3], [4, 3], [5, 6], [6, 7]] + +knapsack(items, capacity) +``` + + + + + [10, [1, 3]] + + + +### Unit Tests + + +```python +# unit tests +import unittest + +class KnapsackTestCase(unittest.TestCase): + def test_case_1(self): + capacity = 10 + items = [[1, 3], [4, 3], [5, 6], [6, 7]] + expected = [10, [1, 3]] + self.assertEqual(knapsack(items, capacity), expected) + + def test_case_when_capacity_is_zero(self): + capacity = 0 + items = [[1, 3], [4, 3], [5, 6], [6, 7]] + expected = [0, []] + self.assertEqual(knapsack(items, capacity), expected) + + +if __name__ == "__main__": + unittest.main(argv=[""], exit=False) +``` diff --git a/dynamic-programming/levenshtein_distance.md b/dynamic-programming/levenshtein_distance.md new file mode 100644 index 0000000..1321d50 --- /dev/null +++ b/dynamic-programming/levenshtein_distance.md @@ -0,0 +1,102 @@ +```python +""" +Write a function that takes in two strings and returns the minimal number of edit +operations that need to be performed on the first string to get the second string. +Edit Operations include: insertion, deletion and substitition. + + +Sample input: str1: "abc", str2: "yabd" +Sample output: 2 +Explanation: insert "y", substitute: "c" for "d" + +""" + +def levenshtein_distance(str1, str2): + """ + Complexity: + Time: O(nm) + Space: O(nm), We can use dynamic programming to improve on the space + complexity. + """ + edits = [[col for col in range(len(str2) + 1)] for row in range(len(str1) + 1)] + # [ + # [0, 1, 2, 3, 4] + # [ ... ] + # ] + + + for row in range(1, len(str1) + 1): + # make the first value of each row to be 1, 2, 3, 4, ... + # [ [0, 1, 2, ..], + # [1, ...] + # [2, ...] + # [3, ...] ] + + edits[row][0] = edits[row - 1][0] + 1 + + # iterate through the 2 dimensional array + for row in range(1, len(str1) + 1): + for col in range(1, len(str2) + 1): + if str1[row - 1] == str2[col - 1]: + edits[row][col] = edits[row - 1][col - 1] + else: + edits[row][col] = 1 + min( + edits[row - 1][col], + edits[row][col - 1], + edits[row - 1][col - 1] + ) + + # return the bottom right value of the two-dim array + return edits[-1][-1] + + +def levenshtein_distance_efficient(str1, str2): + """ + Complexity: + Time: O(nm) where n = length of str1, and m = length of str2 + Space: O(min(m, n)) + """ + small = str1 if len(str1) < len(str2) else str2 + big = str1 if len(str1) >= len(str2) else str2 + + even_edits = [x for x in range(len(small) + 1)] + odd_edits = [None for x in range(len(small) + 1)] + + for i in range(len(big) + 1): + if i % 2 == 1: + # current edits are odd edits + current_edits, previous_edits = odd_edits, even_edits + else: + # curent_edits are even edits + current_edits, previous_edits = even_edits, odd_edits + + # for the first element, make it each equal to i + current_edits[0] = i + + for j in range(1, len(small) + 1): + if big[i - 1] == small[j - 1]: + current_edits[j] = previous_edits[j - 1] + else: + current_edits[j] = 1 + min( + previous_edits[j - 1], previous_edits[j], current_edits[j - 1] + ) + return even_edits[-1] if len(big) % 2 == 0 else odd_edits[-1] + +``` + + +```python +levenshtein_distance("abc", "yabd") +``` + + + + + 2 + + + + +```python + +``` diff --git a/dynamic-programming/longest_common_subsequence.md b/dynamic-programming/longest_common_subsequence.md new file mode 100644 index 0000000..e60e92a --- /dev/null +++ b/dynamic-programming/longest_common_subsequence.md @@ -0,0 +1,53 @@ +## Problem + +Write a function that takes two strings and returns their longest common subsequence. + +A subsequence is a set of characters in a string that aren't necessarily adjacent to each other but are in the same order that they appear in the string. + +For example, given string `abcd`, `acd` is a subsequence. + + + + +```python +def longestCommonSubsequence(str1, str2): + m = len(str1) + n = len(str2) + + # create a 2-dim array from the strings + array = [[[] for j in range(n + 1)] for i in range(m + 1)] + + # iterate through the 2D array + for i in range(1, m + 1): + for j in range(1, n + 1): + # compare the string characters together for equality + if str1[i - 1] == str2[j - 1]: + # Concatenate A[i - 1][j - 1] with A[i][j] + array[i][j] = array[i - 1][j - 1] + [str1[i - 1]] + else: + # A[i][j] = Max(A[i-1][j], A[i][j-1]) + array[i][j] = max(array[i - 1][j], array[i][j - 1], key=len) + # return the bottom-rightmost element of the 2D array + return array[-1][-1] + + # O(M * N ) * O(min(m, n)) ===> O(MN * min(M, N)) == Space complexity + + +``` + + +```python +longestCommonSubsequence("ZXVVYXW", "XKYKZFW") +``` + + + + + ['X', 'Y', 'W'] + + + + +```python + +``` diff --git a/dynamic-programming/max_sum_increasing_subsequence.md b/dynamic-programming/max_sum_increasing_subsequence.md new file mode 100644 index 0000000..c9a2761 --- /dev/null +++ b/dynamic-programming/max_sum_increasing_subsequence.md @@ -0,0 +1,77 @@ +### Max Sum Increasing Subsequence +Write a function that takes in a non-empty array of integers and returns the greatest sum that can be generated from a strictly increasing subsequence in the array, as well as an array of the numbers in that sequence. + +A subsequence of an array is a set of numbers that aren't necessarily adjacent to each other in the array but that are in the same order as they appear in the array. + +For instance, [1, 2, 4] form a subsequence of array [1, 5, 2, 0, 4]. + +Sample input: +```python +array = [1, 7, 2, 3, 5, 1, 3] +``` + +Sample output: +```python +[11, [1, 2, 3, 5]] +``` + + + +```python +def maxSumIncreasingSubsequence(array): + """ + We'll use DP to create an array of same length to store maxsum generated by all + elements before a given index, including the value on the index. + Then, we'll keep track of potential sequences in another array. Here, we save the index + of the previous element that contributed to the maxsum at the present index's position. + + O(n^2) time | O(n) space + + """ + sums = [num for num in array] + sequences = [None for i in array] + + # store the index that contains the max sum + maxSumIndex = 0 + for i in range(len(array)): + for j in range(0, i): + currentSum = sums[j] + array[i] + if array[j] < array[i] and currentSum >= sums[i]: + sums[i] = currentSum + # store the position of the index that has influenced the current sum + sequences[i] = j + # update the maxSumIndex if a bigger sum exists in sums array + maxSumIndex = i if sums[i] >= sums[maxSumIndex] else maxSumIndex + + return [sums[maxSumIndex], buildSequence(array, sequences, maxSumIndex)] + +``` + + +```python +def buildSequence(array, sequences, currentIndex): + """Backtrack while appending the values of the indices we saved""" + sequence = [] + while currentIndex is not None: + sequence.append(array[currentIndex]) + currentIndex = sequences[currentIndex] + return list(reversed(sequence)) +``` + + +```python +array = [1, 7, 2, 3, 5, 1, 3] +maxSumIncreasingSubsequence(array) +``` + + + + + [11, [1, 2, 3, 5]] + + + + +```python + +``` diff --git a/dynamic-programming/max_sum_no_adjacent.md b/dynamic-programming/max_sum_no_adjacent.md new file mode 100644 index 0000000..77f2aa2 --- /dev/null +++ b/dynamic-programming/max_sum_no_adjacent.md @@ -0,0 +1,78 @@ +### Max sum no adjacent +Given a array of integers, find the maximum sum of non-adjacent elements in the array, returning the sum. If the sum can't be generated, return 0. + +``` +Sample input: [1, 2, 4, 20] +Sample output: 22 +``` + + +```python +def maxSubsetNoAdjacent(array): + """Clone the input array and call it maxSum. + Then find the max sum generated from index 0 to the current index, storing them at those indices. Formula: maxSum[i] = max(maxSum[i - 1], maxSum[i - 2] + current_index_value)). + Finally, return the last index. (this will have the maximum sum stored) + + O(n) time | O(n) space, where n == length of input array + """ + if len(array) == 0: + return 0 + elif len(array) == 1: + return A[0] + + max_sums = array[:] + max_sums[1] = max(max_sums[0], max_sums[1]) + + for i in range(2, len(array)): + max_sums[i] = max(max_sums[i - 1], max_sums[i - 2] + array[i]) + return max_sums[-1] +``` + + +```python +A = [1, 2, 3, 4] +maxSubsetNoAdjacent(A) +``` + + + + + 6 + + + +Instead of creating an entire array, we can reduce the problem to `O(1) space` by keeping track of the last two values only. + + +```python +def max_subset_no_adjacent(A): + """O(n) time | O(1) space.""" + if len(A) == 0: + return 0 + elif len(A) == 1: + return A[0] + previous = A[0] + max_sum = max(A[0], A[1]) + for i in range(2, len(A)): + current = max(max_sum, previous + A[i]) + previous = max_sum + max_sum = current + return max_sum +``` + + +```python +max_subset_no_adjacent(A) +``` + + + + + 6 + + + + +```python + +``` diff --git a/dynamic-programming/min_jumps_to_end.md b/dynamic-programming/min_jumps_to_end.md new file mode 100644 index 0000000..7c1aba1 --- /dev/null +++ b/dynamic-programming/min_jumps_to_end.md @@ -0,0 +1,48 @@ +### Problem +Given an array of positive integers, find the minimum number of jumps required to get from the first index to the final one. + +Sample input: +``` +array = [4, 2, 1, 1, 3, 1, 2, 1] +``` + +Sample output: +``` +2 (4 --> 3 --> 1) +``` + +Note that jumping from index `i` to index `i + X` is still one jump, regardless of the size of X + +We build a new array to store minimum number of jumps from index 0 to rest of indices. + +The first is 0. (since step required to jump from an index to itself is zero) + +Progressively build the array using the previously computed min jumps. + + +```python +def minJumps(array): + """O(n) space | O(n^2) time, since for every index, we are checking all elements to its left""" + + jumps = [float('inf') for i in array] + jumps[0] = 0 + for i in range(1, len(array)): + for j in range(0, i): + # check if value before i (array[j]), if the step j is added to it, will it exceed i + if array[j] + j >= i: + jumps[i] = min(jumps[i], jumps[j] + 1) + # the last element contains the min jumps required to reach the end of array + return jumps[-1] +``` + + +```python +minJumps([4, 2, 1, 1, 3, 1, 2, 1]) +``` + + + + + 2 + + diff --git a/dynamic-programming/minimum_number_of_coins__for_change.md b/dynamic-programming/minimum_number_of_coins__for_change.md new file mode 100644 index 0000000..c344723 --- /dev/null +++ b/dynamic-programming/minimum_number_of_coins__for_change.md @@ -0,0 +1,56 @@ +#### Problem +Given an array of positive integers denoting coin denominations and a single non-negative integer representing a target amount of money, + +implement a function to find the minimum number of coins needed to make change for the target using the given coin denominations. + +If it's impossible to make change for the target amount, return -1. + +Sample input: 7, [1, 2, 5] denominations + +Sample output: 2 (coins) i.e (1x2, 5x1) + + +```python +# solution +def min_change(target, denominations): + """ + Complexity: + Space: O(N), where N == size of array obtained from the target + Time: 0(N * d), where d == number of denominations provided. + """ + if target == 0: + return 0 + + coin_list = [float('inf') for i in range(target + 1)] + # make the first index value to be zero + coin_list[0] = 0 + + # for each denomination given + for denom in denominations: + # iterate through the coin list, + # updating each value to be the minimum num of coins required to make change for the respective index + for amount in range(len(coin_list)): + if denom <= amount: + coin_list[amount] = min(coin_list[amount], coin_list[amount - denom] + 1) + + return coin_list[target] if coin_list[target] != float('inf') else -1 + +``` + + +```python +# run it +min_change(7, [1, 2, 5]) +``` + + + + + 2 + + + + +```python + +``` diff --git a/dynamic-programming/no_of_ways_to_make_change.md b/dynamic-programming/no_of_ways_to_make_change.md new file mode 100644 index 0000000..347fa7c --- /dev/null +++ b/dynamic-programming/no_of_ways_to_make_change.md @@ -0,0 +1,39 @@ +### Problem: Number of ways to make change +Given a list of integers representing denominations and an integer representing a target amount of money, create a function that returns the number of ways to make change for that target value using the denominations. +Note that an unlimited amount of coins is at your disposal. + + +```python +## solution +def make_change(n, denoms): + """ + Complexity: + O(nd) time, where n = target amount, and d is the number of denominations + O(n) space + """ + # set a list of zeros, before we begin filling up their respective number of ways to make change + ways = [0 for i in range(n + 1)] + + # base case: if we have zero target amount to make its change, only one way exists: not doing anything --> so it becomes 1, + ways[0] = 1 + for denom in denoms: + for amount in range(1, n + 1): + # check to see if the denom is less than amount, because we can't use a large denomination to make change for a smaller amount + if denom <= amount: + # add the current number of ways there are to the number of ways to generate that amount minus the denomnication + ways[amount] += ways[amount - denom] + return ways[n] +``` + + +```python +make_change(5, [1, 5]) + +``` + + + + + 2 + + diff --git a/dynamic-programming/throw_dice.md b/dynamic-programming/throw_dice.md new file mode 100644 index 0000000..c3ac5b5 --- /dev/null +++ b/dynamic-programming/throw_dice.md @@ -0,0 +1,115 @@ +### Problem + +Write a function, `throw_dice(N, faces, total)`, that determines how many ways it is possible to throw N dice with some number of faces each to get a specific total. + +For example, throw_dice(3, 6, 7) should return 15. + +### Approach +For one dice, there are only two posibilities: +1. If the total is less than the faces in the dice, then there's **`1 way`** to get the total. +2. If the total is greater than the faces, it's impossible, so there's **0** ways + +For two or more die, we find all the possible outcomes for rolling one dice first. + +THEN, for every new dice, we calculate all the ways it could add to the total, by adding each of its faces to the cumulative total of the previous dice. + +We can implement this recursively in O(M^N) time. + + +```python +def throw_dice_recursively(n, faces, total): + if n == 1: + return 1 if total <= faces else 0 + + ways = 0 + for face in range(1, min(total, faces + 1)): + ways += throw_dice_recursively(n - 1, faces, total - face) + + return ways +``` + + +```python +throw_dice_recursively(3, 6, 7) +``` + + + + + 15 + + + +### Dynamic Programming Approach + +We can optimize our previous solution by using dynamic programming. + +We create a 2-dimensional array to store number of ways that `N dice` can contribute to the given total. + +We know the solution for one dice. `No.of ways == 1 if faces is <= total else 0`. We'll update the first row of the matrix to reflect this. + +```python +# Given a total of 7, +# Our matrix will look like this: + +ways = [ + [0, 1, 1, 1, 1, 1, 1, 0] # dice 1 (Last 0 in row because we can't get a 7 from a single dice) + [0 ,0, 0, 0, 0, 0, 0, 0] # dice 2 + [0 ,0, 0, 0, 0, 0, 0, 0] # dice 3 + [0 ,0, 0, 0, 0, 0, 0, 0] # dice N + ] +We add a single column at the beginning, to cater for a total of zero. +``` + +We then loop through each row(each representing a single dice), and through each column (representing potential totals), and through each face value, we accumulate the ways to get to the final total by adding a single face value. + +We'll then obtain the total number of ways from the bottom end of the matrix. + + +```python +def throw_dice(n, faces, total): + ways = [[0 for _ in range(total + 1)] for _ in range(n)] + + for t in range(1, total + 1): + ways[0][t] = 1 if t <= faces else 0 + + for dice in range(1, n): + for t in range(1, total + 1): + for face in range(1, min(t, faces + 1)): + ways[dice][t] += ways[dice - 1][t - face] + + return ways[-1][-1] +``` + + +```python +throw_dice(1, 6, 7) +``` + + + + + 0 + + + + +```python +throw_dice(n=3, faces=6, total=4) +``` + + + + + 3 + + + +As you can see above, the number of ways to get a total of 4 from 3 dice is 3. +``` +dice-1 | dice-2 | dice-3 | Total +------------------------------- + 2 + 1 + 1 == 4 FIRST WAY + 1 + 2 + 1 == 4 SECOND WAY + 1 + 1 + 2 == 4 THIRD WAY +```