1891. Cutting Ribbons 🔒
Problem Description
You have an array ribbons
where each element ribbons[i]
represents the length of a ribbon. You also have an integer k
representing the number of ribbon pieces you need.
You can cut any ribbon into smaller pieces of positive integer lengths, or leave it uncut. For example, a ribbon of length 4
can be:
- Kept as length
4
- Cut into pieces of lengths
3
and1
- Cut into two pieces of length
2
- Cut into pieces of lengths
2
,1
, and1
- Cut into four pieces of length
1
Your goal is to find the maximum possible length x
such that you can obtain at least k
ribbon pieces, where each piece has the same length x
. Any leftover ribbon material after cutting can be discarded.
If it's impossible to get k
ribbons of the same length, return 0
.
For example:
- If
ribbons = [9, 7, 5]
andk = 3
, you could cut each ribbon to get pieces of length5
: from the ribbon of length9
you get one piece (with4
discarded), from length7
you get one piece (with2
discarded), and from length5
you get one piece. This gives you exactly3
pieces of length5
, so the answer is5
. - If
ribbons = [7, 5, 9]
andk = 4
, you could cut ribbons to get pieces of length4
: from length7
you get one piece, from length5
you get one piece, and from length9
you get two pieces. This gives you4
pieces of length4
, so the answer is4
.
Intuition
The key insight is recognizing a monotonic property in this problem: if we can cut k
ribbons of length x
, then we can definitely cut k
ribbons of any length smaller than x
. Why? Because any ribbon that can be cut into pieces of length x
can also be cut into pieces of smaller length - we just get more pieces or have less waste.
For example, if a ribbon of length 9
can give us one piece of length 5
(with remainder 4
), it can definitely give us at least one piece of length 4
, 3
, 2
, or 1
.
This monotonic property suggests we can use binary search. We're looking for the maximum length where we can still get at least k
pieces. Think of it as searching for a threshold value:
- For lengths that are too large, we won't be able to get
k
pieces - For lengths that are small enough, we will be able to get
k
or more pieces - We want to find the largest length in the "possible" range
The search space is from 0
(minimum possible length, representing the case where no valid solution exists) to max(ribbons)
(we can't cut pieces longer than our longest ribbon).
For any candidate length mid
, we can quickly check if it's feasible by counting how many pieces of length mid
we can get from all ribbons: for each ribbon of length ribbons[i]
, we can get ribbons[i] // mid
pieces of length mid
. If the total count is at least k
, then mid
is feasible and we should try larger values. Otherwise, we need to try smaller values.
Learn more about Binary Search patterns.
Solution Approach
We implement a binary search to find the maximum ribbon length. Here's how the algorithm works:
-
Initialize search boundaries: Set
left = 0
andright = max(ribbons)
. The left boundary starts at0
(representing the case where no valid solution exists), and the right boundary is the maximum ribbon length (we can't cut pieces longer than our longest ribbon). -
Binary search loop: While
left < right
, we:- Calculate the middle value:
mid = (left + right + 1) >> 1
- Note: We use
(left + right + 1)
instead of(left + right)
to avoid infinite loops whenleft
andright
are adjacent. The>> 1
operation is equivalent to integer division by 2.
- Note: We use
- Count how many pieces of length
mid
we can obtain:cnt = sum(x // mid for x in ribbons)
- For each ribbon of length
x
, we can cutx // mid
pieces of lengthmid
- We sum up all pieces from all ribbons
- For each ribbon of length
- Make a decision based on the count:
- If
cnt >= k
: We can obtain at leastk
pieces of lengthmid
, so this length is feasible. We try to find a larger length by settingleft = mid
- If
cnt < k
: We cannot obtaink
pieces of lengthmid
, so we need a smaller length. Setright = mid - 1
- If
- Calculate the middle value:
-
Return the result: After the loop ends,
left
contains the maximum length where we can obtain at leastk
pieces. If no valid length exists,left
will be0
.
The time complexity is O(n * log(max_ribbon))
where n
is the number of ribbons, as we perform binary search over the range [0, max_ribbon]
and for each mid value, we iterate through all ribbons to count pieces. The space complexity is O(1)
as we only use a constant amount of extra space.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the solution with ribbons = [9, 7, 5]
and k = 3
.
Step 1: Initialize boundaries
left = 0
(minimum possible length)right = 9
(maximum ribbon length)
Step 2: Binary search iterations
Iteration 1:
mid = (0 + 9 + 1) >> 1 = 5
- Count pieces of length 5:
- From ribbon of length 9:
9 // 5 = 1
piece - From ribbon of length 7:
7 // 5 = 1
piece - From ribbon of length 5:
5 // 5 = 1
piece - Total:
cnt = 1 + 1 + 1 = 3
- From ribbon of length 9:
- Since
cnt = 3 >= k = 3
, length 5 is feasible - Update:
left = 5
(try to find a larger length)
Iteration 2:
mid = (5 + 9 + 1) >> 1 = 7
- Count pieces of length 7:
- From ribbon of length 9:
9 // 7 = 1
piece - From ribbon of length 7:
7 // 7 = 1
piece - From ribbon of length 5:
5 // 7 = 0
pieces - Total:
cnt = 1 + 1 + 0 = 2
- From ribbon of length 9:
- Since
cnt = 2 < k = 3
, length 7 is not feasible - Update:
right = 7 - 1 = 6
Iteration 3:
mid = (5 + 6 + 1) >> 1 = 6
- Count pieces of length 6:
- From ribbon of length 9:
9 // 6 = 1
piece - From ribbon of length 7:
7 // 6 = 1
piece - From ribbon of length 5:
5 // 6 = 0
pieces - Total:
cnt = 1 + 1 + 0 = 2
- From ribbon of length 9:
- Since
cnt = 2 < k = 3
, length 6 is not feasible - Update:
right = 6 - 1 = 5
Step 3: Loop terminates
- Now
left = 5
andright = 5
, soleft >= right
- Return
left = 5
The maximum length where we can obtain at least 3 pieces is 5. Each ribbon contributes one piece of length 5, giving us exactly 3 pieces total.
Solution Implementation
1class Solution:
2 def maxLength(self, ribbons: List[int], k: int) -> int:
3 # Initialize binary search boundaries
4 # left: minimum possible ribbon length (0 means we might not be able to cut any ribbons)
5 # right: maximum possible ribbon length (the longest ribbon in the array)
6 left, right = 0, max(ribbons)
7
8 # Binary search for the maximum valid ribbon length
9 while left < right:
10 # Calculate middle value, using (left + right + 1) // 2 to avoid infinite loop
11 # This ensures mid is biased towards the upper half when left and right differ by 1
12 mid = (left + right + 1) // 2
13
14 # Count how many ribbons of length 'mid' we can cut from all ribbons
15 # For each ribbon, we can get ribbon_length // mid pieces
16 ribbon_count = sum(ribbon_length // mid for ribbon_length in ribbons)
17
18 # If we can cut at least k ribbons of length mid
19 if ribbon_count >= k:
20 # The answer is at least mid, search in the upper half
21 left = mid
22 else:
23 # We cannot cut k ribbons of length mid, search in the lower half
24 right = mid - 1
25
26 # Return the maximum length that allows cutting at least k ribbons
27 return left
28
1class Solution {
2 public int maxLength(int[] ribbons, int k) {
3 // Initialize binary search boundaries
4 // left: minimum possible ribbon length (0 means we might not be able to cut any ribbons)
5 // right: maximum possible ribbon length (the longest ribbon in the array)
6 int left = 0;
7 int right = 0;
8
9 // Find the maximum ribbon length to set as upper bound
10 for (int ribbon : ribbons) {
11 right = Math.max(right, ribbon);
12 }
13
14 // Binary search for the maximum valid ribbon length
15 while (left < right) {
16 // Calculate mid point, using (left + right + 1) / 2 to avoid infinite loop
17 // The +1 ensures we round up, preventing stuck conditions when left = right - 1
18 int mid = (left + right + 1) >>> 1;
19
20 // Count how many ribbons of length 'mid' we can cut
21 int count = 0;
22 for (int ribbon : ribbons) {
23 count += ribbon / mid; // Integer division gives number of pieces
24 }
25
26 // Check if we can cut at least k ribbons of length 'mid'
27 if (count >= k) {
28 // If yes, try to find a longer length
29 left = mid;
30 } else {
31 // If no, the length is too long, reduce upper bound
32 right = mid - 1;
33 }
34 }
35
36 // Return the maximum valid ribbon length found
37 return left;
38 }
39}
40
1class Solution {
2public:
3 int maxLength(vector<int>& ribbons, int k) {
4 // Binary search on the answer: find the maximum length that allows cutting k ribbons
5 // Left boundary: 0 (worst case, no valid ribbon can be cut)
6 int left = 0;
7 // Right boundary: maximum ribbon length in the array
8 int right = *max_element(ribbons.begin(), ribbons.end());
9
10 // Binary search to find the maximum valid ribbon length
11 while (left < right) {
12 // Calculate mid point, using (left + right + 1) / 2 to avoid infinite loop
13 // when left = mid (this ensures mid is biased towards the upper half)
14 int mid = (left + right + 1) >> 1;
15
16 // Count how many ribbons of length 'mid' can be cut from all ribbons
17 int ribbonCount = 0;
18 for (int ribbon : ribbons) {
19 ribbonCount += ribbon / mid; // Integer division gives number of pieces
20 }
21
22 // Check if we can cut at least k ribbons of length 'mid'
23 if (ribbonCount >= k) {
24 // If yes, try to find a larger length (move left boundary up)
25 left = mid;
26 } else {
27 // If no, the length is too large (move right boundary down)
28 right = mid - 1;
29 }
30 }
31
32 // Return the maximum valid ribbon length found
33 return left;
34 }
35};
36
1/**
2 * Finds the maximum length of ribbon pieces that can be cut from given ribbons
3 * to obtain at least k pieces of equal length
4 * @param ribbons - Array of ribbon lengths
5 * @param k - Target number of ribbon pieces needed
6 * @returns Maximum possible length of each piece, or 0 if impossible
7 */
8function maxLength(ribbons: number[], k: number): number {
9 // Binary search bounds: minimum possible length is 0
10 let left: number = 0;
11 // Maximum possible length is the longest ribbon
12 let right: number = Math.max(...ribbons);
13
14 // Binary search for the maximum valid ribbon length
15 while (left < right) {
16 // Calculate middle value, using (left + right + 1) to avoid infinite loop
17 // when left and right are adjacent (ensures mid rounds up)
18 const mid: number = (left + right + 1) >> 1;
19
20 // Count how many pieces of length 'mid' can be obtained
21 let pieceCount: number = 0;
22 for (const ribbonLength of ribbons) {
23 // Each ribbon can be cut into floor(ribbonLength / mid) pieces
24 pieceCount += Math.floor(ribbonLength / mid);
25 }
26
27 // Check if we can obtain at least k pieces
28 if (pieceCount >= k) {
29 // Valid length found, try to find a larger one
30 left = mid;
31 } else {
32 // Cannot obtain k pieces, try smaller length
33 right = mid - 1;
34 }
35 }
36
37 // Return the maximum valid length found
38 return left;
39}
40
Time and Space Complexity
The time complexity is O(n × log M)
, where n
is the number of ribbons and M
is the maximum length among all ribbons.
- The binary search runs for
O(log M)
iterations, as it searches through the range[0, max(ribbons)]
wheremax(ribbons)
represents the maximum ribbon lengthM
. - In each iteration of the binary search, we calculate
sum(x // mid for x in ribbons)
, which takesO(n)
time as it iterates through alln
ribbons. - Therefore, the overall time complexity is
O(n × log M)
.
The space complexity is O(1)
.
- The algorithm only uses a constant amount of extra space for variables
left
,right
,mid
, andcnt
. - The sum operation with generator expression
sum(x // mid for x in ribbons)
doesn't create additional data structures and computes the sum on-the-fly. - No additional data structures that scale with input size are created.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Division by Zero Error
The Pitfall: When calculating ribbon_count = sum(ribbon_length // mid for ribbon_length in ribbons)
, if mid
equals 0, this will cause a division by zero error. This happens when the binary search explores the lower boundary where mid
could potentially be 0.
Why It Happens: In the binary search loop, when left = 0
and right = 0
or right = 1
, the calculated mid
value could be 0, leading to a runtime error.
Solution: Add a check to handle the case when mid = 0
:
def maxLength(self, ribbons: List[int], k: int) -> int:
left, right = 0, max(ribbons)
while left < right:
mid = (left + right + 1) // 2
# Handle the edge case where mid is 0
if mid == 0:
return 0
ribbon_count = sum(ribbon_length // mid for ribbon_length in ribbons)
if ribbon_count >= k:
left = mid
else:
right = mid - 1
return left
2. Incorrect Mid Calculation Leading to Infinite Loop
The Pitfall: Using mid = (left + right) // 2
instead of mid = (left + right + 1) // 2
can cause an infinite loop when left
and right
are adjacent values.
Why It Happens: When left = n
and right = n + 1
, using (left + right) // 2
gives mid = n
. If ribbon_count >= k
, we set left = mid = n
, which doesn't change the state, causing an infinite loop.
Example Scenario:
left = 4, right = 5
mid = (4 + 5) // 2 = 4
- If condition is satisfied,
left = 4
(no change!) - Loop continues infinitely
Solution: Always use mid = (left + right + 1) // 2
when the update condition is left = mid
, or alternatively restructure the binary search to use left = mid + 1
:
# Alternative approach with different update logic
def maxLength(self, ribbons: List[int], k: int) -> int:
left, right = 1, max(ribbons)
result = 0
while left <= right:
mid = (left + right) // 2
ribbon_count = sum(ribbon_length // mid for ribbon_length in ribbons)
if ribbon_count >= k:
result = mid # Store the valid answer
left = mid + 1 # Try to find a larger length
else:
right = mid - 1
return result
3. Not Handling the Case Where k > Total Ribbon Length
The Pitfall: Not considering that if k
is larger than the sum of all ribbon lengths, it's impossible to cut k
pieces even of length 1.
Why It Happens: The problem states we need positive integer lengths, so the minimum piece length is 1. If sum(ribbons) < k
, we cannot possibly get k
pieces.
Solution: While the binary search naturally handles this by returning 0, you could add an early check for clarity and optimization:
def maxLength(self, ribbons: List[int], k: int) -> int:
# Early termination if impossible
if sum(ribbons) < k:
return 0
left, right = 0, max(ribbons)
# ... rest of the binary search logic
What does the following code do?
1def f(arr1, arr2):
2 i, j = 0, 0
3 new_arr = []
4 while i < len(arr1) and j < len(arr2):
5 if arr1[i] < arr2[j]:
6 new_arr.append(arr1[i])
7 i += 1
8 else:
9 new_arr.append(arr2[j])
10 j += 1
11 new_arr.extend(arr1[i:])
12 new_arr.extend(arr2[j:])
13 return new_arr
14
1public static List<Integer> f(int[] arr1, int[] arr2) {
2 int i = 0, j = 0;
3 List<Integer> newArr = new ArrayList<>();
4
5 while (i < arr1.length && j < arr2.length) {
6 if (arr1[i] < arr2[j]) {
7 newArr.add(arr1[i]);
8 i++;
9 } else {
10 newArr.add(arr2[j]);
11 j++;
12 }
13 }
14
15 while (i < arr1.length) {
16 newArr.add(arr1[i]);
17 i++;
18 }
19
20 while (j < arr2.length) {
21 newArr.add(arr2[j]);
22 j++;
23 }
24
25 return newArr;
26}
27
1function f(arr1, arr2) {
2 let i = 0, j = 0;
3 let newArr = [];
4
5 while (i < arr1.length && j < arr2.length) {
6 if (arr1[i] < arr2[j]) {
7 newArr.push(arr1[i]);
8 i++;
9 } else {
10 newArr.push(arr2[j]);
11 j++;
12 }
13 }
14
15 while (i < arr1.length) {
16 newArr.push(arr1[i]);
17 i++;
18 }
19
20 while (j < arr2.length) {
21 newArr.push(arr2[j]);
22 j++;
23 }
24
25 return newArr;
26}
27
Recommended Readings
Binary Search Speedrun For each of the Speedrun questions you will be given a binary search related problem and a corresponding multiple choice question The multiple choice questions are related to the techniques and template s introduced in the binary search section It's recommended that you have gone through at
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!