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
3and1 - 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 length9you get one piece (with4discarded), from length7you get one piece (with2discarded), and from length5you get one piece. This gives you exactly3pieces of length5, so the answer is5. - If
ribbons = [7, 5, 9]andk = 4, you could cut ribbons to get pieces of length4: from length7you get one piece, from length5you get one piece, and from length9you get two pieces. This gives you4pieces 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 boundary in a sequence of feasibility values:
Length: 1 2 3 4 5 6 7 8 9 Feasible: T T T T T F F F F ↑ first_true_index (searching from right)
Since we want the maximum feasible length, we can reframe the problem: find the first length where we cannot get k pieces. The answer is one less than that. Alternatively, we search for the last true in the feasible sequence.
The search space is from 1 (minimum valid ribbon length) to max(ribbons) (we can't cut pieces longer than our longest ribbon).
For any candidate length mid, we define feasible(mid) as: can we cut at least k pieces of length mid? We count pieces by summing ribbons[i] // mid for each ribbon. If the count is at least k, it's feasible.
Learn more about Binary Search patterns.
Solution Implementation
1class Solution: 2 def maxLength(self, ribbons: List[int], k: int) -> int: 3 def feasible(length: int) -> bool: 4 """Check if we can cut at least k pieces of the given length.""" 5 count = sum(ribbon // length for ribbon in ribbons) 6 return count >= k 7 8 # Binary search boundaries 9 left, right = 1, max(ribbons) 10 first_true_index = 0 # Default: no valid length found 11 12 while left <= right: 13 mid = (left + right) // 2 14 15 if feasible(mid): 16 # Length mid is feasible, record it and try larger 17 first_true_index = mid 18 left = mid + 1 19 else: 20 # Length mid is not feasible, try smaller 21 right = mid - 1 22 23 return first_true_index 241class Solution { 2 public int maxLength(int[] ribbons, int k) { 3 // Find the maximum ribbon length for upper bound 4 int maxRibbon = 0; 5 for (int ribbon : ribbons) { 6 maxRibbon = Math.max(maxRibbon, ribbon); 7 } 8 9 // Binary search boundaries 10 int left = 1; 11 int right = maxRibbon; 12 int firstTrueIndex = 0; // Default: no valid length found 13 14 while (left <= right) { 15 int mid = left + (right - left) / 2; 16 17 // Check if we can cut at least k pieces of length mid 18 int count = 0; 19 for (int ribbon : ribbons) { 20 count += ribbon / mid; 21 } 22 23 if (count >= k) { 24 // Length mid is feasible, record it and try larger 25 firstTrueIndex = mid; 26 left = mid + 1; 27 } else { 28 // Length mid is not feasible, try smaller 29 right = mid - 1; 30 } 31 } 32 33 return firstTrueIndex; 34 } 35} 361class Solution { 2public: 3 int maxLength(vector<int>& ribbons, int k) { 4 // Binary search boundaries 5 int left = 1; 6 int right = *max_element(ribbons.begin(), ribbons.end()); 7 int firstTrueIndex = 0; // Default: no valid length found 8 9 while (left <= right) { 10 int mid = left + (right - left) / 2; 11 12 // Check if we can cut at least k pieces of length mid 13 int count = 0; 14 for (int ribbon : ribbons) { 15 count += ribbon / mid; 16 } 17 18 if (count >= k) { 19 // Length mid is feasible, record it and try larger 20 firstTrueIndex = mid; 21 left = mid + 1; 22 } else { 23 // Length mid is not feasible, try smaller 24 right = mid - 1; 25 } 26 } 27 28 return firstTrueIndex; 29 } 30}; 311function maxLength(ribbons: number[], k: number): number { 2 const feasible = (length: number): boolean => { 3 let count = 0; 4 for (const ribbon of ribbons) { 5 count += Math.floor(ribbon / length); 6 } 7 return count >= k; 8 }; 9 10 // Binary search boundaries 11 let left = 1; 12 let right = Math.max(...ribbons); 13 let firstTrueIndex = 0; // Default: no valid length found 14 15 while (left <= right) { 16 const mid = Math.floor((left + right) / 2); 17 18 if (feasible(mid)) { 19 // Length mid is feasible, record it and try larger 20 firstTrueIndex = mid; 21 left = mid + 1; 22 } else { 23 // Length mid is not feasible, try smaller 24 right = mid - 1; 25 } 26 } 27 28 return firstTrueIndex; 29} 30Solution Approach
We implement a binary search to find the maximum ribbon length using the standard template. Since we want the maximum feasible length, we search for the first infeasible length and track the last feasible one.
Template Structure:
left, right = 1, max(ribbons) first_true_index = -1 # Tracks the answer (or 0 for this problem) while left <= right: mid = (left + right) // 2 if feasible(mid): first_true_index = mid # Record this feasible length left = mid + 1 # Try larger lengths else: right = mid - 1 # Try smaller lengths return first_true_index if first_true_index != -1 else 0
Key components:
-
Initialize: Set
left = 1(minimum valid ribbon length) andright = max(ribbons). Usefirst_true_index = 0to handle the case where no valid length exists. -
Feasibility check: For each
mid, count pieces:count = sum(ribbon // mid for ribbon in ribbons). Ifcount >= k, the length is feasible. -
Binary search logic:
- If feasible: Record
first_true_index = midand search right (left = mid + 1) for potentially larger lengths - If not feasible: Search left (
right = mid - 1) for smaller lengths
- If feasible: Record
-
Return:
first_true_indexcontains the maximum feasible length, or0if none exists.
The time complexity is O(n × log(max_ribbon)) where n is the number of ribbons. The space complexity is O(1).
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
left = 1,right = 9(max ribbon length)first_true_index = 0(default: no valid length found)
Step 2: Binary search iterations
Iteration 1: left = 1, right = 9
mid = (1 + 9) // 2 = 5- Count pieces of length 5:
- Ribbon 9:
9 // 5 = 1piece - Ribbon 7:
7 // 5 = 1piece - Ribbon 5:
5 // 5 = 1piece - Total:
3 >= k = 3✓ feasible
- Ribbon 9:
- Update:
first_true_index = 5,left = 5 + 1 = 6
Iteration 2: left = 6, right = 9
mid = (6 + 9) // 2 = 7- Count pieces of length 7:
- Ribbon 9:
9 // 7 = 1piece - Ribbon 7:
7 // 7 = 1piece - Ribbon 5:
5 // 7 = 0pieces - Total:
2 < k = 3✗ not feasible
- Ribbon 9:
- Update:
right = 7 - 1 = 6
Iteration 3: left = 6, right = 6
mid = (6 + 6) // 2 = 6- Count pieces of length 6:
- Ribbon 9:
9 // 6 = 1piece - Ribbon 7:
7 // 6 = 1piece - Ribbon 5:
5 // 6 = 0pieces - Total:
2 < k = 3✗ not feasible
- Ribbon 9:
- Update:
right = 6 - 1 = 5
Step 3: Loop terminates
- Now
left = 6 > right = 5, so loop exits - Return
first_true_index = 5
The maximum length where we can obtain at least 3 pieces is 5.
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 allnribbons. - 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. Using the Wrong Binary Search Template
The Pitfall: Using while left < right with left = mid instead of the standard while left <= right template with first_true_index tracking.
Why It Happens: There are multiple binary search variants. The left < right with left = mid variant requires careful mid = (left + right + 1) // 2 calculation to avoid infinite loops. The standard template is more straightforward.
Solution: Use the standard template:
left, right = 1, max(ribbons) first_true_index = 0 while left <= right: mid = (left + right) // 2 if feasible(mid): first_true_index = mid left = mid + 1 else: right = mid - 1 return first_true_index
2. Division by Zero Error
The Pitfall: If left starts at 0, then mid could be 0, causing division by zero when calculating ribbon // mid.
Solution: Start left = 1 since ribbon length must be at least 1. The template naturally avoids this issue.
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.
Solution: The binary search naturally handles this by returning first_true_index = 0 when no feasible length exists. You could add an early check for optimization:
if sum(ribbons) < k: return 0
Which algorithm should you use to find a node that is close to the root of the tree?
Recommended Readings
https assets algo monster cover_photos Binary_Search svg Binary Search Intuition Binary search is an efficient array search algorithm It works by narrowing down the search range by half each time If you have looked up a word in a physical dictionary you've already used binary search in real life Let's
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 If you prefer videos here's a video that explains recursion in a fun and easy way 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 him
Want a Structured Path to Master System Design Too? Don’t Miss This!