Consider the coin changing problem: Given coin denominations, devise a method to pay an amount to a customer using the fewest possible number of coins.
When we studied greedy algorithm, we saw that the greedy cashier's strategy (i.e., at each iteration we add a coin of the largest denomination that does not take us past the amount to be paid) produces an optimal solution IF the coin denominations are those of the US coins (i.e., 1, 5, 10, 25, and 100 cents). However, for other denominations, this greedy cashier's strategy might not be optimal. We saw an example in which if the coin denominations are {1, 10, 21, 34, 70, 100}, and the amount to be paid is 140 cents then the greedy cashier strategy will produce {100, 34, 1, 1, 1, 1, 1, 1} for a total of 8 coins, when the optimal answer is {70, 70} with a total of 2 coins.
In this problem you are asked to provide a dynamic programming solution to the coin changing problem that always produces an optimal solution (i.e., minimum number of coins) regardless of the coin denominations.
Input:
Output:
Solve this problem following the steps below.
Hint: Consider using a 2-dimensional array OPT[1..n][0..M], where OPT[i,j] = the minimum number of coins that are needed to make change for j cents using only the first i coin denominations d1,...,di, where 1 ≤ i ≤ n, and 0 ≤ j ≤ M (both i and j are integer numbers).Find a recurrence relation that expresses the solution of OPT[i,j] in terms of the solutions of slightly "smaller" problems (i.e., other cells in the matrix OPT whose values can be calculated before). Include suitable boundary conditions for the recurrence relation. Based on this, determine in what order you'll calculate the values of the cells in OPT. Explain.
Solution:For each index pair (i,j) with 1 ≤ i ≤ n and 0 ≤ j ≤ M, define a subproblem consisting of finding the minimum number of coins that are needed to make change for j cents using only the first i coin denominations d1,...,di. The final solution, that is the minimum number of coins needed to make change for M is the value stored in the index pair (n,M).
Recurrence relation for the optimized (in this case, minimized) objective function. We need to fill in each cell OPT(i,j). Consider coins of denomination di. The minimum number of coins from the first i denominations 1,...,i that can be used to make change for j cents, either includes denomination i or it doesn't:
- If it does NOT include denomination i, then the minimum number of coins from the first i denominations 1,...,i that can be used to make change for j cents must be equal to the minimum number of coins from the first i-1 denominations 1,...,i-1. In other words, OPT(i,j) = OPT(i-1,j).
- If it does include denomination i, then the minimum number of coins from the first i denominations 1,...,i that can be used to make change for j cents must be equal to the minimum number of coins from the first i denominations 1,...,i that can be used to make change for j-di cents plus 1. In other words, OPT(i,j) = OPT(i,j-di) + 1.
Note that i is kept the same in the right hand side of the equation above (that is, OPT(i, ...) = OPT(i, ...) + ...) because it is possible that the denomination di gets used more than once.The best of the two options above has to be the one that produces the fewer number of coins. In other words:
OPT(i,j) = minimum(OPT(i-1,j), OPT(i,j-di) + 1).This gives us the recurrence we were looking for. However, when i=1 (i.e., i-1 = 0) and/or when j < di, then we need to be careful about calculating the recurrence above, because in those cases the recurrence refers to non existing cells in the 2-dimensional array OPT (namely OPT(0,j) and/or OPT(i, "negative value"). To deal with this, we add the following conditions to our recurrence.Boundary conditions. We can fill in the values of OPT(i,j) along the edge consisting of points of the form (i,0), as we know that the minimum number of coins needed to make change for 0 cents is 0.
- If i=1 and j < di : In this case, OPT(i,j) should be ∞
- If i=1 and j ≥ di : In this case, OPT(i,j) = OPT(i,j-di) + 1
- If i > 1 and j < di : In this case, OPT(i,j) = OPT(i-1,j)
OPT(i,0) = 0 for all 1 ≤ i ≤ n.Updated order for OPT(i,j). All values OPT(i,j) with 1 ≤ i ≤ n, 1 ≤ j ≤ m will be needed. We can update these values by scanning across rows (i from 1 to n) for each value of j, increasing j to j+1 afterwards so as to eventually scan column j=1 through column j=M. We just need to make sure that when we calculate OPT(i,j), other values OPT(i',j') with i' ≤ i and j' ≤ j have already been calculated.
Solution:Coin Changing(denominations,M) Input: {Array denominations[1...n] and M.} Output: {It prints the minimum number of coins k that are needed to make change for M, and also how many coins of each denomination are included in this optimal solution with k coins.} { Assume n is a global variable. /* Initialize the 0th row and the 0th column of matrix L */ /* using the boundary conditions */ For i:=0 to n { OPT[i,0] := 0 } /* Fill in OPT using the recurrence identified above */ For i:=1 to n { For j:=1 to M { If (i==1) and (j < denominations[i]) Then OPT[i,j]:= ∞ Else If (i==1) Then OPT[i,j]:= OPT[i,j-denominations[i]] + 1 Else If (j < denominations[i]) Then OPT[i,j]:= OPT[i-1,j] Else OPT[i,j] := minimum(OPT[i-1,j], OPT[i,j-denominations[i]] + 1) } } /* Print the optimal solution */ print("Min number of coint:", OPT[n,M]) print("Coin denominations used in the optimal solution:") Print-Denominations-Optimal-Solution(OPT,denominations,n,M) } Print-Denominations-Optimal-Solution(OPT,denominations,i,j) { For k:= 1 to n num-coins-denom[k]:= 0 While j > 0 do { If OPT[i,j] == OPT[i-1,j] Then { i:= i-1 Else If OPT[i,j] == OPT[i,j-denominations[i]] + 1) Then { num-coins-denom[i] := num-coins-denom[i] + 1 j := j - denominations[i] } } } For k:= 1 to n If num-coins-denom[k] > 0 Then Print("Denomination: ", denomination[k], "# of coins:" num-coins-denom[k]) }
Solution:Our algorithm consists of 2 parts:
Hence, the runtime of the whole algorithm is Θ(n*M).
- Traversing our matrix OPT once cell by cell filling them in using the recurrence. This part takes Θ(n*M).
- Traversing our matrix OPT, from the cell OPT[n,M] "backwards" until getting to a cell OPT[.,0]. In each step either a coin is added to the optimal answer, or a denomination is skept over. Hence, the time taken by this part is Θ(n + OPT[n,M]).
The goal of this problem is to design a dynamic programming algorithm that, given two arrays, determines the length of their longest shared subarray. Note carefully that gaps are not allowed. That is, a subarray qualifies only if it matches some set of consecutive elements of each of the two input arrays. For example, suppose that the input arrays are
[15, 6, -17, 5, -5, 20, 43, 5, 33] and [-47, 5, -5, 20, 5, 33, 43].The longest qualifying subarray here is [5, -5, 20] (the sequence {5, -5, 20, 43} does not count, because it has a gap in the second array). Therefore, the desired output for this particular input instance is:
The input-output description of this problem is:
Input: Two arrays A[1..n] and B[1..m] , A of length n, and B of length m.
Output: Your pseudo-code should print the following output:
Solve this problem following the steps below.
Solution:For each index pair (i,j) with 1 ≤ i ≤ n and 1 ≤ j ≤ m, define a subproblem consisting of finding the length of the longest consecutive "suffix" subsequence of the subarrays A[1...i] and B[1...j], that is, a subsequence that ends at position i in A and at position j in B. The objective function measures the lengths of such subsequences. Define L(i,j) to be the maximum such length. In other words, L(i,j) is the length of the maximum shared subarray of A and B that ends exactly at A[i] and at B[j].
Solution:Recurrence relation for the optimized (in this case, maximized) objective function. If A[i]=B[j], then the longest consecutive common subsequence that enters into L(i,j) will simply be the concatentation of the longest subsequence that enters into L(i-1,j-1) with the common element A[i]=B[j]; hence L(i,j) = L(i-1,j-1) + 1 in this case. If A[i] ≠ B[j], then there are no common consecutive subsequences that end at index i in A and at index j in B, so L(i,j)=0 in this case. Summarizing:
| 1 + L(i-1,j-1), if A[i] = B[j] L(i,j) = | | 0, if A[i] ≠ B[j]Boundary conditions. We can fill in the values of L(i,j) along the two edges consisting of points of the form (i,0) and (0,j):L(i,0) = 0 = L(0,j) for all 1 ≤ i ≤ n and 1 ≤ j ≤ m.Updated order for L(i,j). All values L(i,j) with 1 ≤ i ≤ n, 1 ≤ j ≤ m will be needed. We can update these values by scanning across rows (i from 1 to n) for each value of j, increasing j to j+1 afterwards so as to eventually scan column j=1 through column j=m. We just need to make sure that when we calculate L(i,j), the value L(i-1,j-1) has already been calculated.
Solution:Longest Shared Subarray(A,B) Input: {Arrays A[1...n] and B[1...m].} Output: {It prints the maximum length k of a consecutive subarray A[s+1...s+k] = B[t+1...t+k] that is common to A and B, and the subarray A[s+1...s+k]} { /* Initialize the 0th row and the 0th column of matrix L */ /* using the boundary conditions */ For i:=0 to n { L[i,0] := 0 } For j:=0 to m { L[0,j] := 0 } /* Fill in L using the recurrence identified above in an */ /* order such that L[i-1,j-1] is calculated before L[i,j] is */ For j:=1 to m { For i:=1 to n { If A[i]==B[j] Then { L[i,j]:= L[i-1,j-1] + 1 } Else {L[i,j] := 0} } } /* Find the maximum value in L. That's the length of the */ /* longest shared subarray of A and B. */ max_length := 0 max_i := 0 max_j := 0 For j:=1 to m { For i:=1 to n { If L[i,j] > max_length { max_length := L[i,j] max_i := i max_j := j } } } /* Print the longest shared subarray and its length */ print("Length of Longest Shared Subarray:", max_length) print("Longest Shared Subarray:") Print-Longest-Shared-Subarray(A,B,L,max_i,max_j) } Print-Longest-Shared-Subarray(A,B,L,mi,mj) { If L[mi,mj] != 0 { Print-Longest-Shared-Subarray(A,B,L,mi - 1,mj - 1) print(A[mi]) /* note that A[mi]==B[mj] in this case */ } }
Solution:Our algorithm consists of 3 parts:
Since clearly maximum(n,m) ≤ n*m, the runtime of the whole algorithm is Θ(n*m).
- Traversing our matrix L once cell by cell filling them in using the recurrence. This part takes Θ(n*m).
- Traversing our matrix L once cell by cell looking for the maximum value stored in L. This part takes Θ(n*m).
- Traversing our matrix L, from the cell containing the maximum value, L[max_i,max_j] "diagonally backwards" to L[max_i - 1,max_j - 1], L[max_i - 2,max_j - 2] until we reach a cell with value 0. This will take O(maximum(n,m)).