Facebook Pixel

418. Sentence Screen Fitting 🔒

Problem Description

You are given a screen with dimensions rows x cols and a list of words forming a sentence. Your task is to determine how many times the complete sentence can be displayed on the screen.

The rules for displaying the sentence are:

  • Words must appear in their original order from the sentence
  • A word cannot be split across two lines - if a word doesn't fit on the current line, it must start on the next line
  • Consecutive words on the same line must be separated by exactly one space
  • After reaching the end of the sentence, you can immediately start displaying it again (the sentence can wrap around)

For example, if you have a sentence ["hello", "world"] and a screen with 2 rows and 8 columns:

  • Row 1: hello wo (8 characters)
  • Row 2: rld hell (8 characters)

The sentence "hello world" appears once completely, with a second occurrence partially started.

The goal is to count how many complete sentences can fit when using all available rows of the screen optimally.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The key insight is to think of the sentence as a circular string that repeats infinitely. Instead of manually placing each word and tracking line breaks, we can simulate "cursor movement" through this infinite string.

Imagine concatenating all words with spaces to form a single string like "hello world " (note the trailing space). If we had unlimited horizontal space, we could just write this string repeatedly: "hello world hello world hello world...".

Now, with the screen constraint, we process row by row:

  • Each row allows us to advance our cursor by cols positions in this infinite string
  • However, we need to handle word boundaries properly

The clever part is handling word breaks:

  1. After advancing by cols positions, if we land on a space character, we're at a perfect word boundary - move forward one more position to start the next word
  2. If we land in the middle of a word, we need to backtrack to find the last space (word boundary) to ensure we don't split the word

By tracking our total cursor position after processing all rows, we can determine how many complete sentences we've traversed. Since each complete sentence has length m (including spaces), dividing the final cursor position by m gives us the answer.

This approach transforms a complex 2D placement problem into a simple 1D cursor movement problem, where we only need to track position and handle word boundary adjustments.

Solution Approach

The implementation follows the cursor movement strategy through an infinite repeating sentence string:

  1. Create the circular sentence string: Join all words with spaces and add a trailing space: s = " ".join(sentence) + " ". This ensures consistent spacing between word repetitions.

  2. Initialize tracking variables:

    • m = len(s) stores the length of one complete sentence
    • cur = 0 tracks our current position in the infinite string
  3. Process each row:

    for _ in range(rows):
        cur += cols  # Advance cursor by column width

    After each row, we move forward by cols positions.

  4. Handle word boundaries after each row:

    • Check if we landed on a space: if s[cur % m] == " ":
      • If yes, move forward one position to start the next word: cur += 1
    • Backtrack if in middle of a word:
      while cur and s[(cur - 1) % m] != " ":
          cur -= 1
      • Keep moving backward until we find a space (word boundary)
      • The modulo operation % m handles the circular nature of the string
  5. Calculate the result: return cur // m

    • Integer division of final cursor position by sentence length gives the number of complete sentences

The modulo operator % m is crucial - it maps any position in the infinite string back to the corresponding position in our base sentence string s. This allows us to check characters without actually creating an infinite string.

The algorithm runs in O(rows × average_word_length) time in the worst case, where we might need to backtrack through an entire word for each row. The space complexity is O(total_sentence_length) for storing the concatenated sentence string.

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through a concrete example with sentence = ["hello", "world"] and a screen of rows = 2, cols = 8.

Step 1: Create the circular sentence string

  • Join words with spaces: s = "hello world "
  • Length m = 12 (includes the trailing space)

Step 2: Process Row 1

  • Start with cur = 0
  • Advance cursor by cols: cur = 0 + 8 = 8
  • Check character at position 8 % 12 = 8: s[8] = 'r' (middle of "world")
  • Since we're in the middle of a word, backtrack:
    • cur = 7: s[6] = 'w' (still in word)
    • cur = 6: s[5] = ' ' (found space!)
  • Stop backtracking at cur = 6

Step 3: Process Row 2

  • Current position: cur = 6
  • Advance cursor by cols: cur = 6 + 8 = 14
  • Check character at position 14 % 12 = 2: s[2] = 'l' (middle of "hello")
  • Backtrack to find space:
    • cur = 13: s[1] = 'e' (still in word)
    • cur = 12: s[0] = 'h' (still in word)
    • cur = 11: s[11] = ' ' (found space at end of sentence!)
  • Stop backtracking at cur = 11

Step 4: Calculate result

  • Final cursor position: cur = 11
  • Complete sentences: 11 // 12 = 0

This matches our expectation - we couldn't fit even one complete sentence. The screen would show:

  • Row 1: "hello " (6 characters used, 2 spaces padding)
  • Row 2: "world " (6 characters used, 2 spaces padding)

We've only displayed 11 characters total ("hello world"), missing the final space to complete one full sentence cycle.

Solution Implementation

1class Solution:
2    def wordsTyping(self, sentence: List[str], rows: int, cols: int) -> int:
3        # Create a single string with all words separated by spaces, plus trailing space
4        # This represents one complete sentence cycle
5        sentence_string = " ".join(sentence) + " "
6        sentence_length = len(sentence_string)
7      
8        # Track the current position in the infinite repeating sentence
9        current_position = 0
10      
11        # Process each row of the screen
12        for _ in range(rows):
13            # Try to place 'cols' characters in this row
14            current_position += cols
15          
16            # If we land exactly on a space, we can start the next row cleanly
17            if sentence_string[current_position % sentence_length] == " ":
18                current_position += 1
19            # If we land in the middle of a word, we need to backtrack
20            else:
21                # Move backward until we find the start of the current word
22                # (the character after the previous space)
23                while current_position > 0 and sentence_string[(current_position - 1) % sentence_length] != " ":
24                    current_position -= 1
25      
26        # Calculate how many complete sentences we've placed
27        # by dividing total characters processed by sentence length
28        return current_position // sentence_length
29
1class Solution {
2    public int wordsTyping(String[] sentence, int rows, int cols) {
3        // Concatenate all words with spaces and add a trailing space
4        // This creates a repeating pattern: "word1 word2 word3 "
5        String repeatingPattern = String.join(" ", sentence) + " ";
6        int patternLength = repeatingPattern.length();
7      
8        // Track the current position in the infinite repeating pattern
9        int currentPosition = 0;
10      
11        // Process each row
12        while (rows-- > 0) {
13            // Add the entire column width to current position
14            // This assumes we can fit 'cols' characters in this row
15            currentPosition += cols;
16          
17            // Check the character at the current position in the pattern
18            if (repeatingPattern.charAt(currentPosition % patternLength) == ' ') {
19                // If we land on a space, move forward one position
20                // This means the next row can start with a new word
21                ++currentPosition;
22            } else {
23                // If we land in the middle of a word, we need to backtrack
24                // Move back until we find the start of this word (after a space)
25                while (currentPosition > 0 && 
26                       repeatingPattern.charAt((currentPosition - 1) % patternLength) != ' ') {
27                    --currentPosition;
28                }
29            }
30        }
31      
32        // Calculate how many complete patterns we've traversed
33        // This gives us the number of times the sentence was fitted
34        return currentPosition / patternLength;
35    }
36}
37
1class Solution {
2public:
3    int wordsTyping(vector<string>& sentence, int rows, int cols) {
4        // Concatenate all words with spaces to form a complete sentence pattern
5        string sentencePattern;
6        for (const auto& word : sentence) {
7            sentencePattern += word;
8            sentencePattern += " ";
9        }
10      
11        // Store the length of the complete sentence pattern
12        int patternLength = sentencePattern.size();
13      
14        // Track the current position in the infinite repeating sentence
15        int currentPosition = 0;
16      
17        // Process each row of the screen
18        while (rows--) {
19            // Try to fit 'cols' characters in the current row
20            currentPosition += cols;
21          
22            // Check the character at the current position in the pattern
23            if (sentencePattern[currentPosition % patternLength] == ' ') {
24                // If we land on a space, move to the next position (start of next word)
25                ++currentPosition;
26            } else {
27                // If we land in the middle of a word, backtrack to find the last space
28                // This ensures we don't break words across rows
29                while (currentPosition > 0 && 
30                       sentencePattern[(currentPosition - 1) % patternLength] != ' ') {
31                    --currentPosition;
32                }
33            }
34        }
35      
36        // Calculate how many complete sentence patterns fit
37        return currentPosition / patternLength;
38    }
39};
40
1/**
2 * Calculates how many times a sentence can be fitted on a screen with given dimensions
3 * @param sentence - Array of words forming the sentence
4 * @param rows - Number of rows on the screen
5 * @param cols - Number of columns on the screen
6 * @returns Number of times the sentence can be fitted on the screen
7 */
8function wordsTyping(sentence: string[], rows: number, cols: number): number {
9    // Join all words with spaces and add a trailing space to form a complete sentence pattern
10    const sentenceString: string = sentence.join(' ') + ' ';
11  
12    // Track the current position in the virtual infinite sentence string
13    let currentPosition: number = 0;
14  
15    // Store the length of the complete sentence pattern
16    const sentenceLength: number = sentenceString.length;
17  
18    // Process each row of the screen
19    for (let rowIndex: number = 0; rowIndex < rows; ++rowIndex) {
20        // Try to fill the current row by advancing the position by column count
21        currentPosition += cols;
22      
23        // Check the character at the current position (using modulo for cyclic behavior)
24        if (sentenceString[currentPosition % sentenceLength] === ' ') {
25            // If we land on a space, we can start the next row from the next character
26            ++currentPosition;
27        } else {
28            // If we land in the middle of a word, we need to backtrack to the last space
29            // This ensures words are not broken across rows
30            while (currentPosition > 0 && sentenceString[(currentPosition - 1) % sentenceLength] !== ' ') {
31                --currentPosition;
32            }
33        }
34    }
35  
36    // Calculate how many complete sentences fit based on the final position
37    return Math.floor(currentPosition / sentenceLength);
38}
39

Time and Space Complexity

Time Complexity: O(rows × cols)

The algorithm iterates through each row exactly once (rows iterations). In each iteration:

  • Adding cols to cur takes O(1) time
  • Checking if s[cur % m] == " " takes O(1) time
  • The while loop in the worst case can backtrack up to cols positions (when an entire row is filled with a single long word that doesn't fit)

Therefore, the worst-case time complexity is O(rows × cols).

Space Complexity: O(n × k)

Where n is the number of words in the sentence and k is the average length of each word.

  • The string s is created by joining all words in the sentence with spaces, resulting in a string of length proportional to the total characters in all words plus spaces
  • This requires O(n × k) space to store the concatenated string
  • All other variables (m, cur, loop counter) use constant O(1) space

The overall space complexity is O(n × k).

Common Pitfalls

1. Not Handling the Circular Nature Correctly

A common mistake is forgetting to use the modulo operator when checking characters, which leads to index out of bounds errors:

Incorrect:

if sentence_string[current_position] == " ":  # IndexError when current_position >= sentence_length
    current_position += 1

Correct:

if sentence_string[current_position % sentence_length] == " ":
    current_position += 1

2. Infinite Loop During Backtracking

The backtracking logic can cause an infinite loop if not bounded properly. Consider when current_position becomes 0:

Problematic:

while sentence_string[(current_position - 1) % sentence_length] != " ":
    current_position -= 1

If current_position reaches 0 and the condition is still true, (current_position - 1) % sentence_length becomes (-1) % sentence_length, which in Python equals sentence_length - 1. This could continue indefinitely if there's no space at that position.

Solution:

while current_position > 0 and sentence_string[(current_position - 1) % sentence_length] != " ":
    current_position -= 1

Adding the current_position > 0 check prevents negative indices and ensures termination.

3. Forgetting the Trailing Space

Without adding a trailing space to the sentence string, word boundaries between sentence repetitions won't be handled correctly:

Incorrect:

sentence_string = " ".join(sentence)  # No trailing space

This causes issues when one sentence ends and another begins - there would be no space separator between the last word of one instance and the first word of the next.

Correct:

sentence_string = " ".join(sentence) + " "

4. Off-by-One Error in Space Handling

When landing on a space, forgetting to advance the cursor by 1 position causes the next row to start with a space:

Incorrect:

if sentence_string[current_position % sentence_length] == " ":
    pass  # Forgot to increment

Correct:

if sentence_string[current_position % sentence_length] == " ":
    current_position += 1  # Skip the space for the next row

5. Edge Case: Single Long Word

If a single word is longer than cols, the backtracking logic could move current_position back to 0 or negative values. While the provided solution handles this with the current_position > 0 check, it's important to note that in such cases, the function correctly returns 0 (no complete sentences can fit).

Test Case to Verify:

sentence = ["verylongword"]
rows = 4
cols = 5
# Expected output: 0 (word doesn't fit in any row)
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Consider the classic dynamic programming of longest increasing subsequence:

Find the length of the longest subsequence of a given sequence such that all elements of the subsequence are sorted in increasing order.

For example, the length of LIS for [50, 3, 10, 7, 40, 80] is 4 and LIS is [3, 7, 40, 80].

What is the recurrence relation?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More