806. Number of Lines To Write String
Problem Description
You have a string s
containing only lowercase English letters and an array widths
of length 26. Each element in widths
represents the pixel width of the corresponding lowercase letter - widths[0]
is the width of 'a', widths[1]
is the width of 'b', and so on through widths[25]
for 'z'.
You need to write the string s
across multiple lines following these rules:
- Each line can hold a maximum of
100
pixels width - Start writing from the beginning of
s
and fit as many complete letters as possible on the first line without exceeding 100 pixels - Continue from where you left off, writing as many letters as possible on each subsequent line
- Keep going until all letters in
s
have been written
The task is to return an array of two integers:
- The first element should be the total number of lines needed to write the entire string
- The second element should be the pixel width used on the last line
For example, if you're writing a string where adding the next letter would make the current line exceed 100 pixels, you must start a new line with that letter instead.
Intuition
The problem is essentially about simulating the process of writing text with a line width constraint. Think of it like typing on an old typewriter that automatically moves to the next line when you reach the right margin.
The key insight is that we need to keep track of two things as we process each character:
- How many lines we've used so far
- How much space is currently used on the current line
For each character in the string, we face a simple decision: can it fit on the current line or not?
If we're currently at position last
pixels on the current line and want to add a character with width w
, we check if last + w <= 100
. If yes, we simply add the character to the current line by updating last += w
. If no, we must start a new line, which means incrementing our line counter and resetting the current line width to just the width of this character (last = w
).
The process naturally simulates what happens when writing with a fixed-width constraint - we greedily fit as many characters as possible on each line before moving to the next one. Since we start with lines = 1
(we always have at least one line) and last = 0
(initially nothing is written), we can process each character sequentially and maintain these two values throughout.
The beauty of this approach is its simplicity - we don't need to look ahead or backtrack. We just process each character in order, making the immediate decision of whether it fits on the current line or requires a new one.
Solution Approach
The solution uses a simple simulation approach to solve the problem. We process each character in the string sequentially and maintain two variables to track our progress.
Variables:
lines
: Counts the total number of lines used (initialized to 1 since we always have at least one line)last
: Tracks the current width used on the current line (initialized to 0)
Algorithm Steps:
-
Initialize the tracking variables: Start with
lines = 1
andlast = 0
. -
Process each character: For each character
c
in the strings
:- Calculate its width:
w = widths[ord(c) - ord('a')]
- This uses ASCII values to map each character to its corresponding index in the
widths
array - For example, 'a' maps to index 0, 'b' to index 1, etc.
- This uses ASCII values to map each character to its corresponding index in the
- Calculate its width:
-
Check if the character fits on the current line:
- If
last + w <= 100
: The character fits on the current line- Add its width to the current line:
last += w
- Add its width to the current line:
- Otherwise: The character doesn't fit and needs a new line
- Increment the line counter:
lines += 1
- Start the new line with just this character:
last = w
- Increment the line counter:
- If
-
Return the result: After processing all characters, return
[lines, last]
The implementation uses Python's map
function with a lambda expression to elegantly convert each character to its width: map(lambda c: widths[ord(c) - ord("a")], s)
. This creates an iterator that yields the width of each character as we process the string.
Time Complexity: O(n)
where n
is the length of string s
, as we process each character exactly once.
Space Complexity: O(1)
as we only use a constant amount of extra space for the two tracking variables.
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 a concrete example to illustrate how the solution works.
Input:
s = "abcdefghij"
widths = [10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10]
(Each letter has width 10 pixels)
Step-by-step simulation:
Initialize: lines = 1
, last = 0
-
Character 'a' (width = 10):
- Check:
last + 10 = 0 + 10 = 10 ≤ 100
✓ - Add to current line:
last = 10
- Status: Line 1 has "a" (10 pixels used)
- Check:
-
Character 'b' (width = 10):
- Check:
last + 10 = 10 + 10 = 20 ≤ 100
✓ - Add to current line:
last = 20
- Status: Line 1 has "ab" (20 pixels used)
- Check:
-
Characters 'c' through 'j':
- Each adds 10 more pixels to the current line
- After 'c':
last = 30
(Line 1: "abc") - After 'd':
last = 40
(Line 1: "abcd") - After 'e':
last = 50
(Line 1: "abcde") - After 'f':
last = 60
(Line 1: "abcdef") - After 'g':
last = 70
(Line 1: "abcdefg") - After 'h':
last = 80
(Line 1: "abcdefgh") - After 'i':
last = 90
(Line 1: "abcdefghi") - After 'j':
last = 100
(Line 1: "abcdefghij")
Result: [1, 100]
- We used 1 line with 100 pixels on the last (and only) line.
Another example with line wrapping:
Input:
s = "bbbcccdddaa"
widths = [4,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10]
(Letter 'a' has width 4, letters 'b', 'c', 'd' have width 10)
Initialize: lines = 1
, last = 0
- 'b' (width = 10):
0 + 10 ≤ 100
✓ →last = 10
- 'b' (width = 10):
10 + 10 ≤ 100
✓ →last = 20
- 'b' (width = 10):
20 + 10 ≤ 100
✓ →last = 30
- 'c' (width = 10):
30 + 10 ≤ 100
✓ →last = 40
- 'c' (width = 10):
40 + 10 ≤ 100
✓ →last = 50
- 'c' (width = 10):
50 + 10 ≤ 100
✓ →last = 60
- 'd' (width = 10):
60 + 10 ≤ 100
✓ →last = 70
- 'd' (width = 10):
70 + 10 ≤ 100
✓ →last = 80
- 'd' (width = 10):
80 + 10 ≤ 100
✓ →last = 90
- 'd' (width = 10):
90 + 10 ≤ 100
✓ →last = 100
- 'a' (width = 4):
100 + 4 = 104 > 100
✗- Start new line:
lines = 2
,last = 4
- Start new line:
- 'a' (width = 4):
4 + 4 ≤ 100
✓ →last = 8
Result: [2, 8]
- We used 2 lines, with 8 pixels on the last line.
The visual representation would be:
- Line 1: "bbbcccddd" (100 pixels)
- Line 2: "aa" (8 pixels)
Solution Implementation
1class Solution:
2 def numberOfLines(self, widths: List[int], s: str) -> List[int]:
3 # Initialize line counter and current line width
4 line_count = 1
5 current_line_width = 0
6
7 # Process each character in the string
8 for char in s:
9 # Get the width of the current character
10 # widths[0] corresponds to 'a', widths[1] to 'b', etc.
11 char_width = widths[ord(char) - ord('a')]
12
13 # Check if adding this character exceeds the line limit of 100 units
14 if current_line_width + char_width <= 100:
15 # Character fits on current line
16 current_line_width += char_width
17 else:
18 # Need to start a new line
19 line_count += 1
20 current_line_width = char_width
21
22 # Return the total number of lines and the width used on the last line
23 return [line_count, current_line_width]
24
1class Solution {
2 /**
3 * Calculate the number of lines needed to write a string and the width of the last line.
4 * Each line can hold a maximum of 100 units.
5 *
6 * @param widths An array where widths[i] represents the width of character ('a' + i)
7 * @param s The string to be written
8 * @return An array containing [number of lines, width of last line]
9 */
10 public int[] numberOfLines(int[] widths, String s) {
11 // Initialize line counter and current line width
12 int lineCount = 1;
13 int currentLineWidth = 0;
14
15 // Process each character in the string
16 for (int i = 0; i < s.length(); i++) {
17 // Get the width of the current character
18 // Convert character to index (0-25) by subtracting 'a'
19 int characterWidth = widths[s.charAt(i) - 'a'];
20
21 // Check if adding this character exceeds the line limit
22 if (currentLineWidth + characterWidth <= 100) {
23 // Character fits in current line, add its width
24 currentLineWidth += characterWidth;
25 } else {
26 // Character doesn't fit, start a new line
27 lineCount++;
28 currentLineWidth = characterWidth;
29 }
30 }
31
32 // Return the result as an array
33 return new int[] {lineCount, currentLineWidth};
34 }
35}
36
1class Solution {
2public:
3 vector<int> numberOfLines(vector<int>& widths, string s) {
4 // Initialize line counter and current line width
5 int lineCount = 1; // Start with first line
6 int currentLineWidth = 0; // Track width used in current line
7
8 // Process each character in the string
9 for (char currentChar : s) {
10 // Get the width of current character (widths[0] = 'a', widths[1] = 'b', etc.)
11 int charWidth = widths[currentChar - 'a'];
12
13 // Check if adding current character exceeds line limit (100 units)
14 if (currentLineWidth + charWidth <= 100) {
15 // Character fits in current line, add its width
16 currentLineWidth += charWidth;
17 } else {
18 // Character doesn't fit, start a new line
19 lineCount++;
20 currentLineWidth = charWidth; // New line starts with current character
21 }
22 }
23
24 // Return the total number of lines and width of the last line
25 return {lineCount, currentLineWidth};
26 }
27};
28
1/**
2 * Calculates the number of lines needed to write a string and the width of the last line
3 * @param widths - Array of 26 integers representing the width of each lowercase letter a-z
4 * @param s - The string to be written
5 * @returns Array with [number of lines, width of last line]
6 */
7function numberOfLines(widths: number[], s: string): number[] {
8 // Initialize line count to 1 and current line width to 0
9 let lineCount: number = 1;
10 let currentLineWidth: number = 0;
11
12 // Process each character in the string
13 for (const character of s) {
14 // Calculate the width of the current character
15 // Get the index by subtracting 'a' from the character's ASCII value
16 const characterWidth: number = widths[character.charCodeAt(0) - 'a'.charCodeAt(0)];
17
18 // Check if adding this character would exceed the line limit of 100 units
19 if (currentLineWidth + characterWidth <= 100) {
20 // Character fits on current line, add its width
21 currentLineWidth += characterWidth;
22 } else {
23 // Character doesn't fit, start a new line
24 lineCount++;
25 currentLineWidth = characterWidth;
26 }
27 }
28
29 // Return the total number of lines and the width of the last line
30 return [lineCount, currentLineWidth];
31}
32
Time and Space Complexity
The time complexity is O(n)
, where n
is the length of the string s
. This is because the algorithm iterates through each character in the string exactly once. The map
function processes each character to get its corresponding width value from the widths
array, and for each character, it performs constant-time operations: looking up the width value using ord(c) - ord("a")
(which is O(1)
), comparing last + w <= 100
(which is O(1)
), and updating the lines
and last
variables (which are O(1)
operations).
The space complexity is O(1)
. The algorithm only uses a fixed amount of extra space regardless of the input size. It maintains two integer variables (lines
and last
) to track the current state, and the map
function creates an iterator that processes elements one at a time without storing them all in memory. The space used does not grow with the size of the input string s
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Off-by-One Error in Line Counting
A common mistake is incorrectly initializing the line counter or forgetting to start with at least one line. Some might initialize line_count = 0
thinking they'll increment it when adding characters, but this leads to an incorrect count.
Incorrect approach:
line_count = 0 # Wrong initialization
for char in s:
char_width = widths[ord(char) - ord('a')]
if current_line_width + char_width <= 100:
current_line_width += char_width
else:
line_count += 1
current_line_width = char_width
# This misses counting the first/last line properly
Solution: Always initialize line_count = 1
since you start writing on the first line immediately.
2. Incorrect Character-to-Index Mapping
Another frequent error is using the wrong formula to map characters to their width indices. Common mistakes include:
- Using
ord(char) - ord('A')
instead oford(char) - ord('a')
(wrong case) - Directly using
widths[char]
without converting to index - Off-by-one errors like
ord(char) - ord('a') + 1
Incorrect approaches:
# Wrong: Using uppercase 'A'
char_width = widths[ord(char) - ord('A')]
# Wrong: Treating char as index directly
char_width = widths[char]
# Wrong: Adding unnecessary offset
char_width = widths[ord(char) - ord('a') + 1]
Solution: Use the correct formula: widths[ord(char) - ord('a')]
where ord('a')
gives the ASCII value of lowercase 'a'.
3. Resetting Line Width Incorrectly
When starting a new line, some might forget to include the current character's width or might accidentally carry over the previous line's width.
Incorrect approach:
if current_line_width + char_width > 100: line_count += 1 current_line_width = 0 # Wrong: should be char_width, not 0
Solution: When starting a new line, set current_line_width = char_width
to account for the character that triggered the new line.
4. Edge Case: Empty String
While not explicitly mentioned in the problem, handling an empty string could cause issues if not considered.
Solution: Add a check at the beginning:
if not s: return [0, 0] # or [1, 0] depending on requirements
5. Using Wrong Comparison Operator
Using <
instead of <=
when checking if a character fits on the current line.
Incorrect approach:
if current_line_width + char_width < 100: # Wrong: should be <= current_line_width += char_width
Solution: Use <=
to allow exactly 100 pixels on a line: if current_line_width + char_width <= 100:
Depth first search is equivalent to which of the tree traversal order?
Recommended Readings
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
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!