n
and a set of possible ribbon lengths. We need to cut the ribbon into the maximum number of pieces that comply with the above-mentioned possible lengths. Write a method that will return the count of pieces.Example 1
n: 5
Ribbon Lengths: {2,3,5}
Output: 2
Explanation: Ribbon pieces will be {2,3}.
Example 2
n: 7
Ribbon Lengths: {2,3}
Output: 3
Explanation: Ribbon pieces will be {2,2,3}.
Example 3
n: 13
Ribbon Lengths: {3,5,7}
Output: 3
Explanation: Ribbon pieces will be {3,3,7}.
n
, we need to find the maximum number of pieces that the ribbon can be cut into.A basic brute-force solution could be to try all combinations of the given lengths to select the maximum one that gives the total length of n
. This is what our algorithm will look like:
for each length 'l'
create a new set which includes one quantity of length 'l' if it does not exceed 'n', and
recursively call to process all lengths
create a new set without length 'l', and recursively call to process the remaining lengths
return the number of pieces from the above two sets with a higher number of pieces
Here is the code for the brute-force solution:
import math
def count_ribbon_pieces(ribbonLengths, total):
maxPieces = count_ribbon_pieces_recursive(ribbonLengths, total, 0)
return -1 if maxPieces == -math.inf else maxPieces
def count_ribbon_pieces_recursive(ribbonLengths, total, currentIndex):
# base check
if total == 0:
return 0
n = len(ribbonLengths)
if n == 0 or currentIndex >= n:
return -math.inf
# recursive call after selecting the ribbon length at the currentIndex
# if the ribbon length at the currentIndex exceeds the total, we shouldn't process this
c1 = -math.inf
if ribbonLengths[currentIndex] <= total:
result = count_ribbon_pieces_recursive(
ribbonLengths, total - ribbonLengths[currentIndex], currentIndex)
if result != -math.inf:
c1 = result + 1
# recursive call after excluding the ribbon length at the currentIndex
c2 = count_ribbon_pieces_recursive(ribbonLengths, total, currentIndex + 1)
return max(c1, c2)
def main():
print(count_ribbon_pieces([2, 3, 5], 5))
print(count_ribbon_pieces([2, 3], 7))
print(count_ribbon_pieces([3, 5, 7], 13))
print(count_ribbon_pieces([3, 5], 7))
main()
Since this problem is quite similar to Minimum Coin Change, let’s jump on to the bottom-up dynamic programming solution.
Let’s try to populate our array dp[ribbonLength][total+1]
for every possible ribbon length with a maximum number of pieces.
So for every possible length len
(0 <= len <= total) and for every possible ribbon length index (0 <= index < ribbonLengths.length), we have two options:
Exclude the ribbon length: In this case, we will take the maximum piece count from the previous set => dp[index-1][len]
Include the ribbon length if its value is not more than len
: In this case, we will take the maximum pieces needed to get the remaining total, plus include 1
for the current ribbon length => 1 + dp[index][len-ribbonLengths[index]]
Finally, we will take the maximum of the above two values for our solution:
dp[index][len] = max(dp[index-1][len], 1 + dp[index][len-ribbonLengths[index]])
Here is the code for our bottom-up dynamic programming approach:
import math
def count_ribbon_pieces(ribbonLengths, total):
n = len(ribbonLengths)
dp = [[-math.inf for _ in range(total+1)] for _ in range(n)]
# populate the total=0 columns, as we don't need any ribbon to make zero total
for i in range(n):
dp[i][0] = 0
for i in range(n):
for t in range(1, total+1):
if i > 0: # exclude the ribbon
dp[i][t] = dp[i - 1][t]
# include the ribbon and check if the remaining length can be cut into available lengths
if t >= ribbonLengths[i] and dp[i][t - ribbonLengths[i]] != -math.inf:
dp[i][t] = max(dp[i][t], dp[i][t - ribbonLengths[i]] + 1)
# total combinations will be at the bottom-right corner, return '-1' if cutting is not possible
return -1 if dp[n - 1][total] == -math.inf else dp[n - 1][total]
def main():
print(count_ribbon_pieces([2, 3, 5], 5))
print(count_ribbon_pieces([2, 3], 7))
print(count_ribbon_pieces([3, 5, 7], 13))
print(count_ribbon_pieces([3, 5], 7))
main()
✓→ Fibonacci Numbers